php中就不能不知道swoole这个扩展了,有了这个扩展很多不可能就变成了可能。
借助于swoole提供的websocket机制,实现一个websocket服务器其实非常简单,我们只需要关注如何正确的管理用户链接以及状态。
实现要点:
- 使用swoole_table 在进程间共享数据,用来存储房间号中的fd列表。
- 处理订阅以及取消订阅的时候要加锁。(涉及到fd列表的反序列化)
我们一起来看看完整的实现
<?php
/**
* websocket 客户端
*/
date_default_timezone_set('Asia/Shanghai');
class Server
{
private $_server;
private $_lock;
private $_table;
private $_config = [];
public function __construct($config)
{
$this->_config = $config;
$this->_lock = new \Swoole\Lock(SWOOLE_MUTEX);
$this->_server = new \Swoole\Websocket\Server($this->_config['ws']['host'], $this->_config['ws']['port']);
$this->_table = new \Swoole\Table($config['ws']['max_channel']);
$this->_table->column('fds', \Swoole\Table::TYPE_STRING, $config['ws']['max_fds'] * 2);// fds 以逗号分隔
$this->_table->create();
}
public function run()
{
$this->_server->on('start', [$this, 'onStart']);
$this->_server->on('open', [$this, 'onOpen']);
$this->_server->on('message', [$this, 'onMessage']);
$this->_server->on('request', [$this, 'onRequest']);
$this->_server->on('close', [$this, 'onClose']);
$this->_server->set($this->_config['websocket_server']);
$this->_server->start();
}
/**
* 打开socket链接回调
*/
public function onOpen($ws, $frame)
{
// 不是websocket链接 有可能是常规的http
if (!$this->is_webscoket($frame->fd)) {
return;
}
$this->broadcast('internal.open', json_encode(['fd' => $frame->fd]));
$this->log('info', "user open the connection", "fd=". $frame->fd);
}
/**
* 程序启动回调
*/
public function onStart($server)
{
$this->log('info', "websocket start success", "address=ws://". $this->_config['ws']['host'] . ":" . $this->_config['ws']['port']);
}
/**
* 收到ws消息的回调 {"event": "", "channel": "", "cid":0}
*/
public function onMessage($server, $frame)
{
$req = json_decode($frame->data, true);
if (empty($req)) {
$this->push($frame->fd, $this->pack($req, [], -1, 'empty request'));
return;
}
if (!isset(