什么是WebSokcet?
WebSocket是一种协议,而且是各大主流浏览器做为客户端支持的协议。它的目标就是用来替代基于 XMLHTTPRequest和长轮询的解决方案。应用在时时弹幕,消息推送,棋牌游戏等须要及时通信的业务场景。html
握手
WebSocket链接有两个阶段:握手(handshake)和数据传输(data transfer)。此握手非TCP三次握手,可是目的差很少,就是客户端告诉浏览器我想要使用WebSocket协议进行通信。客户端须要发送以下请求,它是一个 HTTP Upgrade
请求:web
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
客户端发送握手请求服务器
- Uri要知足以下格式:
ws-URI = "ws:" "//" host [ ":" port ] path [ "?" query ]
wss-URI = "wss:" "//" host [ ":" port ] path [ "?" query ]
- 在与服务端创建链接时,客户端必须方发送握手请求,请求是一个HTTP的升级协议(Upgrade)请求。而且该请求必须知足
- 握手请求必须是一个正常的HTTP请求。
- 请求方法必须为GET,而且HTTP协议最低为1.1
- 请求头必须包含Host
- 请求头必须包含Upgrade,而且值为websocket
- 请求头必须包含Connection,而且值为Upgrade
- 请求头必须包含Sec-WebSocket-Key,值为通过Base64转换的长度为16字节的一组数据
- 请求头必须包含Origin,若是客户端是浏览器这个值确定是有的,若是非浏览器的客户端,这个值能够随意改。
- 请求头必须包含Sec-WebSocket-Version,而且值为13
- 请求头能够带一个Sec-WebSocket-Protocol,这个值告诉服务端客户端想用的子协议,多个用逗号分开
- 请求头能够带一个Sec-WebSocket-Extensions,这个值告诉服务端客户端支持的协议级别的扩展。
- 请求头能够带一个和权限校验相关的头,例如Cookie,Authentication等
当客户端将握手请求发出去以后,就要等待服务端的响应了。当服务端成功响应以后,客户端还须要作以下校验:websocket
- 返回的响应码非101,例如401,500,403,503 等等,客户端链接失败
- 返回的响应头部不包含Upgrade或者Upgrade的值不是websocket,客户端链接失败
- 返回的响应头部不包含Connection或者Connection的值不是Upgrade,客户端链接失败
- 返回的响应头部不包含Sec-WebSocket-Accept或者Sec-WebSocket-Accept的值并非
Base64(SHA1(Sec-WebSocket-Key+"258EAFA5-E914-47DA-95CA-C5AB0DC85B11"))
,客户端链接失败
- 返回的响应头部Sec-WebSocket-Extensions中的值并非客户端发送的Sec-WebSocket-Extensions中的值,客户端链接失败
- 返回的响应头部Sec-WebSocket-Protocol中的值并非客户端发送的Sec-WebSocket-Protocol中的值,客户端链接失败
服务端接收握手请求
若是服务端在处理请求过程当中不知足一下任何一点,服务端都会终止处理该请求网络
- 必须是HTTP1.1+的GET请求
- 包含Host请求头
- 包含Upgrade值为WebSocket的请求头
- 包含Connection值为Upgrade的请求头
- 包含Sec-WebSocket-Key值为16字节长度的Base64字符串
- 包含Sec-WebSocket-Version值为13的请求头
- 非必须:Origin
- 非必须:Sec-WebSocket-Protocol
- 非必须:Sec-WebSocket-Extensions
当服务端肯定这是一个正常的握手请求而且愿意处理此请求,那么服务端须要回应一个HTTP响应:框架
- 状态码必须为 101 Switching Protocol
- Upgrade:WebSocket
- Connection:Upgrade
- Sec-WebSocket-Accept,如上文所说,值为:
Base64(SHA1(Sec-WebSocket-Key+"258EAFA5-E914-47DA-95CA-C5AB0DC85B11"))
- Sec-WebSocket-Protocol,根据客户端传的值
- Sec-WebSocket-Extensions,根据客户端传的值
至此,握手结束。链接状态由CONNECTING
进入OPEN
状态socket
协议帧
WebSocket的协议帧格式以下:
3d
- FIN 1bit
包结束标志,1 表明最后一个消息包,0表明某一段消息包
- RSV1, RSV2, RSV3: 每一个1bit,共3bit
值为0,除非协议扩展(Extensions)声明了非0的值的含义。若是服务端收到非0的值,而且没有相应的定义,那么服务端将直接终止链接。
- Opcode 4bit
x0 后续帧
x1 文本帧
x2 二进制帧
x3-X7 非控制帧预留
x8 关闭链接
x9 PING
xA PONG
xB-xF 控制帧预留
- Mask 1 bit 是否掩码。客户端向服务器发送,必须掩码。服务端向客户端发送不需掩码
- PayLoad Length 7bits,7+16bits,7+64bits,若是值为 0-125,则数据包长度为0-125.若是值为126,则后2个字节为数据包长度:16bit。若是值为127,则后8个字节为数据包长度:64bit。
- Masking-Key, 0-4bits.是否有值取决于 Mask 标识位是否为1.
- Extension data X bytes 若是在握手时协商了扩展,会有值,不然为0
- Application data y bytes 剩余消息包
- PayLoad data (x+y)bytes 总消息包=Extension data + Application data.若是有掩码,解码公式以下:
body[i] = body[i] ^ body[i % 4]
代码解析
下面我用tio网络通信框架
代码来解释一下上文中的内容,没必要纠结具体代码,只要大概理解代码功能便可。

具体协议升级代码以下:

以上就是握手部分Http协议升级过程的代码部分。没有什么难理解的地方,只要对着文档要求去实现便可。不过要注意的是,这里是升级协议的过程,若是有其余业务处理,好比访问权限校验失败等,能够直接返回 HttpStatusCode 401.code
协议帧解析:


总结
大体过了一遍RFC-6455文档,发现仍是官方文档中解释的更详细的也更清楚一些,可是苦于英语水平不过关,有些部分理解起来比较困难。
参考资料
RFC-6455