- A+
所属分类:JavaScript
在这个全民直播的时代,在线视频直播已经成为我们饭后必看的内容了。视频直播中有个弹幕功能,相信大家也玩过其实这个类似一个聊天室。今天要讲的内容就是使用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就是其中一个。环境准备
- Chrome
- PHP 7.1.* swoole2.0.*
- Nginx
- Node.js Npm 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";
}
}
注意: 定义通过以上两个类我们完成了推送接收消息,接下来我们要完成Html页面的内容制作和Websocket(JavaScript)的脚本编写。Html页面我们使用Bootstrap构建的模板private $message
是因为数据帧不完整,一个WebSocket请求可能会分成多个数据帧进行发送,所有我们必须使用$frame->finish
来检测数据帧的完整性。在不完整的情况我们使用类属性$message
来保存帧数据。
静态页面
- 创建一个html5标准的index.html, chat.js, app.css三个文件。
- 打开 https://bootsnipp.com/snippets/WaEvr地址。
- 将上地址的HTML、CSS、JS页签的内容拷贝到index.html, app.css, chat.js对应的文件
- 添加依赖到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;
}

- 编写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
- 绑定域名,查看效果

项目地址
推荐阅读
- 我的微信
- 这是我的微信扫一扫
-
- 我的微信公众号
- 我的微信公众号扫一扫
-