Web Socket

EasySwoole的Web Socket 其实是由 swoole_websocket_server实现。若想使用WebSocket,请修改/Conf/Config.php,改变服务模式。

"SERVER_TYPE"=>\Core\Swoole\Config::SERVER_TYPE_WEB_SOCKET

开启WebSocket模式之后,需要注册onMessage事件。

相关事件注册

EasySwoole的beforeWorkerStart事件,可以对Server做一些列的补充操作。

 $server->on("message",function (\swoole_websocket_server $server, \swoole_websocket_frame $frame){
            Logger::getInstance()->console("receive data ".$frame->data);
            $json = json_decode($frame->data,1);
            if(is_array($json)){
                if($json['action'] == 'who'){
                    //可以获取bind后的uid
                    //var_dump($server->connection_info($frame->fd));
                    $server->push($frame->fd,"your fd is ".$frame->fd);
                }else{
                    $server->push($frame->fd,"this is server and you say :".$json['content']);
                }
            }else{
                $server->push($frame->fd,"command error");
            }
        }
 );

注册message事件后,客户端发送过来的全部数据包都会被该函数处理。

$server->on("handshake",function (\swoole_http_request $request, \swoole_http_response $response){
            //自定定握手规则,没有设置则用系统内置的(只支持version:13的)
            if (!isset($request->header['sec-websocket-key']))
            {
                //'Bad protocol implementation: it is not RFC6455.'
                $response->end();
                return false;
            }
            if (0 === preg_match('#^[+/0-9A-Za-z]{21}[AQgw]==$#', $request->header['sec-websocket-key'])
                || 16 !== strlen(base64_decode($request->header['sec-websocket-key']))
            )
            {
                //Header Sec-WebSocket-Key is illegal;
                $response->end();
                return false;
            }

            $key = base64_encode(sha1($request->header['sec-websocket-key']
                . '258EAFA5-E914-47DA-95CA-C5AB0DC85B11',
                true));
            $headers = array(
                'Upgrade'               => 'websocket',
                'Connection'            => 'Upgrade',
                'Sec-WebSocket-Accept'  => $key,
                'Sec-WebSocket-Version' => '13',
                'KeepAlive'             => 'off',
            );
            foreach ($headers as $key => $val)
            {
                $response->header($key, $val);
            }
            //注意 一定要有101状态码,协议规定
            $response->status(101);
            Logger::getInstance()->console('fd is '.$request->fd);
            //再做标记,保证唯一性,此操作可选
            Server::getInstance()->getServer()->bind($request->fd,time().Random::randNumStr(6));
            $response->end();
        }
);

连接验证

WS的链接建立,其实是一个特殊的HTTP请求,因此,客户端在发起链接的时候,其实是像服务端发送了一个带有特殊标记的HTTP 请求, 该请求会被handshake回调函数处理,在该回调函数中,可以根据request的cookie或者header进行判断,允许不允许该用户进行链接。

Swoole支持自定义WebSocket 握手规则。若对此握手规则有疑问的,请自行百度RFC规范,查看关于WebSocket的规定。

 $server->on("close",function ($ser,$fd){
            Logger::getInstance()->console("client {$fd} close");
        }
 );

当客户端与服务的断开链接时,均会触发此操作,注意:HTTP 协议也会触发该请求,可以通过server->connection_info()函数来判定链接类型。

HTTP对WebSocket操作

模拟简单的WebSocket客户端

在/App/Static/Template 目录下建立一个websocket_client.html

<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
</head>
<body>
<div>
    <div>
        <p>info below</p>
        <ul  id="line">

        </ul>
    </div>
    <div>
        <select id="action">
            <option value="who">who</option>
            <option value="hello">hello</option>
        </select>
        <input type="text" id="says">
        <button onclick="say()">发送</button>
    </div>
</div>
</body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.js"></script>
<script>
    var wsServer = 'ws://127.0.0.1:9501';
    var websocket = new WebSocket(wsServer);
    window.onload = function () {
        websocket.onopen = function (evt) {
            addLine("Connected to WebSocket server.");
        };

        websocket.onclose = function (evt) {
            addLine("Disconnected");
        };

        websocket.onmessage = function (evt) {
            addLine('Retrieved data from server: ' + evt.data);
        };

        websocket.onerror = function (evt, e) {
            addLine('Error occured: ' + evt.data);
        };
    };
    function addLine(data) {
        $("#line").append("<li>"+data+"</li>");
    }
    function say() {
        var content = $("#says").val();
        var action = $("#action").val();
        $("#says").val('');
        websocket.send(JSON.stringify({
            action:action,
            content:content
        }));
    }
</script>
</html>

建立对应的测试控制器

<?php
/**
 * Created by PhpStorm.
 * User: yf
 * Date: 2017/9/27
 * Time: 上午11:58
 */

namespace App\Controller;

use Core\AbstractInterface\AbstractController;
use Core\Http\Message\Status;
use Core\Component\Logger;
use Core\Swoole\AsyncTaskManager;
use Core\Swoole\Server;

class WebSocket extends AbstractController
{

    function index()
    {
        // TODO: Implement index() method.
        $this->response()->write(file_get_contents(ROOT."/App/Static/Template/websocket_client.html"));
    }
    function push(){
        /*
         * url :/webSocket/push/index.html?fd=xxxx
         */
        $fd = $this->request()->getRequestParam("fd");
        $info =  Server::getInstance()->getServer()->connection_info($fd);
        if($info['websocket_status']){
            Logger::getInstance()->console("push data to client {$fd}");
            Server::getInstance()->getServer()->push($fd,"data from server at ".time());
            $this->response()->write("push to fd :{$fd}");
        }else{
            $this->response()->write("fd {$fd} not a websocket");
        }
    }
    function connectionList(){
        /*
         * url:/webSocket/connectionList/index.html
         * 注意   本example未引入redis来做fd信息记录,因此每次采用遍历的形式来获取结果,
         * 仅供思路参考,不建议在生产环节使用
         */
        $list = array();
        foreach (Server::getInstance()->getServer()->connections as $connection){
            $info =  Server::getInstance()->getServer()->connection_info($connection);
            if($info['websocket_status']){
                $list[] = $connection;
            }
        }
        $this->response()->writeJson(200,$list,"this is all websocket list");
    }
    function broadcast(){
        /*
         * url :/webSocket/broadcast/index.html?fds=xx,xx,xx
         */
        $fds = $this->request()->getRequestParam("fds");
        $fds = explode(",",$fds);
        AsyncTaskManager::getInstance()->add(function ()use ($fds){
            foreach ( $fds as $fd) {
                Server::getInstance()->getServer()->push($fd,"this is broadcast");
            }
        });
        $this->response()->write('broadcast to all client');
    }
}

注意:客户端断线问题要处理好,否则会遇见向一个不存在链接推送数据,导致底层发出waring的问题。此问题不会导致服务出错,但对于业务逻辑与保障数据送达方面,会有影响。

results matching ""

    No results matching ""