webSocket 在PHP框架中方法整合

前面我们已经简单的了解了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/package...

你可能感兴趣的:(websocket,swoole,php)