资讯专栏INFORMATION COLUMN

通过Swoole Framework学习websocket协议

wenyiweb / 786人阅读

摘要:服务端的开发离不开协议,的出现对于学习通信来说,无疑是非常好的教材。一旦握手成功,一个双向连接通道就建立了。类型包括文本,二进制,协议层信号等。目前一共有种类型,种保留类型。表示异或,表示取模。

  

服务端的开发离不开协议,swoole的出现对于学习通信来说,无疑是非常好的教材。非常推荐大家下载 Swoole Framework,其中包含了多种协议的php实现,例如FTP,HTTP,Websocket等。本文大部分代码都是受这个项目的启发,当然学习的同时别忘了star一下这个项目。笔者本身计算机基础较弱,写这篇文章的同时也查了不少资料,如果有错误欢迎提出批评。

websocket协议学习 概述

协议分为两部分:握手,数据传输

客户端发出的握手信息类似:

GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Origin: http://example.com
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13

服务器返回的握手信息类似:

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
Sec-WebSocket-Protocol: chat
Sec-WebSocket-Version: 13

两段信息的第一行大家应该都比较熟悉,是HTTP协议中的Request-Line和Status-Line,RFC2616。下面接着出现的是无序的头信息,这和HTTP协议相同。 一旦握手成功,一个双向连接通道就建立了。
连接用于传输message, message由一个或多个frame组成。每个frame有一个类型,属于同一个message的frame的类型都相同。类型包括:文本,二进制,control frame(协议层信号)等。目前一共有6种类型,10种保留类型。

握手

根据上面的客户端头信息可以看出,握手和HTTP是兼容的。WS的握手是HTTP的"升级版本"。

客户端发送的握手请求必须
1. 是一个合法的HTTP请求
2. 方法是GET
3. 头必须包含HOST字段
4. 头必须包含Upgrade字段,值为websocket,可以看作是判断请求为ws的标志。
5. 头必须包含Connection字段,值为Upgrade。
6. 头必须包含Sec-WebSocket-Key字段,用于验证。
7. 如果请求来自浏览器,头必须包含 Origin字段。
8. 头必须包含Sec-WebSocket-Version字段,值为13

验证握手

Sec-WebSocket-Key字段的值,连接一个GUID字符串,"258EAFA5-E914-47DA-95CA-C5AB0DC85B11", sha1 hash一下,再base64_encode,得到的值作为字段Sec-WebSocket-Accept的值返回给客户端。用php代码表示:

"Sec-WebSocket-Accept" => base64_encode(sha1($key . static::GUID, true))

同时,返回的状态设置为101,其他状态都表示握手没有成功。 Connection,Upgrade字段作为HTTP升级版必须存在。一个握手返回如下:

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
Frame(帧)的结构如下:
  0                   1                   2                   3
  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
 +-+-+-+-+-------+-+-------------+-------------------------------+
 |F|R|R|R| opcode|M| Payload len |    Extended payload length    |
 |I|S|S|S|  (4)  |A|     (7)     |             (16/64)           |
 |N|V|V|V|       |S|             |   (if payload len==126/127)   |
 | |1|2|3|       |K|             |                               |
 +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
 |     Extended payload length continued, if payload len == 127  |
 + - - - - - - - - - - - - - - - +-------------------------------+
 |                               |Masking-key, if MASK set to 1  |
 +-------------------------------+-------------------------------+
 | Masking-key (continued)       |          Payload Data         |
 +-------------------------------- - - - - - - - - - - - - - - - +
 :                     Payload Data continued ...                :
 + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
 |                     Payload Data continued ...                |
 +---------------------------------------------------------------+

FIN: 1 bit, 标记是否是最后一个message的最后一个片段

RSV1, RSV2, RSV3: 各 1 bit, 保留标记,都为0

Opcode:4 bits, 是对payload data的说明,指明这个帧的类型。

0x0 表明为接着上一帧的连续帧

0x1 表明为text frame

0x2 表明为binary frame

0x3-7 保留

0x8 表示连接关闭

0x9 为ping

0xA 为pong

0xB-F 保留

- Mask: 1 bit, 指明Payload data是否被mask,如果为1,那么数据需要根据masking-key来unmask。客户端发送的帧都是mask的。
- Payload length: 7 bits 或 7+16 bits 或 7+64 bits. 如果值为0-125,那么该值就是payload的长度;如果为126,那么接下来的2个byte表示payload长度(16bit, unsigned); 如果为127,那么接下来的8个bytes表示payload的长度(64bit, unsigned)。
- Masking-key: 0 或 4 bytes, 用于unmask payload data。
- Payload data: 长度为 Payload length, 可以分为 extension data + application data, 扩展数据的长度计算方法是是事先商议好的,剩余的就是应用数据。

Mask规则

masking-key是客户端随意指定的32bit长度值。从原始数据到masked数据的方式为:原始数据第i个字节的值 XOR masking-key的第(i%4)个字节的值。XOR表示异或,%表示取模。

片段化的作用

当传递一个未知长度的数据时,可以不用一下子buffer全部的数据。尤其当数据非常大时,可以分多次buffer,包装为frame来发送。

尝试解析一个frame

看到这里,我们已经了解了frame的结构,是否想尝试解析一个frame,官方文档提供了几段二进制数据,我们可以用来练习一下。我挑选了其中两段, 代码如下:

php> 7) & 0x1;
    $RSV1 = ($temp >> 6) & 0x1;
    $RSV2 = ($temp >> 5) & 0x1;
    $RSV3 = ($temp >> 4) & 0x1;
    $opcode = $temp & 0xf;

    echo "First byte: FIN is $FIN, RSV1-3 are $RSV1, $RSV2, $RSV3; Opcode is $opcode 
";

    $temp = ord($data[$offset++]);
    $mask = ($temp >> 7) & 0x1;
    $payload_length = $temp & 0x7f;
    if($payload_length == 126){
        $temp = substr($data, $offset, 2);
        $offset += 2;
        $temp = unpack("nl", $temp);
        $payload_length = $temp["l"];
    }elseif($payload_length == 127){
        $temp = substr($data, $offset, 8);
        $offset += 8;
        $temp = unpack("nl", $temp);
        $payload_length = $temp["l"];
    }
    echo "mask is $mask, payload_length is $payload_length 
";

    if($mask ==0){
        $temp = substr($data, $offset);
        $content = "";
        for ($i=0; $i < $payload_length; $i++) { 
            $content .= $temp[$i];
        }
    }else{
        $masking_key = substr($data, $offset, 4);
        $offset += 4;

        $temp = substr($data, $offset);
        $content = "";
        for ($i=0; $i < $payload_length; $i++) { 
            $content .= chr(ord($temp[$i]) ^ ord($masking_key[$i%4]));
        }
    }

    echo "content is $content 
";
}

结果输出如下图:

  

到这里其实并不算完,ws协议还有很多很多规则,RFC文档实在是太长了。比如,如何应对每一种control frame,有详细的说明;如何关闭连接;协议扩展;错误处理;安全相关;一些基本的内容都能在swoole framework中找到对应的代码。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/21069.html

相关文章

  • swoolefy-基于swoole扩展实现的高性能的常驻内存型API和Web应用服务框架

    摘要:是一个基于扩展实现的轻量级高性能的常驻内存型的和应用服务框架高度封装了,,服务器,以及基于实现可扩展的服务,同时支持包方式安装部署项目。基于实用,抽象事件处理类,实现与底层的回调的解耦,支持同步异步调用,内置等常用组件等。 swoolefy swoolefy是一个基于swoole扩展实现的轻量级高性能的常驻内存型的API和Web应用服务框架,高度封装了http,websocket,ud...

    lewinlee 评论0 收藏0
  • php只能做网站?基于swoole+websocket开发双向通信应用

    摘要:那么,是否就无法用来开发双向通信的应用呢答案是否定的。内置通信支持,可以与程序基于进行双向通信。通信协议于年被定为标准,并由补充规范。前言 众所周知,PHP用于开发基于HTTP协议的网站应用非常便捷。而HTTP协议是一种单向的通信协议,只能接收客户端的请求,然后响应请求,不能主动向客户端推送信息。因此,一些实时性要求比较高的应用,如实时聊天、直播应用、在线网页游戏等,就不适合采用HTTP协议...

    番茄西红柿 评论0 收藏0
  • php只能做网站?基于swoole+websocket开发双向通信应用

    摘要:那么,是否就无法用来开发双向通信的应用呢答案是否定的。内置通信支持,可以与程序基于进行双向通信。通信协议于年被定为标准,并由补充规范。前言 众所周知,PHP用于开发基于HTTP协议的网站应用非常便捷。而HTTP协议是一种单向的通信协议,只能接收客户端的请求,然后响应请求,不能主动向客户端推送信息。因此,一些实时性要求比较高的应用,如实时聊天、直播应用、在线网页游戏等,就不适合采用HTTP协议...

    琛h。 评论0 收藏0
  • swoft| 源码解读系列一: 好难! swoft demo 都跑不起来怎么破? docker 了解

    摘要:源码解读系列一好难都跑不起来怎么破了解一下呗阅读框架源码第一步搞定环境小伙伴刚接触的时候会感觉压力有点大更直观的说法是难开发组是不赞成难这个说法的的代码都是实现的而又是世界上最好的语言的代码阅读起来是很轻松的开发组会用源码解读系列博客深 date: 2018-8-01 14:22:17title: swoft| 源码解读系列一: 好难! swoft demo 都跑不起来怎么破? doc...

    shenhualong 评论0 收藏0
  • swoft| 源码解读系列一: 好难! swoft demo 都跑不起来怎么破? docker 了解

    摘要:源码解读系列一好难都跑不起来怎么破了解一下呗阅读框架源码第一步搞定环境小伙伴刚接触的时候会感觉压力有点大更直观的说法是难开发组是不赞成难这个说法的的代码都是实现的而又是世界上最好的语言的代码阅读起来是很轻松的开发组会用源码解读系列博客深 date: 2018-8-01 14:22:17title: swoft| 源码解读系列一: 好难! swoft demo 都跑不起来怎么破? doc...

    rollback 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<