前面咱們已經簡單的瞭解了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