一、 概述
HTML5中的WebSocket
http://tools.ietf.org/html/rfc6455
WebSocket API 是 HTML5 标准的一部分 , 但这并不代表 WebSocket 一定要用在HTML中,或者只能在基于浏览器的应用程序中使用。
WebSocket API最伟大之处在于服务器和客户端可以在给定的时间范围内的任意时刻,相互推送信息。WebSocket并不限于以Ajax(或XHR)方式通信,因为Ajax技术需要客户端发起请求,而WebSocket服务器和客户端可以彼此相互推送信息;
XHR受到域的限制,而WebSocket允许跨域通信。
WebSocket目前由W3C进行标准化。WebSocket已经受到 Firefox 4、Chrome 4、Opera 10.70以及Safari 5等浏览器的支持。
WebSocket的语法非常简单,使用WebSockets是难以置信的容易,除非客户端不支持WebSocket。IE浏览器目前不支持WebSocket通信。
Nginx 从 1.3 开始支持 WebSocket , 详见官方文档: https://www.nginx.com/press/nginx-websockets/
url: ws://yourdomain:port/path
ws是普通的WebSocket通信协议,而wss是安全的WebSocket通信协议(就像HTTP与HTTPS之间的差异一样)。
在缺省情况下,ws的端口是80而wss的端口是443。
-----------------------------------------
二、协议说明:
WebSocket协议主要分为两部分,第一部分是连接许可验证和验证后的数据交互。
连接许可验证比较简单,由Client发送一个类似于HTTP的请求,服务端获取请求后根据请求的KEY生成对应的值并返回。
Websocket是应用层第七层上的一个应用层协议,它必须依赖 HTTP 协议进行一次握手,握手成功后,数据就直接从 TCP 通道传输,与 HTTP 无关了。
(1)WebSocket 客户端连接报文:
GET http://192.168.5.238:845/ HTTP/1.1
Host: 192.168.5.238:845
Connection: Upgrade
Pragma: no-cache
Cache-Control: no-cache
Upgrade: websocket
Origin: null
Sec-WebSocket-Version: 13
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.101 Safari/537.36
Accept-Encoding: gzip, deflate, sdch
Accept-Language: zh-CN,zh;q=0.8
Sec-WebSocket-Key: EmR05JYWVPf7Tw6FYxeGiA==
Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits
请求方法必须是GET、且HTTP版本必须是至少1.1。
Sec-WebSocket-Key头字段:
客户端发起的 WebSocket 连接报文类似传统 HTTP 报文
"Upgrade:websocket"参数值表明这是 WebSocket 类型请求
"Sec-WebSocket-Key"是 WebSocket 客户端发送的一个 base64 编码的密文,要求 服务端必须返回 一个对应加密的"Sec-WebSocket-Accept"应答,否则客户端会抛出"Error during WebSocket handshake"错误,并关闭连接。
Sec-WebSocket-Version头字段:
该头字段的值必须是13。
注意:尽管本文档的草案版本(-09、-10、-11、和-12)发布了,值9、10、11、和12不被用作有效的Sec-WebSocket-Version。这些值被保留在IANA注册中心,但并将不会被使用。
Sec-WebSocket-Extensions头字段:
像其他HTTP头字段,Sec-WebSocket-Extensions头字段可以跨多个行分割或组合,以下是等价的:
Sec-WebSocket-Extensions: foo
Sec-WebSocket-Extensions: bar; baz=2
完全等价于
Sec-WebSocket-Extensions: foo, bar; baz=2
注意,扩展的顺序是重要的。在多个扩展间的相互作用可以定义在定义扩展的文档中。
客户端请求报文, Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits ,有压缩和window_bit选项说明。
"permessage-deflate" Extension
This section defines a specific PMCE called "permessage-deflate". It compresses the payload of a message using the DEFLATE algorithm[RFC1951] and uses the byte boundary alignment method introduced in[RFC1979].
(2)WebSocket 服务端响应报文:
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: K7DJLdLooIwIG/MOpvWFB3y3FE8=
Server 端返回的 HTTP 状态码是101,如果不是101 ,那就说明握手一开始就失败了。
Sec-WebSocket-Accept头字段:
"Sec-WebSocket-Accept"是服务端采用 与客户端一致的 密钥计算出来后返回客户端的
"HTTP/1.1 101 Switching Protocols"表示服务端接受 WebSocket 协议的客户端连接,经过这样的请求-响应处理后,客户端服务端的 WebSocket 连接握手成功, 后续就可以进行 TCP 通讯了。读者可以查阅WebSocket 协议栈了解 WebSocket 客户端和服务端更详细的交互数据格式。
websocket客户端连接请求报文包含Sec-WebSocket-Key:dGhlIHNhbXBsZSBub25jZQ==
服务器接受处理后将Sec-WebSocket-Key设置的字符串和固定UUID(258EAFA5-E914-47DA-95CA-C5AB0DC85B11)连接起来,再经过SHA-1散列(160位)、base-64编码设置到服务器的响应报文Sec-WebSocket-Accept中返回给客户端。
举例:|Sec-WebSocket-Key|头字段的值为"dGhlIHNhbXBsZSBub25jZQ==",服务器将连接字符串"258EAFA5-E914-47DA-95CA-C5AB0DC85B11"形成字符串"dGhlIHNhbXBsZSBub25jZQ==258EAFA5-E914-47DA-95CA-C5AB0DC85B11"。
服务器接着使用 SHA-1 散列这个连接字符串,并产生值0xb3 0x7a 0x4f 0x2c 0xc0 0x62 0x4f 0x16 0x90 0xf6 0x46 0x06 0xcf 0x38 0x59 0x45 0xb2 0xbe 0xc4 0xea。
使用这个sha1产生的20字节使用base64编码产生值"s3pPLMBiTxaQ9kYGzzhZRbK+xOo="。
这个值将接着在|Sec-WebSocket-Accept|头字段中回应。
(3)数据交互协议:
Websocket的数据传输是 frame 形式传输的,比如会将一条消息分为几个frame,按照先后顺序传输出去。
数据报文头固定 2字节 。
客户端发给服务器 mask 必须设置为1,数据不需要gzip压缩。
服务器发送给客户端 mask 必须设置为0,数据需要gzip压缩。
fin:设置该消息是否是最后片段。设置为1表示是最后的片段。
rsv1、rsv2、rsv3:各占一个位,预留位,一般都是0
opcode:操作码,占四个位。
%x0 代表一个继续帧
%x1 代表一个文本帧,编码为 UTF-8 的文本数据。
%x2 代表一个二进制帧
%x3-7 保留用于未来的非控制帧
%x8 代表连接关闭
%x9 代表ping
%xA 代表pong
%xB-F 保留用于未来的控制帧
masked:占一个位,表示是否对数据进行掩码处理,客户端发送给服务端时为1,服务端发送给客户端时为0。
rfc6455规定从客户端发往服务器端的数据帧必需使用掩码,反过来,从服务器发回来的,则必需不使用掩码。
payload len:占7位,或者7+16位、或者7+64位(对照协议图)。
payload len <= 125: 1111101, 数据长度使用 7bits 表示
payload len == 126: 1111110, 数据长度使用 16 bits 表示
payload len == 127: 1111111, 数据长度使用 64 bits 表示
"负载数据"的长度,以字节为单位:
如果0-125,这是负载长度。
如果126,之后的两字节解释为一个16位的无符号整数是负载长度。
如果127,之后的8字节解释为一个64位的无符号整数(最高有效位必须是0)是负载长度。
多字节长度数量以 网络字节顺序 来表示。
masking key(0 ~ 4 bytes):当masked为1的时候才存在( 该字段不一定存在 ),用于对我们需要的数据进行解密。
掩码键是由客户端随机选择的32位值。如果mask位设置为1,则该字段存在,如果mask位设置为0,则该字段缺失。掩码不影响"负载数据"的长度。
变换数据的八位位组i("transformed-octet-i")是原始数据的八位位组i("original-octet-i")异或(XOR)i取模4位置的掩码键的八位位组("masking-key-octet-j"),算法如下:
j = i MOD 4
transformed-octet-i = original-octet-i XOR masking-key-octet-j
payload data:我们需要的数据,如果masked为1,该数据会被加密,要通过masking key进行异或运算解密才能获取到真实数据。
Accept-Encoding: gzip, deflate, sdch
(4)控制帧
当前定义的用于控制帧的操作码包括0x8 (Close)、0x9(Ping)、和0xA(Pong)。操作码0xB-0xF保留用于未来尚未定义的控制帧。
控制帧用于传达有关WebSocket的状态。控制帧可以插入到分片消息的中间。
所有控制帧必须有一个125字节的负载长度或更少, 必须不被分段。
websockets还存在的问题
websockets虽然是实现了长连接,但是如果客户端与服务端长时间没有发送数据,那么网络链路就会认为这个连接已经失效,会自作主张的将其断开。而解决方案,WebSocket 的设计者们也早已想过。就是让服务器和客户端能够发送 Ping/Pong Frame(RFC 6455 - TheWebSocket Protocol)。这种 Frame 是一种特殊的数据包,它只包含一些元数据而不需要真正的 Data Payload,可以在不影响 Application 的情况下维持住中间网络的连接状态。
-----------------------------------------
数据报例子解析:
发送 "111"
rcv success from app:-content(9 byte):
81 83 d9 56 04 52 e8 67 35 --- ...V.R.g5
1000 0001 1000 0003
fin:1
rsv1:0
rsv2:0
rsv3:0
opcode:1(txt)
mask:1
payload len:3
--------------------------
rcv success from app:-content(206 byte):
81 fe 00 c6 9f ee 80 7d ae df b1 4c ae df b1 4c --- ...... }...L...L
ae df b1 4c ae df b1 4c ae df b1 4c ae df b1 4c --- ...L...L...L...L
ae df b1 4c ae df b1 4c ae df b1 4c ae df b1 4c --- ...L...L...L...L
ae df b1 4c ae df b1 4c ae df b1 4c ae df b1 4c --- ...L...L...L...L
ae df b1 4c ae df b1 4c ae df b1 4c ae df b1 4c --- ...L...L...L...L
ae df b1 4c ae df b1 4c ae df b1 4c ae df b1 4c --- ...L...L...L...L
ae df b1 4c ae df b1 4c ae df b1 4c ae df b1 4c --- ...L...L...L...L
ae df b1 4c ae df b1 4c ae df b1 4c ae df b1 4c --- ...L...L...L...L
ae df b1 4c ae df b1 4c ae df b1 4c ae df b1 4c --- ...L...L...L...L
ae df b1 4c ae df b1 4c ae df b1 4c ae df b1 4c --- ...L...L...L...L
ae df b1 4c ae df b1 4c ae df b1 4c ae df b1 4c --- ...L...L...L...L
ae df b1 4c ae df b1 4c ae df b1 4c ae df b1 4c --- ...L...L...L...L
ae df b1 4c ae df b1 4c ae df b1 4c ae df --- ...L...L...L..
1000 0001 1111 1110 0000 0000 0110
fin:1
rsv1:0
rsv2:0
rsv3:0
opcode:1(txt)
mask:1
payload len:126
extended payload len: 198(c6)
--------------------------
三、浏览器支持情况
-----------------------------------------
四、开源项目
(1)libwebsocket
C语言实现,包括websocket客户端和服务器。
服务器可以基于底层线程池也可以基于libuv和libev
(2)uWebSockets
https://github.com/uWebSockets/uWebSockets
gcc >= 4.8.0
libuv 1.3+
OpenSSL 1.0.x
zlib 1.x
CMake 3.x
cmake -DCMAKE_INSTALL_PREFIX=/home/xucuiping/3rd_party/sdk/uWebSockets/ ./
实现一个websocket server非常的简单。接口也比较简练。
(3)nopoll
noPoll is a OpenSource WebSocket implementation (RFC 6455), written in ansi C, that allows building pure WebSocket solutions or to provide WebSocket support to existing TCP oriented applications.
noPoll provides support for WebSocket (ws://) and TLS (secure) WebSocket (wss://), allowing message based (handler notified) programming or stream oriented access.
(4)Wslay
Wslay 是一个用 C 语言实现的 WebSocket 开发库。实现了 RFC 6455 中描述的第 13 版本的协议。提供了基于事件的 API 和基于帧的底层 API。
特别适合非堵塞的 reactor 模式风格应用。可在不同的事件中设置回调。Wslay 只支持 WebSocket 协议的数据传输部分,不执行 HTTP 的握手过程。
https://github.com/tatsuhiro-t/wslay