webSocket 在PHP框架中方法整合

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

https://packagist.org/package...swoole

相關文章
相關標籤/搜索