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>

查看github上的源码

demo还是一样,decodeDataFrame.js 和 index.html, 先进入项目中对应的目录后,使用node decodeDataFrame.js,  然后打开index.html后查看效果

如下:

这样服务器接收客户端穿过了的数据就没问题了。

WebSocket原理与实践(三)--解析数据帧的更多相关文章

  1. WebSocket原理与实践(四)--生成数据帧

    WebSocket原理与实践(四)--生成数据帧 从服务器发往客户端的数据也是同样的数据帧,但是从服务器发送到客户端的数据帧不需要掩码的.我们自己需要去生成数据帧,解析数据帧的时候我们需要分片. 消息 ...

  2. WebSocket原理与实践(二)---WebSocket协议

    WebSocket原理与实践(二)---WebSocket协议 WebSocket协议是为了解决web即时应用中服务器与客户端浏览器全双工通信问题而设计的.协议定义ws和wss协议,分别为普通请求和基 ...

  3. WebSocket原理与实践(一)---基本原理

    WebSocket原理与实践(一)---基本原理 一:为什么要使用WebSocket?1. 了解现有的HTTP的架构模式:Http是客户端/服务器模式中请求-响应所用的协议,在这种模式中,客户端(一般 ...

  4. WebSocket原理与实践

    开题思考:如何实现客户端及时获取服务端数据? Polling 指客户端每隔一段时间(周期性)请求服务端获取数据,可能有更新数据返回,也可能什么都没有,它并不在乎服务端数据有无更新.(Web端一般采用a ...

  5. Atitit 表达式原理 语法分析 原理与实践 解析java的dsl  递归下降是现阶段主流的语法分析方法

    Atitit 表达式原理 语法分析 原理与实践 解析java的dsl  递归下降是现阶段主流的语法分析方法 于是我们可以把上面的语法改写成如下形式:1 合并前缀1 语法分析有自上而下和自下而上两种分析 ...

  6. 嵌入式实时操作系统μCOS原理与实践任务控制与时间的解析

    /*************************************************************************************************** ...

  7. 2017-2018-2 20155228 《网络对抗技术》 实验三:MAL_免杀原理与实践

    2017-2018-2 20155228 <网络对抗技术> 实验三:MAL_免杀原理与实践 实验内容 正确使用msf编码器,msfvenom生成如jar之类的其他文件,veil-evasi ...

  8. kafka原理和实践(三)spring-kafka生产者源码

    系列目录 kafka原理和实践(一)原理:10分钟入门 kafka原理和实践(二)spring-kafka简单实践 kafka原理和实践(三)spring-kafka生产者源码 kafka原理和实践( ...

  9. nodejs 实践:express 最佳实践(三) express 解析

    nodejs 实践:express 最佳实践(三) express 解析 nodejs 发展很快,从 npm 上面的包托管数量就可以看出来.不过从另一方面来看,也是反映了 nodejs 的基础不稳固, ...

随机推荐

  1. ORM&MySQL

    概念: ORM:对象关系映射 , 全拼 Object-Relation Mapping ,是一种为了解决面向对象与关系数据库存在的互不匹配现象的技术.主要实现模型对象到关系型数据库数据的映射.比如:把 ...

  2. [笔记] imooc《JavaScript深入浅出》对象与函数

    懒得做草稿了,习惯md也懒得扔印象笔记 主要是之前没去接触这一部分,就随手记下来了 创建对象的方法 对象字面量 new构造器/原型链 Object.create() 属性操作 属性读写(以及读写异常. ...

  3. JS点击按钮下载文件

    通过form表单提交: 由于ajax函数的返回类型只有xml.text.json.html等类型,没有“流”类型,所以通过ajax去请求该接口是无法下载文件的,所以我们创建一个新的form元素来请求接 ...

  4. 如今领占主导地位的19种AI技术!

    如今领占主导地位的19种AI技术! http://blog.itpub.net/31542119/viewspace-2212797/ 深度学习的突破将人工智能带进全新阶段. 2006 年-2015 ...

  5. IDEA项目搭建六——使用Eureka和Ribbon进行项目服务化

    一.Eureka的作用 这里先简单说明使用eureka进行业务层隔离,实现项目服务化也可以理解为微服务,我一直崇尚先实现代码再学习理论,先简单上手进行操作,eureka使用分为三块,1是服务注册中心, ...

  6. 程序员Web面试之jQuery

    又到了一年一度的毕业季了,青春散场,却等待下一场开幕. 在求职大军中,IT行业的程序员.码农是工科类大学生的热门选择之一, 尤其是近几年Web的如火如荼,更是吸引了成千上万的程序员投身其中追求自己的梦 ...

  7. Angular基础(四) 创建Angular应用

    应用(Application)是由组件构成的树.树的根部是最顶层的组件即应用本身,启动的时候,浏览器会最先渲染顶层组件,然后根据树形结构,迭代渲染子组件.组件是可装配的,可以互相组合以构成更大的组件. ...

  8. iOS开发NSDate、NSString、时间戳之间的转化

    //将UTCDate(世界标准时间)转化为当地时区的标准Date(钟表显示的时间) //NSDate *date = [NSDate date]; 2018-03-27 06:54:41 +0000 ...

  9. (网页)jQuery的时间datetime控件在AngularJs中使用实例

    百度一下,自己也想了一下,有一种简单,无脑的方式分享给你: <input ng-model="start" id="start" placeholder= ...

  10. [20171124]xxd与通配符.txt

    [20171124]xxd与通配符.txt --//linux 上许多命令都支持通配符,比如$ ls -l *.txt-rw-r--r-- 1 oracle oinstall 44801024 201 ...