WebSocket协议探究(一)
一 复习和目标
1 复习
- 上一节使用wireshark抓包分析了WebSocket流量
- 包含连接的建立:HTTP协议升级WebSocket协议
- 使用建立完成的WebSocket协议发送数据
2 目标
协议对比
初始握手和计算响应键值
消息格式
关闭握手
注:WebSocket服务器使用《HTML5 WebSocket权威指南》3.4节中使用nodejs实现,WebSocket客户端使用Chrome浏览器实现。
二 协议对比
| 特性 | TCP | HTTP | WebSocket |
|---|---|---|---|
| 寻址 | IP地址和端口 | URL | URL |
| 并发传输 | 全双工 | 半双工 | 全双工 |
| 内容 | 字节流 | MIME消息 | 文本和二进制消息 |
| 消息定界 | 否 | 是 | 是 |
| 连接定向 | 是 | 否 | 是 |
注:
- TCP传送字节流,消息定界由高层协议来表现。
- WebSocket中,多字节的消息作为整体、按照顺序到达。.
三 初始握手
1 HTTP请求升级协议和协议升级成功响应
- HTTP请求
GET ws://localhost:9999/echo HTTP/1.1
Host: localhost:9999
Connection: Upgrade
Upgrade: websocket
Sec-WebSocket-Version: 13
Sec-WebSocket-Key: UjxPJpGjxC4JH5+0znrYBg==
Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits
- HTTP响应
HTTP/1.1 101 Web Socket Protocol Handshake
Upgrade: WebSocket
Connection: Upgrade
sec-websocket-accept: NTeDlW+9/P48+pMOtotMmM1m/J0=
注:响应不带Sec-WebSocket-Extensions代表该服务器不支持请求中的拓展
2 计算响应键值
(1)概述
响应中的sec-websocket-accept等于base64(sha1(请求中的Sec-WebSocket-Key + 258EAFA5-E914-47DA-95CA-C5AB0DC85B11))
(2)nodejs版本实现
var KEY_SUFFIX = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
function(key){
var sha1 = crypto.createHash('sha1');
sha1.update(key+KEY_SUFFIX,'ascii');
return sha1.digest('base64');
}
(3)java版本
public class MessageDigestUtils {
private final static String KEY_SUFFIX = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
public static String generateFinalKey(String key) {
String seckey = key.trim() + KEY_SUFFIX;
MessageDigest sha1;
try {
sha1 = MessageDigest.getInstance( "SHA1" );
} catch ( NoSuchAlgorithmException e ) {
throw new IllegalStateException( e );
}
return Base64.getEncoder().encodeToString(sha1.digest(seckey.getBytes()));
}
}
(4)其他首部
| 首部字段 | 描述 |
|---|---|
| Sec-WebSocket-Key | 用于初始握手,避免跨协议攻击。 |
| Sec-WebSocket-Accept | 用于初始握手,服务器确认WebSocket协议。 |
| Sec-WebSocket-Extensions | 用于初始握手,服务器确认客户端的拓展。 |
| Sec-WebSocket-Protocol | 用于初始握手,服务器子协议选择。 |
| Sec-WebSocket-Version | 用于初始握手,对于RFC 6455对应为13。 |
四 消息格式
1 帧和消息
- 帧:最小的通信单位,包含可变长度的帧首部和净荷部分,净荷可能包含完整或部分应用消息。
- 消息:一系列帧,与应用消息对等。
2 帧格式

- FIN:表示当前帧是否为消息的最后一帧;可能一条消息就只有一帧。
- 操作码(4位):表示被传输帧的类型
- 1:文本
- 2:二进制
- 8:关闭连接
- 9:呼叫,ping
- 10:回应,pong
- 掩码位:净荷是否有掩码(只适用客户端发送给服务器的消息)
- 净荷长度:
- 0~125:表示长度
- 126:接下来2个字节的16位无符号整数才是该帧的长度
- 127:接下来8个字节的64位无符号整数才是该帧的长度,高位必须为0。
- 掩码键:包含32位,用于给净荷加掩护
- 净荷包含应用数据,如果客户端和服务器在建立连接时协商过,也可以包含自定义的扩展数据。
注:WebSocket的队首阻塞:如果一个大消息被分成多个WebSocket 帧,就会阻塞其他消息的帧。
3 数据抓包
3.1 基础信息
- 客户端:
- IP:192.168.1.10
- Port:3263
- 服务器:
- IP:192.168.1.10
- Port:9999
注:如果使用localhost,wireshark无法抓包,因为流量走的时loop back接口。
# 管理员执行route add 本机IP地址 mask 掩码 网关IP地址
route add 192.168.1.10 mask 255.255.255.255 192.168.1.1
3.2 客户端 -> 服务器(长度小于125的小包)
(1)wireshark抓包
WebSocket
1... .... = Fin: True
.000 .... = Reserved: 0x0
.... 0001 = Opcode: Text (1)
1... .... = Mask: True
.000 0101 = Payload length: 5
# [Extended Payload length (16 bits): 40200] 如果length超过125
Masking-Key: 0b4b5535
Masked payload
63 2e 39 59 64
(2)掩码解析:nodejs
// maskBytes为0b4b5535 data为632e395964
// 结果为:68656c6c6f ==> hello
var unmask = function (maskBytes, data) {
var payload = new Buffer(data.length);
for (var i = 0; i < data.length; i++) {
payload[i] = maskBytes[i % 4] ^ data[i];
}
return payload;
}
(3)掩码解析:java
public static String unmask(byte[] maskBytes,byte[] data){
byte[] payload = new byte[data.length];
for (int i = 0; i < data.length; i++) {
payload[i] = (byte)(maskBytes[i % 4] ^ data[i]);
}
return new String(payload);
}
(4)数据解析:nodejs
WebSocketConnection.prototype._processBuffer = function () {
var buf = this.buffer;
if (buf.length < 2) return;
var b1 = buf.readUInt8(0);
var fin = b1 & 0x80;
var opcode = b1 & 0x0f;
var b2 = buf.readUInt8(1);
var mask = b2 & 0x80;
var length = b2 & 0x7f;
var idx = 2; // 索引
if (length > 125) {
if (buf.length < 8) return;
if (length == 126) {
length = buf.readUInt16BE(2);
idx += 2;
} else if (length == 127) {
var highBits = buf.readUInt32BE(2);
if (highBits != 0) this.close(1009, "");// 高位必须为0
length = buf.readUInt32BE(6);
idx += 8;
}
}
// 4个字节的掩码
if (buf.length < idx + 4 + length) {
return;
}
maskBytes = buf.slice(idx, idx + 4);
idx += 4;
var payload = buf.slice(idx, idx + length);
payload = unmask(maskBytes, payload);
this._handleFrame(opcode, payload);
this.buffer = buf.slice(idx + length); // buffer置空
return true;
}
注:java版本的数据解析太麻烦,后期考虑补上。
3.3 服务器 -> 客户端 (长度小于125的小包)
- 服务器发给客户端不需要掩码,直接发送即可。
WebSocket
1... .... = Fin: True
.000 .... = Reserved: 0x0
.... 0001 = Opcode: Text (1)
0... .... = Mask: False
.000 0101 = Payload length: 5
Payload
hello
五 关闭握手
1 关闭握手异常代号
| 代号 | 描述 | 使用场景 |
|---|---|---|
| 1000 | 正常关闭 | 会话正常完成时 |
| 1001 | 离开 | 应用离开且不期望后续连接的尝试而关闭连接时 |
| 1002 | 协议错误 | 因协议错误而关闭连接时 |
| 1003 | 不可接受的数据类型 | 非二进制或文本类型时 |
| 1007 | 无效数据 | 文本格式错误,如编码错误 |
| 1008 | 消息违反政策 | 当应用程序由于其他代号不包含的原因时 |
| 1009 | 消息过大 | 当接收的消息太大,应用程序无法处理时(帧的载荷最大为64字节) |
| 1010 | 需要拓展 | |
| 1011 | 意外情况 |
2 其他代号
| 代号 | 描述 | 使用情况 |
|---|---|---|
| 0~999 | 禁止 | |
| 1000~2999 | 保留 | |
| 3000~3999 | 需要注册 | 用于程序库、框架和应用程序 |
| 4000~4999 | 私有 | 应用程序自由使用 |
3 抓包分析
(1)客户端发起关闭
WebSocket
1... .... = Fin: True
.000 .... = Reserved: 0x0
.... 1000 = Opcode: Connection Close (8)
1... .... = Mask: True
.000 0000 = Payload length: 0
Masking-Key: 461086e0
(2)服务器响应关闭
WebSocket
1... .... = Fin: True
.000 .... = Reserved: 0x0
.... 1000 = Opcode: Connection Close (8)
0... .... = Mask: False
.000 0000 = Payload length: 0
参考:
- 《Web性能权威指南》
- 《HTML5 WebSocket权威指南》
- RFC 6455
WebSocket协议探究(一)的更多相关文章
- WebSocket协议探究(三):MQTT子协议
一 复习和目标 1 复习 Nodejs实现WebSocket服务器 Netty实现WebSocket服务器(附带了源码分析) Js api实现WebSocket客户端 注:Nodejs使用的Socke ...
- WebSocket协议探究(序章)
一 WebSocket协议基于HTTP和TCP协议 与往常一样,进入WebSocket协议学习之前,先进行WebSocket协议抓包,来一个第一印象. WebSocket能实现客户端和服务器间双向.基 ...
- WebSocket协议探究(二)
一 复习和目标 1 复习 协议概述: WebSocket内置消息定界并且全双工通信 WebSocket使用HTTP进行协议协商,协商成功使用TCP连接进行传输数据 WebScoket数据格式支持二进制 ...
- Websocket 协议解析
WebSocket protocol 是HTML5一种新的协议.它是实现了浏览器与服务器全双工通信(full-duplex). 现 很多网站为了实现即时通讯,所用的技术都是轮询(po ...
- WebSocket协议开发
一直以来,网络在很大程度上都是围绕着HTTP的请求/响应模式而构建的.客户端加载一个网页,然后直到用户点击下一页之前,什么都不会发生.在2005年左右,Ajax开始让网络变得更加动态了.但所有的HTT ...
- 初识WebSocket协议
1.什么是WebSocket协议 RFC6455文档的表述如下: The WebSocket Protocol enables two-way communication between a clie ...
- Websocket协议的学习、调研和实现
本文章同时发在 cpper.info. 1. websocket是什么 Websocket是html5提出的一个协议规范,参考rfc6455. websocket约定了一个通信的规范,通过一个握手的机 ...
- python测试基于websocket协议的即时通讯接口
随着html5的广泛应用,基于websocket协议的即时通讯有了越来越多的使用场景,本文使用python中的websocket-client模块来做相关的接口测试 import webclient ...
- Websocket协议之php实现
前面学习了HTML5中websocket的握手协议.打开和关闭连接等基础内容,最近用php实现了与浏览器websocket的双向通信.在学习概念的时候觉得看懂了的内容,真正在实践过程中还是会遇到各种问 ...
随机推荐
- gis空间分析案例教程-下篇预告
1. 三维空间坐标转换,地理处理工具 2. 3参数,7参数计算,地理处理工具 3. 3参数,7参数坐标转换,地理处理工具 4. 坐标转换工具箱:集成高斯投影,参数计算,坐标转换所有功能. 作业:GIS ...
- linux操作利器alias用法
写在前边 学习这件事,有时候并不一定很刻意,而是从生活,从经验中去积累,不知道什么时候就发生了. type 命令 一般情况下,type命令被用于判断另外一个命令是否是内置命令,但是它实际上有更多的用法 ...
- IOS APP开发入门案例
1.创建新项目 2.设计布局,main.storyboard中,在控件库中 3.布局控件关联控制器 4.设置事件或者显示模式 5.编写代码: import UIKit class ViewContro ...
- js的Map实例
1.创建实例 let map= new Map(); // 创建 2.对map的写入 // 要添加的对象 let obj1 = {name:'张三', sex:'boy',age: 21}; let ...
- 一百四十八:部署python项目之环境依赖
环境:centos7 + python3.6 准备工作,生成项目requirements.txt文件,用于存放第三方库和版本信息:pip freeze > requirements.txt,并且 ...
- spark的task调度器(FAIR公平调度算法)
FAIR 调度策略的树结构如下图所示: FAIR 调度策略内存结构 FAIR 模式中有一个 rootPool 和多个子 Pool, 各个子 Pool 中存储着所有待分配的 TaskSetMagage ...
- Java-WebSocket调用报错:WebSocketClient objects are not reuseable
我的代码 import com.google.common.collect.ImmutableMap; import com.google.common.io.ByteArrayDataOutput; ...
- Windows下免费的屏幕录制软件——EV录屏——推荐
EV录屏,现在使用起来效果还不错. 软件地址:https://www.ieway.cn/evcapture.html
- 【转】hr的嘴,骗人的鬼
入职前,从上往下读,入职后,从下往上读. - 我们非常欢迎新鲜血液补充进我们的团队:- 如果条件太苛刻,我待不久的:- 我们公司绝对不可能这样:- 请问每个月的工作都能按时足额发放吗?- 这难道不是理 ...
- python介绍,计算机组成。内存分布,进制,操作系统介绍
学习小方法 三个W一个Hwwwh:what(是什么) why(为什么) where(怎么用) how(如何用) 来思考知识点多练,多写,多敲代码增加熟练度与代码量 Python 是一门面向后台的编 ...