您现在的位置是:网站首页>>PHP>>webSocket

webSocket 在PHP框架中方法整合

发布时间:2020-01-09 18:23:44作者:wangjian浏览量:261点赞量:0

    前面我们已经简单的了解了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处理

    https://packagist.org/packages/huaweichenai/web-socket

0 +1