目录
一、WebSocket 原理
1、握手handshaking)
2、数据通信
3、通讯示例
二、WebSocket 通信需要注意点说明
1、关于帧段(碎片)规则说明
2、通信示例
待续...
关于控件的源码下载见《Delphi 的Websocket Server 控件实现(四、WebSocket Demo程序使用说明)》
WebSocket 标准使用 UTF8编码通信,切记!
关于WebSocket 的原理,主要是RFC 6455标准,该标准的原文参见官方:RFC 6455(2011.11),如果大家不好下载,可以直接下载这个链接:WebSoket_协议(rfc6455).pdf(71页)
WebSocket 是 HTML5 开始提供的一种在单个 TCP 连接上进行全双工通讯的协议。关于WebSocket浏览器端javascript的实现使用请参见《菜鸟教程》的HTML5 WebSocket,说的非常清楚。
WebSocket 是独立的、创建在 TCP 上的协议。
Websocket 通过HTTP/1.1 协议的101状态码进行握手。
为了创建Websocket连接,需要通过浏览器发出请求,之后服务器进行回应,这个过程通常称为“握手”(handshaking)。
握手完成之后,客户端和服务器就建立了持久连接,可以进行通信,除非任何一端主动断开。在通信过程中,客户端发给服务器端的数据是需要掩码计算的,而服务器端发给客户端的不需要。
客户端主动发起,客户端会给服务器端发送类似如下文本数据:
GET / HTTP/1.1
Upgrade: websocket
Connection: Upgrade
Host: 127.0.0.1:3002
Origin: http://127.0.0.1:8020
Pragma: no-cache
Cache-Control: no-cache
Sec-WebSocket-Key: Zi5YEXnU4yRo6R4hg1Wsdw==
Sec-WebSocket-Version: 13
Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits, x-webkit-deflate-frame
User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.138 Safari/537.36
注意客户端发送的是 GET / HTTP/1.1 打头命令,必须告诉服务器端Upgrade为websocket,Connection为Upgrade,这样服务器端就知道客户端需要进行websocket通信,此时服务器端需要取得Sec-WebSocket-Key中的钥匙串Zi5YEXnU4yRo6R4hg1Wsdw==,通过和“258EAFA5-E914-47DA-95CA-C5AB0DC85B11”连接再SHA-1签名后Base64编码返回给客户端,客户端验证通过就完成了握手流程。
Zi5YEXnU4yRo6R4hg1Wsdw==258EAFA5-E914-47DA-95CA-C5AB0DC85B11,对这个字符串进行SHA-1签名再Base64编码,注意连接两个字符串的时候中间不能有空格。结果为:hUfGucluAOxLIDaafqTtjEZFjMs=,见下面。
服务器返回给客户端的数据如下:
HTTP/1.1 101 Switching Protocols
Connection: Upgrade
Upgrade: websocket
Sec-WebSocket-Accept: hUfGucluAOxLIDaafqTtjEZFjMs=
服务器返回101结果,同时继续返回Connection和Upgrade字段,并且返回经过签名的结果:hUfGucluAOxLIDaafqTtjEZFjMs=
在数据通信过程中,使用的是帧概念,帧数据有结构规范。一个完整的数据帧,可以通过几个小帧段组成,基本的帧结构如下:
FIN:1bit 表示是不是最后一个帧段,1表示是,否则表示不是。
RSV1、RSV2、RSV3:1 bit each 暂时不需要关注,默认是0,都是一位。
Opcode: 4 bits 定义实际帧数据的类型:
0 : 表示是一个连续的Frame
1 : 表示是一条文本消息
2 : 表示是流消息
3-7 : 保留
8 : 表示一个连接关闭
9 : 一个ping
A : 一个pong
B-F : 保留
Mask:1 bit 表示数据是否需要进行反掩码接开,1表示需要,0表示不需要
Payload length:7 bits, 7+16 bits, or 7+64 bits 这个理解上需要转换下。如果此值介于0和125之间,则为消息的长度。如果是126,则以下2个字节(16位无符号整数)是长度。如果是127,则以下8个字节(64位无符号整数)是长度。
Masking-key: 0 or 4 bytes 从客户端发送到服务器端的所有数据都包含这4个字节,此时上面的Mask位设置也必须为1,服务器端需要通过这4个字节对后续数据进行反掩码计算。如果Mask位为0,则不包含这4个字节,表示数据无需进行反掩码运算,服务器端到客户端的数据都是这样的。
Payload data:实际的数据字节流。
假如客户端需要给服务器端发送4个字母:SZHN,那么服务器端实际收到的数据为:(129, 132, 91, 168, 122, 85, 8, 242, 50, 27)
转换为HEX就是($81,$84,$5B,$A8,$7A,$55,$08,$F2,$32,$1B)根据协议解释如下:
129($81) = 1 0 0 0 0 0 0 1 前面的第一个1表示的是FIN,这是最后一帧数据,最后面的4bit也是1,表示这是一个文本消息。
132($84)= 1 0 0 0 0 1 0 0 前面的1表示Mask位,这说明随后会有4个字节的Masking-key,需要进行反掩码计算,后面的000100就是4,表示的数据长度为4,因为我们发送的就是SZHN这4个字符。
91, 168,122,85 表示的是Masking-key,需要用这4个字节循环对后续的数据进行xor反掩码计算。
8,242, 50, 27 表示数据,进行反掩码后得到的数据如下:
91 xor 8 = 83 (S) 168 xor 242 = 90 (Z) 122 xor 50 = 72 (H) 85 xor 27 = 78 (N),接出来的结果正好是SZHN。
对于一帧数据(Frame),可以分成几个帧段(碎片fragmentation)发送,这样的话,规则定义如下:
举例如下:对于作为三个片段发送的文本消息,第一个片段的操作码为0x1,FIN位为0;第二个片段的操作码为0x0,FIN位为0,
第三个片段有一个0x0操作码和一个FIN位为1,说明整个数据已经准备好了。
编码 | 类型 | 示例 |
---|---|---|
1 | 单一非掩码帧 | 0x81 0x05 0x48 0x65 0x6c 0x6c 0x6f (contains "Hello") |
2 | 单一掩码帧 | 0x81 0x85 0x37 0xfa 0x21 0x3d 0x7f 0x9f 0x4d 0x51 0x58 (contains "Hello") |
3 | 帧段(碎片)非掩码帧 | 0x01 0x03 0x48 0x65 0x6c (contains "Hel") 0x80 0x02 0x6c 0x6f (contains "lo") |
4 | 非掩码ping请求和掩码ping返回 | 0x89 0x05 0x48 0x65 0x6c 0x6c 0x6f (contains a body of "Hello",内容随意) 0x8a 0x85 0x37 0xfa 0x21 0x3d 0x7f 0x9f 0x4d 0x51 0x58 (contains a body of "Hello", 必须匹配请求) |
5 | 一帧中包含256个非掩码二进制数据 | 0x82 0x7E 0x0100 [256 bytes of binary data] |
6 | 一帧包含64K非掩码二进制数据 | 0x82 0x7F 0x0000000000010000 [65536 bytes of binary data] |