握手协议实现了,接下来就是解析数据了,这个相对来说要麻烦很多,相比草案7.6变得更复杂了。下图是数据传输的格式:
各个值的具体含义可以参考这个中文翻译:http://blog.csdn.net/fenglibing/article/details/6852497,英文原文在这里:http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-10#section-4.2
简单概括一下就是:FIN一直为1,RSV1、RSV2、RSV3一直为0,当发送内容为文本时,opcode为1,MASK如果为1就使用掩码,需要获取四位掩码,依次轮流和数据做异或运算,第一个内容数据与第一个掩码异或,第二个内容数据与第二个掩码异或……第五个内容数据与第一个掩码异或……一直到结束,然后再对内容进行编码就可以了。
在Chrome/Firefox下面,FIN永远为1,如果只是文本消息,opcode一直是1,RSV1、RSV2、RSV3都为0,所以第一个数据是0×81;MASK为1,再跟上数据长度,所以第二个数据会大于0×80,由于数据长度不一样,其需要占用的字节数也不一样,因此掩码的位置也不一样。根据规范,换算下来是这样:数据总长度(包含FIN、RSV等等信息)小于0×84,则掩码为第三个数据到第六个数据;数据总长度(包含FIN、RSV等等信息)小于0xfe81,则掩码为第五个数据到第八个数据;其他掩码则为第十三个数据到第十六个数据。当数据长度超过一个包的长度时,后面的数据包不再包含RIN、RSV和MASK等信息,二是直接为内容数据,需要根据上一次取到的MASK来做异或运算。在客户端主动要求断开链接时,会没有内容数据,只有头信息和掩码,长度固定为6,为了避免混淆,Chrome会保证正常内容发送时不会出现6字节的数据,基本上可以把6字节当断开连接请求处理。区分客户端主动要求断开连接和正常的连续数据包要根据长度区分:如果是连续的数据包,则已解析的包的长度会小于首包头信息里的长度,否则则认为是客户端主动要求断开链接。主动断开连接时会发送的一个额外的数据包,此时第一个字节不会为正常的0×81,通常为0×88,NodeJS写出来大致是这样的:
-
module.exports = Parser;
-
-
var util = require('util'),
-
events = require('events');
-
-
/**
-
* http.Server 数据解析类
-
* */
-
function Parser(version) {
-
events.EventEmitter.call(this);
-
-
this.version = version || 'draft10';
-
this.length = 0;
-
this.parsed = 0;
-
this.maskData = [];
-
this.frameData = [];
-
this.order = 0;
-
this.closing = false;
-
}
-
-
util.inherits(Parser, events.EventEmitter);
-
-
Parser.prototype.write = function(data) {
-
console.log(data.length);
-
dataParser[this.version](data, this);
-
};
-
-
// 数据解析工具
-
var dataParser = {
-
'draft10': function(data, parser){
-
var pkt, i =0, len = 0, start = 0;
-
-
// 如果不包含包头,而且数据已经解析完成
-
// 则认为这个包为结束包,首字节通常为0x88
-
if(data[0] != 0x81 && parser.length == 0){
-
parser.emit('close');
-
parser.closing = false;
-
return;
-
}
-
-
// 草案10
-
// 首包会包含掩码信息
-
if(data[0] == 0x81){
-
// 使用了掩码
-
if(data[1] >= 0x80){
-
// 数据长度不一样,掩码位置不一样
-
if(data.length < 0x84){
-
// firefox下,超过一个包长度的数据会被拆分为多个包
-
// 其中首包只包含头信息,第二个字节为0xfe
-
if(data[1] == 0xfe){
-
len = data.length;
-
// firefox
-
parser.maskData = [data[len - 4], data[len - 3], data[len - 2], data[len - 1]];
-
parser.length = data[len - 5];
-
for(i = len - 6; i > 1; i--){
-
parser.length += data[i] * (len - 5 - i) * 256;
-
}
-
console.log('firefox multi packages, length: ', parser.length);
-
start = data.length;
-
}
-
else{
-
// chrome
-
parser.length = data[1] - 0x80;
-
console.log('7bit, length: ', parser.length);
-
parser.maskData = [data[2], data[3], data[4], data[5]];
-
start = 6;
-
}
-
}
-
else if(data.length < 0xfe80){ parser.length = data[2] * 256 + data[3]; console.log('7 + 16bit, length: ', parser.length); parser.maskData = [data[4], data[5], data[6], data[7]]; start = 8; } else{ dparser.length = data[11]; for(i = 10; i > 3; i--){
-
parser.length += data[i] * (11 - i) * 256;
-
}
-
console.log('7 + 64bit, length: ', parser.length);
-
parser.maskData = [data[12], data[13], data[14], data[15]];
-
start = 16;
-
}
-
-
for(i = start, len = data.length; i < len; i++){
-
parser.frameData.push(parser.maskData[(i - start) % 4] ^ data[i]);
-
}
-
}
-
else{
-
if(data.length < 0x80){
-
start = 2;
-
}
-
else if(data.length < 0xfe81){
-
start = 4;
-
}
-
else{
-
start = 12;
-
}
-
// find contents
-
parser.frameData = data.splice(start);
-
}
-
console.log('1st packge frame length: ', parser.frameData.length);
-
if(parser.frameData.length == parser.length){
-
pkt = new Buffer(parser.frameData);
-
// console.log(pkt.toString('utf8', 0, pkt.length));
-
parser.emit('message', pkt.toString('utf8', 0, pkt.length));
-
// 数据包结束,重置长度信息
-
parser.frameData = [];
-
parser.length = 0;
-
}
-
return;
-
}
-
-
// 连续的数据包
-
if(parser.maskData.length){
-
// continue to parse data
-
for(i = 0, l = data.length; i < l; i++){
-
parser.frameData.push(parser.maskData[i % 4] ^ data[i]);
-
}
-
console.log('frame length: ', parser.frameData.length);
-
if(parser.frameData.length == parser.length){
-
pkt = new Buffer(parser.frameData);
-
// console.log(pkt.toString('utf8', 0, pkt.length));
-
parser.emit('message', pkt.toString('utf8', 0, pkt.length));
-
// 数据包结束,重置长度信息
-
parser.frameData = [];
-
parser.length = 0;
-
}
-
return;
-
}
-
}
-
};
转自:http://fdream.net/blog/article/759.aspx
在Draft 10中,如果解析数据的过程弄清楚了,这个就更简单了,返回数据的格式和之前接受到的数据格式非常类似,只是你不用生成mask了,头部的其他格式还是一模一样的。
第一个字节还是固定的,是0×81,意义和接受数据的意义一样,第二个字节也是,后七个位表示数据长度,由于没有mask,所以第一位是0;长度的表示方法和接受的标识方法一致,可能用7位表示,也可能用16位表示。
用NodeJS实现如下:
-
var socketWriter = {
-
'draft10': function(socket, data){
-
var frames,
-
length = new Buffer(data, 'utf8').length;
-
if(data.length > 0x7d){
-
frames = new Buffer(4);
-
frames[0] = 0x81;
-
frames[1] = 0x7e;
-
frames[2] = length >> 8;
-
frames[3] = length & 0xFF; //1111 1111
-
}
-
else{
-
frames = new Buffer(2);
-
frames[0] = 0x81;
-
frames[1] = length;
-
}
-
-
if (socket.writable) {
-
socket.write(frames, 'binary');
-
socket.write(data, 'utf8');
-
return true;
-
}
-
return false;
-
},
-
'draft76': function(socket, data){
-
var byteLen = Buffer.byteLength(data, 'utf8'),
-
bytes = new Buffer(byteLen + 2);
-
-
bytes[0] = 0x00;
-
bytes.write(data, 1, 'utf8');
-
bytes[byteLen + 1] = 0xFF;
-
-
return socket.write(bytes);
-
}
-
};
-
-
socketWriter['draft75'] = socketWriter['draft76'];
转自:http://fdream.net/blog/article/767.aspx
http://fdream.net/blog/article/767.aspx
分享到:
相关推荐
UI01.HTML 页面,采用echarts折线图、柱状图、饼图、仪表盘等显示形式,展示后台发送的数据; 2、javascript websocket.js,websocket页面使用javascript文件,定义.onopen、onmessage、onclose、onerror函数,html...
基于node的前端websocket简单实时推送数据基本用法 含有goEasy基础版本
主要介绍了C#实现WebSocket协议客户端和服务器websocket sharp组件实例解析,包括websocket sharp组件的概念及使用方法,需要的朋友可以参考下
vue ,uni-app 都可以使用得表情包大全,下载即可使用
cefsharp后台代码获取 网页中有websocket(ws)发送和收到的数据
Spring Boot整合websocket实现群聊,点对点聊天,图片发送,音频发送
使用RabbitMQ消息队列,实现使用Java代码向MQ发送消息,将发送的消息实时显示到Web页面上
request: socket句柄,能够发送和接收数据接。发送数据request.ws.send(data),收数据request.ws_recv(1024) data: 客户端发送的数据存于此处 from pywss import Pyws, route @route('/test/example
把数据封按WebSocket协议封装为16进制,解析服务端收到的WebSocket16进制包。
包含了spingBoot服务器代码以及android客户端,客户端基于Websocket协议以及Okhttp框架实现的语音是半吊子的Kotlin,支持文字语音图片发送,
WebSocket长连接实现聊天IM 接收发送消息,百分百能用
WebSocket安卓客户端实现详解(二)--客户端发送请求
项目用到了websocket,需求是调用后台接口时候可以主动往前台推送数据 下面是代码,涉及业务部分基本删除, 思路是,在前台打开页面的时候会与websocket服务建立连接,这时候会产生websocketsession ,把websocket...
本例使用websocket获取服务端的图片并显示在浏览器上,服务端使用java注解方式实现,在tomcat8中调试通过
通过调用webSocketSev类的方法,指定用户名,实现指定用户发送消息和群发消息
使用websocket+mysql+java8+Tomcat8技术,管理端动态添加数据到数据库,用户端实时刷新新添加的数据。
springboot实现websocket发送和接收消息
说明:工程分为两个。一个是Linux C语言编写的MQTT客户端,另一个是websocket编写的MQTT客户端,先运行Linux的,再运行websocket就出实验现象了。(发布的主要是温湿度数据、继电器控制状态、GPS定位系统等等)
c#、.NET6实现websocket协议demo,完整清晰的解析和打包示例 包含请求头解析、数据帧解析、数据帧掩码处理、数据帧打包
websocket 给指定用户发送聊天消息,Java,8080端口,