WebSocket协议是基于TCP的一种新的网络协议。它实现了浏览器与服务器全双工(full-duplex)通信——允许服务器主动发送信息给客户端。

WebSocket通信协议于2011年被IETF定为标准RFC 6455，并被RFC7936所补充规范。

WebSocket通讯分为握手阶段、二进制通讯阶段。 一个典型的WebSocket握手请求信息:

GET /webfin/websocket/ HTTP/1.1

Host: localhost
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: xqBt3ImNzJbYqRINxEFlkg==
Origin: http://referer-url
Sec-WebSocket-Version: 13

服务器响应
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: K7DJLdLooIwIG/MOpvWFB3y3FE8=

WebSocket借用http请求进行握手，相比正常的http请求，多了一些内容。其中，
Upgrade: websocket
Connection: Upgrade
表示希望将http协议升级到Websocket协议。
Sec-WebSocket-Key是浏览器随机生成的base64 encode的值，用来询问服务器是否是支持WebSocket。

服务器返回:
Upgrade: websocket
Connection: Upgrade

告诉浏览器即将升级的是Websocket协议。
Sec-WebSocket-Accept是将请求包“Sec-WebSocket-Key”的值，与”258EAFA5-E914-47DA-95CA-C5AB0DC85B11″这个字符串进行拼接，然后对拼接后的字符串进行sha-1运算，再进行base64编码得到的。用来说明自己是WebSocket助理服务器。

Sec-WebSocket-Version是WebSocket协议版本号。RFC6455要求使用的版本是13，之前草案的版本均应当被弃用。

更多握手规范详见RFC6455。

beyod内置了WebSocket服务器支持，我们可以快速实现一个WebSocket服务器：

#### 配置文件

在配置文件config/main.php的server组件的listenners中配置：

    //...
    'listeners' => [
        /...
        'ws' => [
            'class' => 'tcp://0.0.0.0:813',
            'parser' => 'beyod\protocol\websocket\Parser',
            'handler' => 'beyod\protocol\websocket\Handler',//应该定义自己的handler并重写相关事件方法实现相关业务功能。
        ]
    ]

webosocket的握手功能，已经实现，只需要实现具体的业务事件回调即可：

    <?php
    /**
     * @link http://www.beyo.io/
     * @copyright Copyright (c) 2017 Beyo.IO Software Team.
     * @license http://www.beyo.io/license/
     */

    namespace beyod\protocol\websocket;

    use beyod\Connection;
    use beyod\MessageEvent;
    use Yii;
    use beyod\ErrorEvent;
    use beyod\protocol\http\Request as HttpRequest;
    use beyod\protocol\http\Response as HttpResponse;

    /**
     * websocket request handler class.
     * @see http://www.beyo.io/document/class/protocol-websocket
     * @author zhang xu <zhangxu@beyo.io>
     * @since 1.0
     */

    class Handler extends \beyod\Handler
    {
        //http握手阶段完成之后，就需要把客户端的Sec-Key保存，以作好状态登记。
        public $secKey = 'ws-seckKey';

        public $magic_code = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11';

        public $headers = [
            'Connection' => 'Upgrade',
            'Upgrade'   => 'WebSocket',
            'Access-Control-Allow-Credentials' => 'true',
            'Access-Control-Allow-Headers' => 'content-type'
        ];

        /**
         * 当收到的客户端数据包时的回调, 根据响应消息的类型，判断是握手或是二进制传输阶段。具体实现请参阅
         beyod\protocol\websocket\Parser::decode

         * @param MessageEvent $event
         *
         * @see Parser::decode
         */
        public function onMessage(MessageEvent $event){
            if($event->message instanceof HttpRequest){
                return $this->processHandshake($event);
            }

            if(!($event->message instanceof Request)){
                Yii::error("message for ".get_class($this).'::onMessage must be StreamRequest');
                return ;
            }

            return $this->processStream($event);
        }

        /**
        * 当收到http握手请求时，发送相应的握手响应, 这部分一般无法重写
        */
        public function processHandshake(MessageEvent $event)
        {
            /** @var \beyod\protocol\http\Request $message */
            $message = $event->message;

            $resp = new HttpResponse(101);
            foreach($this->headers as $name => $value){
                $resp->headers->set($name, $value);
            }

            $magicValue = base64_encode(sha1($event->message->headers->get('Sec-Websocket-Key').$this->magic_code,true));
            $resp->headers->set('Sec-Websocket-Accept', $magicValue);

            $event->sender->setAttribute($this->secKey, $magicValue);

            $event->sender->send($resp);

            $this->onHandshaked($event);
        }

        /**
        * http握手完成之后的回调，如通知其它客户端，或向客户端发送消息。
        */
        public function onHandshaked($event)
        {

        }

        /**
         * 当收到二进制数据包时的回调
         *
         * @param MessageEvent $event
         */

        public function processStream($event)
        {
            /**
             * @var Request $event->message
             */

            //控制指令 作相应的响应即可。
            if($event->message->isCtlFrame()) {
                if($event->message->isCloseFrame()) {
                    return $this->processClose($event);
                }else if($event->message->isPingFrame()){
                    return $this->processPing($event);
                }else if($event->message->isPongFrame()){
                    return $this->processPong($event);
                }

            }else{
                return $this->processData($event);
            }
        }

        /**
         * 关闭数据包实现
         * @param MessageEvent $event
         */
        public function processClose($event)
        {
            $response = new Response();
            $response->opcode = Request::OPCODE_CLOSE;
            return $event->sender->close($response);
        }

        /**
         * ping响应
         * @param MessageEvent $event
         */
        public function processPing($event)
        {
            $res = new Response();
            $res->fin = 1;
            $res->opcode = Request::OPCODE_PONG;
            $res->mask=0;
            $res->payload_len=0;
            $event->sender->send($res);
        }

        /**
         * process pong request
         * @param MessageEvent $event
         */
        public function processPong($event)
        {

        }

        /**
         * 处理正文数据包，一般需要重写此方法，实现业务功能。
         *
         * @tutorial  $event->message Request
         * @tutorial  $event->message->body string
         * @tutorial  $event->message->opcode int
         *
         * @param MessageEvent $event
         */
        public function processData($event)
        {

        }

        /*
        * 当发生错误时，可向客户端发送相应的响应。
        */
        public function sendErrorResponse(ErrorEvent $event)
        {
            if($event->sender->hasAttribute($this->secKey)) {

            }else{
                parent::sendErrorResponse($event);
            }
        }
    }

beyodprotocolwebsocketResponse beyodprotocolwebsocketRequest分别代表了websocket二进制数据包的请求和响应结构封装，它是由Parser的decode方法中返回的值。

具体可查阅websocket的协议说明。