前面我们已经简单的了解了websocket在服务端的一些方法和属性使用,这里我来将websocket处理整合成类方便websocket使用
一:创建一个websocket基类(抽象类)
/**
* websocket server基类
*/
abstract class Server
{
/**
* @var \swoole_websocket_server
*/
public $server;
/**
* @var boolean 是否开启自定义握手处理
*/
public $handshake = false;
public function __construct($host = null, $port = null, $config = [])
{
$this->server = new \swoole_websocket_server($host, $port);
//Server配置选项
$this->server->set($config);
// Server启动在主进程的主线程回调此函数
$this->server->on('start', [$this, 'onStart']);
//WebSocket建立连接后进行握手处理
$this->handshake && $this->server->on('handshake', [$this, 'onHandshake']);
//监听WebSocket成功并完成握手回调事件
$this->server->on('open', [$this, 'onOpen']);
//监听WebSocket消息事件
$this->server->on('message', [$this, 'onMessage']);
//使用http请求时执行,及直接在浏览器上输入websocket地址
$this->server->on('request', [$this, 'onRequest']);
//监听WebSocket连接关闭事件
$this->server->on('close', [$this, 'onClose']);
//此事件在Server正常结束时发生
$this->server->on('shutdown', [$this, 'onShutdown']);
}
/**
* 启动server
*/
public function run()
{
//启动server,监听所有TCP/UDP端口
$this->server->start();
}
/**
* @param \swoole_websocket_server $server
* websocket 启动事件
*/
abstract public function onStart($server);
/**
* @param \swoole_http_request $request
* @param \swoole_http_response $response
* @return boolean 若返回`false`,则握手失败
*
* WebSocket客户端与服务器建立连接时的握手回调事件处理
*/
abstract public function onHandshake($request, $response);
/**
* @param \swoole_websocket_server $server
* @param \swoole_http_request $request
*
* WebSocket客户端与服务器建立连接并完成握手后的回调事件处理
*/
abstract public function onOpen($server, $request);
/**
* @param \swoole_websocket_server $server
* @param \swoole_websocket_frame $frame 对象,包含了客户端发来的数据信息
*
* 当服务器收到来自客户端的数据时的回调事件处理
*/
abstract public function onMessage($server, $frame);
/**
* @param \swoole_http_request $request
* @param \swoole_http_response $response
*
* 当服务器收到来自客户端的HTTP请求时的回调事件处理
*/
abstract public function onRequest($request, $response);
/**
* @param \swoole_websocket_server $server
* @integer $fd
*
* WebSocket客户端与服务器断开连接后的回调事件处理
*/
abstract public function onClose($server, $fd);
/**
* @param \swoole_http_request $request
* @param \swoole_http_response $response
* @return bool
*
* 通用的websocket握手处理
*/
public function handshake($request, $response) {
// websocket握手连接算法验证
$secWebSocketKey = $request->header['sec-websocket-key'];
$patten = '#^[+/0-9A-Za-z]{21}[AQgw]==$#';
if (0 === preg_match($patten, $secWebSocketKey) || 16 !== strlen(base64_decode($secWebSocketKey))) {
$response->end();
return false;
}
$key = base64_encode(sha1($request->header['sec-websocket-key'] . '258EAFA5-E914-47DA-95CA-C5AB0DC85B11', true));
$headers = [
'Upgrade' => 'websocket',
'Connection' => 'Upgrade',
'Sec-WebSocket-Accept' => $key,
'Sec-WebSocket-Version' => '13',
];
// WebSocket connection to 'ws://[host]:[port]/'
// failed: Error during WebSocket handshake:
// Response must not include 'Sec-WebSocket-Protocol' header if not present in request: websocket
if (isset($request->header['sec-websocket-protocol'])) {
$headers['Sec-WebSocket-Protocol'] = $request->header['sec-websocket-protocol'];
}
foreach ($headers as $key => $val) {
$response->header($key, $val);
}
$response->status(101);
$response->end();
}
/**
* @param \swoole_websocket_server $server
*
* Server正常结束时的回调事件处理
*/
abstract public function onShutdown($server);
/**
* 获取请求路由
*
* @param swoole_http_request $request
*/
protected function getRoute($request)
{
return ltrim($request->server['request_uri'], '/');
}
/**
* 获取请求的GET参数
*
* @param swoole_http_request $request
*/
protected function getParams($request)
{
return $request->get;
}
/**
* 日志信息输出函数
*/
protected function stdout($string)
{
fwrite(\STDOUT, $string . "\n");
}
/**
* Before Exec
*/
protected function startTime()
{
$this->stdout(date('Y-m-d H:i:s'));
}
}
二:创建一个websocket类继承上面的websocket基类来处理websocket相关操作
/**
* WebSocketServer 通用类
*/
class WebSocketServer extends Server
{
/**
* @param \swoole_websocket_server $server
*
* 服务启动事件
*/
public function onStart($server){
$this->startTime();
$this->stdout("**websocket 服务启动 **");
$this->stdout("主进程的PID为: " . "{$server->master_pid}" . "\n");
//websocket服务启动自定义处理
//业务代码
}
/**
* @param \swoole_http_request $request
* @param \swoole_http_response $response
* @return bool|void
*
* 握手处理事件
*/
public function onHandshake($request, $response)
{
$this->startTime();
$this->stdout("**websocket 建立握手**");
$this->stdout("客户端连接ID为: " . "{$request->fd}" . "\n");
//websocket握手自定义处理
//业务代码
//默认握手处理
$this->handshake($request, $response);
//触发客户端连接完成事件
$this->server->defer(function() use ($request) {
$this->onOpen($this->server, $request);
});
}
/**
* @param \swoole_websocket_server $server
* @param \swoole_http_request $request
* 建立连接完成
*/
public function onOpen($server, $request)
{
$this->startTime();
$this->stdout("**websocket客户端 连接完成**");
$this->stdout("客户端连接ID为: " . "{$request->fd}");
$route = $this->getRoute($request);
$this->stdout("websocket客户端 连接路由为: " . $route);
$params = $this->getParams($request);
$params = json_encode($params);
$this->stdout("websocket客户端 get传参为: " . $params . "\n");
//websocket建立连接自定义处理
//业务代码
}
/**
* @param \swoole_websocket_server $server
* @param \swoole_websocket_frame $frame
* 接受客户端消息
*/
public function onMessage($server, $frame)
{
$this->startTime();
$this->stdout("**websocket 接收客户端消息**");
$this->stdout('客户端连接ID为' . $frame->fd . '的客户端发送的消息为' . $frame->data . "\n");
//websocket接受客户端信息自定义处理
//业务代码
}
/**
* @param \swoole_http_request $request
* @param \swoole_http_response $response
* http响应
*/
public function onRequest($request, $response)
{
$this->startTime();
$this->stdout("**websocket 路由响应**");
$this->stdout("响应的客户端连接ID: " . $request->fd . "\n");
$response->status(200);
$response->end('success');
//websockethttp响应自定义处理
//业务代码
}
/**
* @param \swoole_websocket_server $server
* @param $fd
* 连接关闭
*/
public function onClose($server, $fd)
{
$this->startTime();
$this->stdout('**websocket客户端 连接关闭**');
$this->stdout('关闭的客户端连接ID:' . $fd . "\n");
//websocket客户端关闭自定义处理
//业务代码
}
/**
* @param \swoole_websocket_server $server
*
* 正常关闭连接事件
*/
public function onShutdown($server)
{
$this->startTime();
$this->stdout("**websocket服务端 关闭**");
$this->stdout("关闭主进程的PID为: " . $server->master_pid . "\n");
//websocket服务正常关闭自定义处理
//业务代码
}
/**
*
*
* 给指定客户端发送消息
*/
/**
* @param $fd 客户端连接ID
* @param \swoole_websocket_server $server
* @param $data string|array 需要发送的消息
*/
public function sendMessage($fd, $server, $data)
{
$data = is_array($data) ? json_encode($data) : $data;
//发送消息
$server->push($fd, $data);
}
}
三:启动websocket
这里我已Yii框架为例:
1:配置运行参数
在params.php文件中配置websocket运行参数
'webSocket' => [
//IP
'host' => '0.0.0.0',
//端口号
'port' => '8888',
// 通过此参数来调节主进程内事件处理线程的数量,以充分利用多核。默认会启用CPU核数相同的数量。一般设置为CPU核数的1-4倍
// 'reactor_num' => 2,
// 设置启动的Worker进程数。业务代码是全异步非阻塞的,这里设置为CPU的1-4倍最合理
// 业务代码为同步阻塞,需要根据请求响应时间和系统负载来调整
// 'worker_num' => 4,
// 设置worker进程的最大任务数,默认为0,一个worker进程在处理完超过此数值的任务后将自动退出,进程退出后会释放所有内存和资源。
'max_request' => 50,
// 服务器程序,最大允许的连接数, 此参数用来设置Server最大允许维持多少个TCP连接。超过此数量后,新进入的连接将被拒绝
// 'max_connection' => 10000,
// 数据包分发策略默认为2。1轮循模式,2固定模式,3抢占模式,4IP分配,5UID分配
'dispatch_mode' => 1,
// swoole在配置dispatch_mode=1或3后,因为系统无法保证onConnect/onReceive/onClose的顺序,默认关闭了onConnect/onClose事件。
// 如果应用程序需要onConnect/onClose事件,并且能接受顺序问题可能带来的安全风险,
// 可以通过设置enable_unsafe_event为true,启用onConnect/onClose事件
'enable_unsafe_event' => true,
// 日志文件路径
'log_file' => '@commands/log/swoole.log',
// 设置swoole_server错误日志打印的等级,范围是0-5。低于log_level设置的日志信息不会抛出
'log_level' => 1,
// 进程的PID存储文件
'pid_file' => '@commands/log/swoole.server.pid',
// 启用TCP-Keepalive死连接检测
'open_tcp_keepalive' => 1,
// 单位秒,连接在n秒内没有数据请求,将开始对此连接进行探测
'tcp_keepidle' => 5,
// 探测的次数,超过次数后将close此连接
'tcp_keepcount' => 2,
// 探测的间隔时间,单位秒
'tcp_keepinterval' => 3,
// 心跳检测,此选项表示每隔多久轮循一次,单位为秒
'heartbeat_check_interval' => 5,
// 心跳检测,连接最大允许空闲的时间
'heartbeat_idle_time' => 15,
],
2:运行websocket和停止websocket
class WebSocketController extends Controller
{
/**
* @var array Swoole参数配置项
*/
public $configs;
/**
* @var string 监听IP
*/
public $host;
/**
* @var string 监听端口号
*/
public $port;
/**
* @param \yii\base\Action $action
* @return bool
*
*执行操作前执行方法
*/
public function beforeAction($action)
{
if (parent::beforeAction($action)) {
//判断是否加载swoole拓展
if (!extension_loaded('swoole')) {
return false;
}
//判断是否有websocket配置信息
if (!Yii::$app->params['webSocket']) {
return false;
}
//获取websocket配置信息
$this->configs = Yii::$app->params['webSocket'];
//获取IP和端口号
$host = ArrayHelper::remove($this->configs, 'host');
$port = ArrayHelper::remove($this->configs, 'port');
$this->host === null && $this->host = $host;
$this->port === null && $this->port = $port;
//地址转换
foreach ($this->configs as &$param) {
if (strncmp($param, '@', 1) === 0) {
$param = Yii::getAlias($param);
}
}
return true;
}
return false;
}
/**
* 启动服务
*/
public function actionStart()
{
//判断websocket是否已启动
if ($this->getPid() !== false) {
$this->stdout("WebSocket Server is already started!\n", Console::FG_RED);
return self::EXIT_CODE_NORMAL;
}
$server = new WebSocketServer($this->host, $this->port, $this->configs);
$server->run();
}
/**
* 停止服务
*/
public function actionStop()
{
$pid = $this->getPid();
if ($pid === false) {
$this->stdout("WebSocket Server is already stoped!\n", Console::FG_RED);
return self::EXIT_CODE_NORMAL;
}
\swoole_process::kill($pid);
}
/**
* 获取进程PID
*
* @return false|integer PID
*/
private function getPid()
{
$pidFile = $this->configs['pid_file'];
if (!file_exists($pidFile)) {
return false;
}
$pid = file_get_contents($pidFile);
if (empty($pid)) {
return false;
}
$pid = intval($pid);
if (\swoole_process::kill($pid, 0)) {
return $pid;
} else {
FileHelper::unlink($pidFile);
return false;
}
}
}
如上在框架中我们就可以实现websocket处理,根据如上我创建了一个composer包来方便websocket处理