WebSocket原理与实践(三)--解析数据帧
WebSocket原理与实践(三)--解析数据帧
1-1 理解数据帧的含义:
在WebSocket协议中,数据是通过帧序列来传输的。为了数据安全原因,客户端必须掩码(mask)它发送到服务器的所有帧,当它收到一个
没有掩码的帧时,服务器必须关闭连接。不过服务器端给客户端发送的所有帧都不是掩码的,如果客户端检测到掩码的帧时,也一样必须关闭连接。
当帧被关闭的时候,可能发送状态码1002(协议错误)。
基本帧协议如下:
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-------+-+-------------+-------------------------------+
|F|R|R|R| opcode|M| Payload len | Extended payload length |
|I|S|S|S| (4) |A| (7) | (16/64) |
|N|V|V|V| |S| | (if payload len==126/127) |
| |1|2|3| |K| | |
+-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
| Extended payload length continued, if payload len == 127 |
+ - - - - - - - - - - - - - - - +-------------------------------+
| |Masking-key, if MASK set to 1 |
+-------------------------------+-------------------------------+
| Masking-key (continued) | Payload Data |
+-------------------------------- - - - - - - - - - - - - - - - +
: Payload Data continued ... :
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
| Payload Data continued ... |
+---------------------------------------------------------------+
如上是基本帧协议,它带有操作码(opcode)的帧类型,负载长度,和用于 "扩展数据" 与 "应用数据" 及 它们一起定义的 "负载数据"的指定位置,
某些字节和操作码保留用于未来协议的扩展。
FIN(1位): 是否为消息的最后一个数据帧。
RSV1,RSV2,Rsv3(每个占1位),必须是0,除非一个扩展协商为非零值定义的。Opcode
表示帧的类型(4位),例如这个传输的帧是文本类型还是二进制类型,二进制类型传输的数据可以是图片或者语音之类的。(这4位转换成16进制值表示的意思如下):
0x0 表示附加数据帧
0x1 表示文本数据帧
0x2 表示二进制数据帧
0x3-7 暂时无定义,为以后的非控制帧保留
0x8 表示连接关闭
0x9 表示ping
0xA 表示pong
0xB-F 暂时无定义,为以后的控制帧保留
Mask(占1位): 表示是否经过掩码处理, 1 是经过掩码的,0是没有经过掩码的。
payload length (7位+16位,或者 7位+64位),定义负载数据的长度。
1. 如果数据长度小于等于125的话,那么该7位用来表示实际数据长度。
2. 如果数据长度为126到65535(2的16次方)之间,该7位值固定为126,也就是 1111110,往后扩展2个字节(16为,第三个区块表示),用于存储数据的实际长度。
3. 如果数据长度大于65535, 该7位的值固定为127,也就是 1111111 ,往后扩展8个字节(64位),用于存储数据实际长度。
Masking-key(0或者4个字节),该区块用于存储掩码密钥,只有在第二个子节中的mask为1,也就是消息进行了掩码处理时才有,否则没有,
所以服务器端向客户端发送消息就没有这一块。
Payload data 扩展数据,是0字节,除非已经协商了一个扩展。
1-2 客户端到服务器掩码
WebSocket协议要求客户端所发送的帧必须掩码,掩码的密钥是一个32位的随机值。所有数据都需要与掩码做一次异或运算。帧头在第二个字节的第一位表示该帧是否使用了掩码。
WebSocket服务器接收的每个载荷在处理之前首先需要处理掩码,解除掩码之后,服务器将得到原始消息内容。二进制消息可以直接交付。文本消息将进行UTF-8解码
并输出到字符串中。
二进制位运算符知识扩展:
>> 含义是右移运算符,
右移运算符是将一个二进制位的操作数按指定移动的位数向右移动,移出位被丢弃,左边移出的空位一律补0.
比如 11 >> 2, 意思是说将数字11右移2位。
首先将11转换为二进制数为 0000 0000 0000 0000 0000 0000 0000 1011 , 然后把低位的最后2个数字移出,因为该数字是正数,
所以在高位补零,则得到的最终结果为:0000 0000 0000 0000 0000 0000 0000 0010,转换为10进制是2.
<< 含义是左移运算符
左移运算符是将一个二进制位的操作数按指定移动的位数向左移位,移出位被丢弃,右边的空位一律补0.
比如 3 << 2, 意思是说将数字3左移2位,
首先将3转换为二进制数为 0000 0000 0000 0000 0000 0000 0000 0011 , 然后把该数字高位(左侧)的两个零移出,其他的数字都朝左平移2位,
最后在右侧的两个空位补0,因此最后的结果是 0000 0000 0000 0000 0000 0000 0000 1100,则转换为十进制是12(1100 = 1*2的3次方 + 1*2的2字方)
注意1: 在使用补码作为机器数的机器中,正数的符号位为0,负数的符号位为1(一般情况下).
比如:十进制数13在计算机中表示为00001101,其中第一位0表示的是符号
注意2:负数的二进制位如何计算?
比如二进制的原码为 10010101,它的补码怎么计算呢?
首先计算它的反码是 01101010; 那么补码 = 反码 + 1 = 01101011
再来看一个列子:
-7 >> 2 意思是将数字 -7 右移2位。
负数先用它的绝对值正数取它的二进制代码,7的二进制位为: 0000 0000 0000 0000 0000 0000 0000 0111 ,那么 -7的二进制位就是 取反,
取反后再加1,就变成补码。
因此-7的二进制位: 1111 1111 1111 1111 1111 1111 1111 1001,
因此 -7右移2位就成 1111 1111 1111 1111 1111 1111 1111 1110 因此转换成十进制的话 -7 >> 2 ,值就变成 -2了。
数据帧解析的程序如下代码:(decodeDataFrame.js 代码如下:)
var crypto = require('crypto'); var WS = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11'; require('net').createServer(function(o) {
var key;
o.on('data', function(e) {
if (!key) { key = e.toString().match(/Sec-WebSocket-Key: (.+)/)[1]; // WS的字符串 加上 key, 变成新的字符串后做一次sha1运算,最后转换成Base64
key = crypto.createHash('sha1').update(key+WS).digest('base64'); // 输出字段数据,返回到客户端,
o.write('HTTP/1.1 101 Switching Protocol\r\n');
o.write('Upgrade: websocket\r\n');
o.write('Connection: Upgrade\r\n');
o.write('Sec-WebSocket-Accept:' +key+'\r\n');
// 输出空行,使HTTP头结束
o.write('\r\n');
} else {
// 数据处理
onmessage(e);
}
})
}).listen(8000);
/*
>> 含义是右移运算符,
右移运算符是将一个二进制位的操作数按指定移动的位数向右移动,移出位被丢弃,左边移出的空位一律补0.
比如 11 >> 2, 意思是说将数字11右移2位。
首先将11转换为二进制数为 0000 0000 0000 0000 0000 0000 0000 1011 , 然后把低位的最后2个数字移出,因为该数字是正数,
所以在高位补零,则得到的最终结果为:0000 0000 0000 0000 0000 0000 0000 0010,转换为10进制是2. << 含义是左移运算符
左移运算符是将一个二进制位的操作数按指定移动的位数向左移位,移出位被丢弃,右边的空位一律补0.
比如 3 << 2, 意思是说将数字3左移2位,
首先将3转换为二进制数为 0000 0000 0000 0000 0000 0000 0000 0011 , 然后把该数字高位(左侧)的两个零移出,其他的数字都朝左平移2位,
最后在右侧的两个空位补0,因此最后的结果是 0000 0000 0000 0000 0000 0000 0000 1100,则转换为十进制是12(1100 = 1*2的3次方 + 1*2的2字方) 注意1: 在使用补码作为机器数的机器中,正数的符号位为0,负数的符号位为1(一般情况下).
比如:十进制数13在计算机中表示为00001101,其中第一位0表示的是符号 注意2:负数的二进制位如何计算?
比如二进制的原码为 10010101,它的补码怎么计算呢?
首先计算它的反码是 01101010; 那么补码 = 反码 + 1 = 01101011 再来看一个列子:
-7 >> 2 意思是将数字 -7 右移2位。
负数先用它的绝对值正数取它的二进制代码,7的二进制位为: 0000 0000 0000 0000 0000 0000 0000 0111 ,那么 -7的二进制位就是 取反,
取反后再加1,就变成补码。
因此-7的二进制位: 1111 1111 1111 1111 1111 1111 1111 1001,
因此 -7右移2位就成 1111 1111 1111 1111 1111 1111 1111 1110 因此转换成十进制的话 -7 >> 2 ,值就变成 -2了。
*/
function decodeDataFrame(e) { var i = 0, j, s, arrs = [],
frame = {
// 解析前两个字节的基本数据
FIN: e[i] >> 7,
Opcode: e[i++] & 15,
Mask: e[i] >> 7,
PayloadLength: e[i++] & 0x7F
}; // 处理特殊长度126和127
if (frame.PayloadLength === 126) {
frame.PayloadLength = (e[i++] << 8) + e[i++];
}
if (frame.PayloadLength === 127) {
i += 4; // 长度一般用4个字节的整型,前四个字节一般为长整型留空的。
frame.PayloadLength = (e[i++] << 24)+(e[i++] << 16)+(e[i++] << 8) + e[i++];
}
// 判断是否使用掩码
if (frame.Mask) {
// 获取掩码实体
frame.MaskingKey = [e[i++], e[i++], e[i++], e[i++]];
// 对数据和掩码做异或运算
for(j = 0, arrs = []; j < frame.PayloadLength; j++) {
arrs.push(e[i+j] ^ frame.MaskingKey[j%4]);
}
} else {
// 否则的话 直接使用数据
arrs = e.slice(i, i + frame.PayloadLength);
}
// 数组转换成缓冲区来使用
arrs = new Buffer(arrs);
// 如果有必要则把缓冲区转换成字符串来使用
if (frame.Opcode === 1) {
arrs = arrs.toString();
}
// 设置上数据部分
frame.PayloadLength = arrs;
// 返回数据帧
return frame;
} function onmessage(e) {
console.log(e)
e = decodeDataFrame(e); // 解析数据帧
console.log(e); // 把数据帧输出到控制台
}
index.html代码如下:
<html>
<head>
<title>WebSocket Demo</title>
</head>
<body>
<script type="text/javascript">
var ws = new WebSocket("ws://127.0.0.1:8000");
ws.onerror = function(e) {
console.log(e);
};
ws.onopen = function(e) {
console.log('握手成功');
ws.send('次碳酸钴');
}
</script>
</body>
</html>
demo还是一样,decodeDataFrame.js 和 index.html, 先进入项目中对应的目录后,使用node decodeDataFrame.js, 然后打开index.html后查看效果
如下:
这样服务器接收客户端穿过了的数据就没问题了。
WebSocket原理与实践(三)--解析数据帧的更多相关文章
- WebSocket原理与实践(四)--生成数据帧
WebSocket原理与实践(四)--生成数据帧 从服务器发往客户端的数据也是同样的数据帧,但是从服务器发送到客户端的数据帧不需要掩码的.我们自己需要去生成数据帧,解析数据帧的时候我们需要分片. 消息 ...
- WebSocket原理与实践(二)---WebSocket协议
WebSocket原理与实践(二)---WebSocket协议 WebSocket协议是为了解决web即时应用中服务器与客户端浏览器全双工通信问题而设计的.协议定义ws和wss协议,分别为普通请求和基 ...
- WebSocket原理与实践(一)---基本原理
WebSocket原理与实践(一)---基本原理 一:为什么要使用WebSocket?1. 了解现有的HTTP的架构模式:Http是客户端/服务器模式中请求-响应所用的协议,在这种模式中,客户端(一般 ...
- WebSocket原理与实践
开题思考:如何实现客户端及时获取服务端数据? Polling 指客户端每隔一段时间(周期性)请求服务端获取数据,可能有更新数据返回,也可能什么都没有,它并不在乎服务端数据有无更新.(Web端一般采用a ...
- Atitit 表达式原理 语法分析 原理与实践 解析java的dsl 递归下降是现阶段主流的语法分析方法
Atitit 表达式原理 语法分析 原理与实践 解析java的dsl 递归下降是现阶段主流的语法分析方法 于是我们可以把上面的语法改写成如下形式:1 合并前缀1 语法分析有自上而下和自下而上两种分析 ...
- 嵌入式实时操作系统μCOS原理与实践任务控制与时间的解析
/*************************************************************************************************** ...
- 2017-2018-2 20155228 《网络对抗技术》 实验三:MAL_免杀原理与实践
2017-2018-2 20155228 <网络对抗技术> 实验三:MAL_免杀原理与实践 实验内容 正确使用msf编码器,msfvenom生成如jar之类的其他文件,veil-evasi ...
- kafka原理和实践(三)spring-kafka生产者源码
系列目录 kafka原理和实践(一)原理:10分钟入门 kafka原理和实践(二)spring-kafka简单实践 kafka原理和实践(三)spring-kafka生产者源码 kafka原理和实践( ...
- nodejs 实践:express 最佳实践(三) express 解析
nodejs 实践:express 最佳实践(三) express 解析 nodejs 发展很快,从 npm 上面的包托管数量就可以看出来.不过从另一方面来看,也是反映了 nodejs 的基础不稳固, ...
随机推荐
- cocoapods使用-库托管到svn或者github
下拉svn库(自定义库或者第三方库)到工程中: 1. 若未安装,请安装cocoapods: http://www.cnblogs.com/sunjianfei/p/6089231.html ...
- 加快maven中jar包的下载速度,maven镜像收集
maven下载jar包的默认仓库是http://my.repository.com/repo/path速度较慢,通过配置国内镜像提高下载速度 1.打开eclipse--->Window---&g ...
- MAC MYSQ忘记密码重置方法
网友的方法,记个笔记请勿转载. step1: 关闭mysql服务: 苹果->系统偏好设置->最下边点mysql 在弹出页面中 关闭mysql服务(点击stop mysql server) ...
- mac 上运行httpserver的问题
如果你的系统是window 我建议你安装http-server 非常好用, 如果是mac,系统自带的就有httpserver 服务,没有必要在安装 按照我说的来操作 首先 sudo apachectl ...
- 前端开发周报: CSS 布局方式方式与JavaScript数据结构和算法
前端开发周报:CSS 布局方式与JavaScript动画库 1.常见 CSS 布局方式详见: 一些常见的 CSS 布局方式梳理,涉及 Flex 布局.Grid 布局.圣杯布局.双飞翼布局等.http: ...
- python自动化开发-5a
python的常用模块 在计算机程序的开发过程中,随着程序代码越写越多,在一个文件里代码就会越来越长,越来越不容易维护.为了编写可维护的代码,我们把很多函数分组,分别放到不同的文件里,这样,每个文件包 ...
- Session Cookies随笔
1.对于Session而言 它是用来保存在服务端的信息,可以用来做登录验证,在后台保存有用信息. 一个浏览器访问一个网站,就是建立一个连接,连接信息是独立的,就是在该建立的连接内,保存到Session ...
- OkHttp3源码详解(一) Request类
每一次网络请求都是一个Request,Request是对url,method,header,body的封装,也是对Http协议中请求行,请求头,实体内容的封装 public final class R ...
- char/varchar/nvarchar的区别
原文:https://blog.csdn.net/w516162189/article/details/78914035 我们在设计数据库的时候,需要根据需求场景选择合适的字段类型,对数据的执行效率有 ...
- 在VS 一切正常,发布到IIS出现问题 [System.Data.OracleClient 需要 Oracle 客户端软件 version 8.1.7 或更高版本]
在VS 一切正常,发布到IIS出现问题 [System.Data.OracleClient 需要 Oracle 客户端软件 version 8.1.7 或更高版本] 前提条件: 在vs 开发的时候,一 ...