前端时间看了看t-io的websocket部分源码,于是抽时间看了看websocket的握手和他的通讯机制。本篇只是简单记录一下websocket握手部分。
好多人都用过websocket,不过有的都是在框架之上,只知道连接某个地址,然后调用js API就可以使用websocket了。但是通过阅读t-io的源码才稍微有点明白,服务端到底做了什么。将t-io的websocket demo运行起来之后,我们看一下请求。
可以看到,请求头部分:
Connection:Upgrade 固定
Upgrade:websocket 固定
Host:为websocket请求地址
Sec-WebSocket-Version:13,websocket协议版本号
Sec-WebSocket-Key:发送给服务端需要校验的key,是一个Base64 encode的值,这个是浏览器随机生成的。那么服务端如果响应的话,需要做如下操作:将 Key 追加固定字符串 :“258EAFA5-E914-47DA-95CA-C5AB0DC85B11”,然后进行SHA-1加密,在转化为base64.
服务端响应如下:
Status Code:101 Switching Protocols
sec-websocket-accept:为上文中转化为base64的串。
upgrade:升级为websocket协议
握手成功,可以进行通讯。
- public static HttpResponse updateWebSocketProtocol(HttpRequest request, ChannelContext channelContext) {
- //首先获取请求头部信息
- Map<String, String> headers = request.getHeaders();
- //获取Sec-WebSocket-Key
- String Sec_WebSocket_Key = headers.get(HttpConst.RequestHeaderKey.Sec_WebSocket_Key);
- //如果key是空的话,肯定不会握手成功
- if (StringUtils.isNotBlank(Sec_WebSocket_Key)) {
- //追加固定串
- String Sec_WebSocket_Key_Magic = Sec_WebSocket_Key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
- //SHA-1加密
- byte[] key_array = SHA1Util.SHA1(Sec_WebSocket_Key_Magic);
- //转化为base64
- String acceptKey = BASE64Util.byteArrayToBase64(key_array);
- //构造响应体
- HttpResponse httpResponse = new HttpResponse(request, null);
- //响应状态码 101 Switching Protocols
- httpResponse.setStatus(HttpResponseStatus.C101);
- Map<String, String> respHeaders = new HashMap<>();
- //Connection:upgrade
- respHeaders.put(HttpConst.ResponseHeaderKey.Connection, HttpConst.ResponseHeaderValue.Connection.Upgrade);
- //Upgrade:websocket
- respHeaders.put(HttpConst.ResponseHeaderKey.Upgrade, "WebSocket");
- //Sec-WebSocket-Accept:生成的base64串
- respHeaders.put(HttpConst.ResponseHeaderKey.Sec_WebSocket_Accept, acceptKey);
- //设置响应头
- httpResponse.setHeaders(respHeaders);
- //返回响应信息 握手成功
- return httpResponse;
- }
- return null;
- }
注:博客部分内容来源于:https://github.com/zhangkaitao/websocket-protocol/wiki/5.数据帧 有兴趣的同学可以直接读本链接内容。
相信很多人从其他博客中也看过这个图,当然啦,这个图是官方出品的权威数据帧格式图。
其实我第一眼看的时候确实看不懂,不过没关系,一点一点的看。
FIN:1bit,指示这个消息是否为最后片段,1是,0否。如果不是最后片段,则服务端需要将所有消息接受完并组装成一个完整的消息才可以。(t-io中目前只支持FIN=1)
RSV123每个长度为1bit,目前就都是固定 0。
opcode:4bit,数据操作类型。
MASK:1bit,是否掩码,1掩码,0非掩码。从客户端发送到服务端的这个值必须为1,否则服务端不接受。服务端返回到客户端的这个值必须为 0.
Payload len:负载数据的长度,7bit。由于7bit只能存储0-127,所以为了能够表示准确的长度,在这个值为0-125区间的时候,payload length的长度就是该值。当 值为126的时候,后边两个字节(16位)的值表示长度。当值为127的时候,后边8字节(64位)的值表示长度。
Mask key:掩码,0或4个bit。值取决于MASK是否为1.在有掩码的情况下,数据就要根据掩码来解析。否则不用解析。解析规则为:每个字节的值与掩码的索引(字节索引值对4取模)异或运算。(array[i] = array[i] ^ mask[i % 4])
其实说实话我也没弄得非常懂,但是基本了解了以上这些知识之后,我们就可以读懂源码的意思了。
来源: http://www.cnblogs.com/panzi/p/7823118.html