• Swoole从入门到入土(16)——WebSocket服务器[事件]


    WIKI:

    问:websocket协议虽然和http协议不同,但是兼容于http协议,如何判断客户端连接使用的是http协议?

    答:通过使用 $server->connection_info($fd) 获取连接信息,返回的数组中有一项为 websocket_status,根据此状态可以判断是否为 WebSocket 客户端。

    ---------- 正文的分割线 -------------

    SwooleWebSocketServer继承自SwooleHttpServer,所以websocket server支持http server和tcp server的所有事件。另外,新增加了以下3个事件:

    · onMessage (必选)

    · onOpen 和 onHandShake (可选)

    事件详解

    onHandShake:WebSocket 建立连接后进行握手。WebSocket 服务器会自动进行 handshake 握手的过程,如果用户希望自己进行握手处理,可以设置 onHandShake 事件回调函数。

    onHandShake(SwooleHttpRequest $request, SwooleHttpResponse $response);

    · onHandShake 事件回调是可选的,需要自行处理 handshake 的时候,再设置这个回调函数。如果您不需要 “自定义” 握手过程,那么不要设置该回调,用 Swoole 默认的握手即可。
    · 设置 onHandShake 回调函数后不会再触发 onOpen 事件,需要应用代码自行处理
    · onHandShake 中必须调用 response->status() 设置状态码为 101 并调用 response->end() 响应,否则会握手失败.
    · 内置的握手协议为 Sec-WebSocket-Version: 13,低版本浏览器需要自行实现握手
    · 可以使用 server->defer 调用 onOpen 逻辑

    示例:

    $server->on('handshake', function (SwooleHttpRequest $request, SwooleHttpResponse $response) {
            // print_r( $request->header );
            // if (如果不满足我某些自定义的需求条件,那么返回end输出,返回false,握手失败) {
            //    $response->end();
            //     return false;
            // }
    
            // 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;
            }
            echo $request->header['sec-websocket-key'];
            $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://127.0.0.1:9502/'
            // 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();
    });

    onOpen:当 WebSocket 客户端与服务器建立连接并完成握手后会回调此函数。

    onOpen(SwooleWebSocketServer $server, SwooleHttpRequest $request);

    · $request 是一个 HTTP 请求对象,包含了客户端发来的握手请求信息
    · onOpen 事件函数中可以调用 push 向客户端发送数据或者调用 close 关闭连接
    · onOpen 事件回调是可选的

    onMessage:当服务器收到来自客户端的数据帧时会回调此函数。

    onMessage(SwooleWebSocketServer $server, SwooleWebSocketFrame $frame)

    · $frame 是 SwooleWebSocketFrame 对象,包含了客户端发来的数据帧信息
    · onMessage 回调必须被设置,未设置服务器将无法启动
    · 客户端发送的 ping 帧不会触发 onMessage,底层会自动回复 pong 包,也可设置 open_websocket_ping_frame 参数手动处理

    关于SwooleWebSocketFrame $frame

     · $frame->data 如果是文本类型,编码格式必然是 UTF-8,这是 WebSocket 协议规定的

    示例:

    面向过程写法:

    $server = new SwooleWebSocketServer("0.0.0.0", 9501);
    $server->on('open', function (SwooleWebSocketServer $server, $request) {
        echo "server: handshake success with fd{$request->fd}
    ";
    });
    $server->on('message', function (SwooleWebSocketServer $server, $frame) {
        echo "receive from {$frame->fd}:{$frame->data},opcode:{$frame->opcode},fin:{$frame->finish}
    ";
        $server->push($frame->fd, "this is server");
    });
    $server->on('close', function ($ser, $fd) {
        echo "client {$fd} closed
    ";
    });
    $server->on('request', function (SwooleHttpRequest $request, SwooleHttpResponse $response) {
        global $server;//调用外部的server
        // $server->connections 遍历所有websocket连接用户的fd,给所有用户推送
        foreach ($server->connections as $fd) {
            // 需要先判断是否是正确的websocket连接,否则有可能会push失败
            if ($server->isEstablished($fd)) {
                $server->push($fd, $request->get['message']);
            }
        }
    });
    $server->start();

    面向对象写法:

    class WebSocketTest
    {
        public $server;
    
        public function __construct()
        {
            $this->server = new SwooleWebSocketServer("0.0.0.0", 9501);
            $this->server->on('open', function (SwooleWebSocketServer $server, $request) {
                echo "server: handshake success with fd{$request->fd}
    ";
            });
            $this->server->on('message', function (SwooleWebSocketServer $server, $frame) {
                echo "receive from {$frame->fd}:{$frame->data},opcode:{$frame->opcode},fin:{$frame->finish}
    ";
                $server->push($frame->fd, "this is server");
            });
            $this->server->on('close', function ($ser, $fd) {
                echo "client {$fd} closed
    ";
            });
            $this->server->on('request', function ($request, $response) {
                // 接收http请求从get获取message参数的值,给用户推送
                // $this->server->connections 遍历所有websocket连接用户的fd,给所有用户推送
                foreach ($this->server->connections as $fd) {
                    // 需要先判断是否是正确的websocket连接,否则有可能会push失败
                    if ($this->server->isEstablished($fd)) {
                        $this->server->push($fd, $request->get['message']);
                    }
                }
            });
            $this->server->start();
        }
    }
    
    new WebSocketTest();

    ---------------------------  我是可爱的分割线  ----------------------------

    最后博主借地宣传一下,漳州编程小组招新了,这是一个面向漳州青少年信息学/软件设计的学习小组,有意向的同学点击链接,联系我吧。

  • 相关阅读:
    【C++】C++ primer 第三章学习笔记
    【C++】C++ primer 第二章学习笔记
    【C++】C++ primer 第一章学习笔记
    【C++】C++ sort函数
    【C++】C++ primer 第五版 Sales_item.h源码
    【其他】MarkDown的使用
    Dockerfile最佳实践
    docker跨主机通信--模拟flannel host-gw直接路由
    pod 生命周期hook钩子函数
    docker mysql 修改密码
  • 原文地址:https://www.cnblogs.com/ddcoder/p/14133789.html
Copyright © 2020-2023  润新知