Swoole和Websocket实现简易聊天室

2,569次阅读

共计 692 个字符,预计需要花费 2 分钟才能阅读完成。

在这个全民直播的时代,在线视频直播已经成为我们饭后必看的内容了。视频直播中有个弹幕功能,相信大家也玩过其实这个类似一个聊天室。今天要讲的内容就是使用 SwooleWebsocket怎么实现一个简易聊天室,下面图片就是最终实现出来的效果。

Swoole 和 Websocket 实现简易聊天室

什么是 Websocket

WebSocket 是一种计算机通信协议,通过单个 TCP 连接提供全双工通信信道。WebSocket 协议在 2011 年被 IETF 标准化为 RFC6455,WebSocket 旨在在 Web 浏览器和 Web 服务器中实现,但可由任何客户端或服务器应用程序使用。WebSocket 协议是独立的基于 TCP 的协议。它与 HTTP 的唯一关系是它的握手被 HTTP 服务器解释为升级请求。WebSocket 协议允许浏览器和 Web 服务器之间的交互具有较低的开销,从而实现从服务器的实时数据传输。大多数主要浏览器(包括 Google Chrome,Microsoft Edge,Internet Explorer,Firefox,Safari 和 Opera)都支持 WebSocket 协议,大部分程序语言都可实现 Websocket 服务 PHP 的 Swoole 就是其中一个。

环境准备

  1. Chrome
  2. PHP 7.1.* swoole2.0.*
  3. Nginx
  4. Node.js Npm Webpack2

上面 Nginx 可选,我用的环境是VagrantPHP(v7.1.4)Chrome(v60)(Node.js(v6.10)Webpack2

开始工作

使用 Swoole 绑定事件实现消息接收和广播消息。广播消息使所有连上服务的 socket 都能收到其它 socket 的消息,从而达到主动推送到客户端。

绑定事件

 \'0.0.0.0\',
        \'port\' => 9527,
    ];

    public function __construct(array $config = [])
    {foreach ($config as $key => $value) {if (array_key_exists($key, $this->config)) {$this->config[$key] = $value;
            }
        }
        $this->initialize();}

    /**
     * 初始化 socket,绑定 open, message, close 回调事件
     * @see https://wiki.swoole.com/wiki/page/397.html
     */
    private function initialize()
    {$this->socket = new WebSocket($this->config[\'host\'], $this->config[\'port\']);

        foreach ([\'open\', \'message\', \'close\'] as $callback) {
            # code...
            $this->socket->on($callback, [$this, $callback]);
        }
    }

    // 返回所有 socket
    public function getConnections()
    {return $this->socket->connections;
    }

    public function open(WebSocket $server, Request $request)
    {echo $request->fd . \'--open\';        
    }

    public function close(WebSocket $server, $fd)
    {echo "$fd--close";}

    // 开启服务
    public function run()
    {$this->socket->start();}
}

接收推送消息

data == "new user") {$this->online  ;
        } else {$this->message .= $frame->data;
            if ($frame->finish) {$message = $this->message;
                $this->message = \'\';
                // 遍历所有连接,将当前消息推送给其它的连接(客户端)
                foreach ($this->getConnections() as $fd) {if ($frame->fd === $fd) continue;
                    $server->push($fd, $message);
                }
            }       
        }
    }

    // 重写 close,在连接断开之后人数自减
    public function close(WebSocket $server, $fd)
    {$this->online--;
        echo "$fd--close";
    }
}

注意: 定义 private $message 是因为数据帧不完整,一个 WebSocket 请求可能会分成多个数据帧进行发送,所有我们必须使用$frame->finish 来检测数据帧的完整性。在不完整的情况我们使用类属性 $message 来保存帧数据。

通过以上两个类我们完成了推送接收消息,接下来我们要完成 Html 页面的内容制作和 Websocket(JavaScript)的脚本编写。Html 页面我们使用 Bootstrap 构建的模板

静态页面

  1. 创建一个 html5 标准的 index.html, chat.js, app.css 三个文件。
  2. 打开 https://bootsnipp.com/snippets/WaEvr地址。
  3. 将上地址的 HTML、CSS、JS 页签的内容拷贝到 index.html, app.css, chat.js 对应的文件
  4. 添加依赖到 index.html
     	
     	
     	

    

上面这个开源的模板是没有聊天室昵称功能,我们要为它加上昵称功能。

  • 修改 HTML 将以下内容添加到 body 之内


  • 修改 CSS 将以下内容添加到 app.css
  .popup {
        position:absolute;
        width:100%;
        height:100%;
        background-color:#f4645f
  }
  .popup .form {
      height: 100px;
      margin-top: -100px;
      position: absolute;
      text-align: center;
      top: 50%;
      width: 100%;
  }
  .form .title, .form .usernameInput {
      color: #fff;
      font-weight: 100;
      font-size: 200%;
  }
  .form .usernameInput {
    background-color: transparent;
    border: none;
    border-bottom: 2px solid #fff;
    outline: none;
    padding-bottom: 15px;
    text-align: center;
    width: 400px;
  }
Swoole 和 Websocket 实现简易聊天室
  • 编写 websocket 代码
function Socket() {if (!(this instanceof Socket)) return new Socket();
    var _this = this;
    //socket 连接状态
    this.isConnection = false;
    this.message = null;
    // 这个是我本地 websocket 的服务
    this.socket = new WebSocket("ws://192.168.56.101:9527");
    this.socket.onopen = function(event) {
        _this.isConnection = true;
        // 连接成功,向服务端发送一个 new user 的信息, 表示一个新的用户连接上了
        _this.socket.send("new user");
    }
    // 接收服务端推送的消息
    this.socket.onmessage = function(event) {if (_this.message) _this.message(event.data);
        else console.log(event);
    };
    this.socket.onclose = function() {_this.isConnection = false;}
};
Socket.prototype.send = function(message) {if (this.isConnection) {this.socket.send(message);
    }   
}
Socket.prototype.bind = function(callback) {this.message = callback;}
Socket.prototype.close = function() {this.socket.close();
}
  • chat.js 最终的完整代码
require("css/app.css");
global.$ = window.$ = require(\'jquery\');

(function () {
    var Message;
    Message = function (arg) {
        this.text = arg.text, this.message_side = arg.message_side, this.user = arg.user;
        this.draw = function (_this) {return function () {
                var $message;
                $message = $($(\'.message_template\').clone().html());
                $message.addClass(_this.message_side).find(\'.text\').html(_this.text);
                $message.find(".avatar").html(_this.user);
                $(\'.messages\').append($message);
                return setTimeout(function () {return $message.addClass(\'appeared\');
                }, 0);
            };
        }(this);
        return this;
    };
    function Socket() {if (!(this instanceof Socket)) return new Socket();
        var _this = this;
        this.isConnection = false;
        this.message = null;
        this.socket = new WebSocket("ws://192.168.56.101:9527");
        this.socket.onopen = function(event) {
            _this.isConnection = true;
            _this.socket.send("new user");
        }
        this.socket.onmessage = function(event) {if (_this.message) _this.message(event.data);
            else console.log(event);
        };
        this.socket.onclose = function() {_this.isConnection = false;}
    };
    Socket.prototype.send = function(message) {if (this.isConnection) {this.socket.send(message);
        }   
    }
    Socket.prototype.bind = function(callback) {this.message = callback;}
    Socket.prototype.close = function() {this.socket.close();
    }
    $(function () {
        var getMessageText, message_side, sendMessage, userName, chat;
        message_side = \'right\';
        getMessageText = function () {
            var $message_input;
            $message_input = $(\'.message_input\');
            return $message_input.val();};
        sendMessage = function (text) {var $messages, message, messageStruct = JSON.parse(text);
            if (text.trim() === \'\') {return;}
            $(\'.message_input\').val(\'\');
            $messages = $(\'.messages\');
            message_side = userName == messageStruct.user ? \'right\' : \'left\';
            message = new Message({
                text: messageStruct.message,
                message_side: message_side,
                user: messageStruct.user
            });
            message.draw();
            return $messages.animate({scrollTop: $messages.prop(\'scrollHeight\') }, 300);
        };
        $(\'.send_message\').click(function (e) {var text = getMessageText();
            if (text.trim() === \'\') return ;
            var message = JSON.stringify({user: userName, message: text});
            chat.send(message);
            return sendMessage(message);
        });
        $(\'.message_input\').keyup(function (e) {if (e.which === 13) {var text = getMessageText();
                if (text.trim() === \'\') return ;
                var message = JSON.stringify({user: userName, message: text});
                chat.send(message);
                return sendMessage(message);
            }
        });
        $(".usernameInput").on("keyup", function(e) {var val = $(this).val();
            if (val != "" && e.keyCode == 13) {
                userName = val;
                $(".popup").remove();
                chat = new Socket();
                chat.bind(sendMessage);
            }
        });
        $(window).on("unload", function(e) {if (chat) {chat.close(); 
                chat = null;
            }
        });
        $(window).on("beforeunload", function(e) {if (chat) {chat.close(); 
                chat = null;
            }
        });

        /**
        sendMessage(\'Hello Philip! :)\');
        setTimeout(function () {return sendMessage(\'Hi Sandy! How are you?\');
        }, 1000);
        return setTimeout(function () {return sendMessage(\'I\\'m fine, thank you!\');
        }, 2000);
        */
    });
}.call(this));
  • 配置 webpack
const path = require("path");
const webpack = require(\'webpack\')
// importing plugins that do not come by default in webpack
const ExtractTextPlugin = require(\'extract-text-webpack-plugin\');
const HtmlWebpackPlugin = require(\'html-webpack-plugin\');

const css = new ExtractTextPlugin(\'app.css\');

const plugins = [


];

const sourcePath = path.join(__dirname, "./src");

const buildPath = path.join(__dirname, "./public/dist");
module.exports = {
    context: sourcePath,
    // 预编译入口
    entry: "./chat.js",
    // 预编译输出
    output: {
        // options related to how webpack emits results

        path: buildPath, // string
        // the target directory for all output files
        // must be an absolute path (use the Node.js path module)

        filename: "bundle.js", // string
        // the filename template for entry chunks

        publicPath: "./public", // string
        // the url to the output directory resolved relative to the HTML page

        library: "", // string,
        // the name of the exported library

        libraryTarget: "umd", // universal module definition
        // the type of the exported library

        /* Advanced output configuration (click to show) */
    },

    module: {

        rules: [
            {
                test: /\.css$/,
                use: css.extract([\'css-loader\'])
            },
            {test: /\.(html|svg|jpe?g|png|ttf|woff2?)$/,
                exclude: /node_modules/,
                use: {
                    loader: \'file-loader\',
                    options: {name: \'static/[name]-[hash:8].[ext]\',
                    },
                },
            }
        ]
    },
    resolve: {extensions: [\'.webpack-loader.js\', \'.web-loader.js\', \'.loader.js\', \'.js\', \'.jsx\'],
        modules: [path.resolve(__dirname, \'node_modules\'), sourcePath],
    },
    plugins: [
        new webpack.optimize.CommonsChunkPlugin({
            async: true,
            children: true,
            minChunks: 2,
        }),

        // setting production environment will strip out
        // some of the development code from the app
        // and libraries
        new webpack.DefinePlugin({\'process.env\': { NODE_ENV: JSON.stringify(process.env.NODE_ENV) }
        }),

        // create css bundle
        css
    ]
}
  • 执行 webpack 生成文件

整个前端流程到此结束

最终工作

  • 创建 websocket 服务
run();
  • 启动服务
[root@meshell chat]# php bin/chat.php
  • 绑定域名,查看效果
Swoole 和 Websocket 实现简易聊天室

项目地址

https://github.com/TianLiangZhou/loocode-example

推荐阅读

  1. https://www.w3.org/TR/2009/WD-websockets-20091222/
  2. https://developer.mozilla.org/en/docs/Web/API/WebSocket
  3. https://tools.ietf.org/html/rfc6455
  4. https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API

正文完
 
Blood.Cold
版权声明:本站原创文章,由 Blood.Cold 2019-06-04发表,共计692字。
转载说明:除特殊说明外本站文章皆由CC-4.0协议发布,转载请注明出处。