前面咱们已经简单的了解了websocket在服务端的一些方法和属性使用,这里我来将websocket处理整合成类方便websocket使用php
一:建立一个websocket基类(抽象类)react
/** * 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相关操做web
/** * 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框架为例:json
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处理websocket