我的编程空间,编程开发者的网络收藏夹
学习永远不晚

浅析nodejs实现Websocket的数据接收与发送

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

浅析nodejs实现Websocket的数据接收与发送

WebSocket是HTML5开始提供的一种浏览器与服务器间进行全双工通讯的网络技术。在WebSocket API中,浏览器和服务器只需要要做一个握手(handshaking)的动作,然后,浏览器和服务器之间就形成了一条快速通道。两者之间就直接可以数据互相传送。

WebSocket是一个通信的协议,分为服务器和客户端。服务器放在后台,保持与客户端的长连接,完成双方通信的任务。客户端一般都是实现在支持HTML5浏览器核心中,通过提供JavascriptAPI使用网页可以建立websocket连接。

在我写这篇文章里:基于html5和nodejs相结合实现websocket即使通讯,里面主要是借助了nodejs-websocket这个插件,后来还用了socket.io做了些demo,但是,这些都是借助于别人封装好的插件做出来的,websocket到底是怎么实现的呢?之前真没想过,最近看朴灵大神的《深入浅析node.js》时候,看到了websocket那一节,看了websocket的数据帧定义,想着用nodejs实现。经过一番折腾实现了。

客户端的代码就不说了,websocket的API还是很简单的,就通过onmessage、onopen、onclose,以及send方法就可以实现了。
websocket api通过onmessage、onopen、onclose以及send方法实现客户端的代码。具体详情就不多说了。

主要说服务端的代码:

首先是协议的升级,这个比较简单,就简述一下:当在客户端执行new Websocket("ws://XXX.com/")的时候,客户端就会发起请求报文进行握手申请,报文中有个很重要的key就是Sec-WebSocket-Key,服务端获取到key,然后将这个key与字符串258EAFA5-E914-47DA-95CA-C5AB0DC85B11相连,对新的字符串通过sha1安全散列算法计算出结果后,再进行base64编码,并且将结果放在请求头的"Sec-WebSocket-Accept"中返回即可完成握手。具体请看代码:


server.on('upgrade', function (req, socket, upgradeHead) {
 var key = req.headers['sec-websocket-key'];
 key = crypto.createHash("sha1").update(key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11").digest("base64");
 var headers = [
 'HTTP/1.1 101 Switching Protocols',
 'Upgrade: websocket',
 'Connection: Upgrade',
 'Sec-WebSocket-Accept: ' + key
 ];
 socket.setNoDelay(true);
 socket.write(headers.join("rn") + "rnrn", 'ascii');
 var ws = new WebSocket(socket);
 webSocketCollector.push(ws);
 callback(ws);
});

upgrade事件其实是http这个模块的封装,再往底层就是net模块的实现,其实都差不多,如果直接用net模块来实现的话,就是监听net.createServer返回的server对象的data事件,接收到的第一份数据就是客户端发来的升级请求报文。

上面那段代码就完成了websocket的握手,然后就可以开始数据传输了。

看数据传输之前,先看看websocket数据帧的定义(因为觉得深入浅出nodejs里的帧定义图最容易理解,所以就贴这张了):

查看图片

上面的图中,每一列就是一个字节,一个字节总共是8位,每一位就是一个二进制数,不同位的值会对应不同的意义。

fin:指示这个是消息的最后片段。第一个片段可能也是最后的片段。如果为1即为最后片段,(其实这个位的用途我个人有点疑惑,按照书上以及网上查的资料,当数据被分片的时候,不同片应该都会有fin位,会根据fin为是不是0来判断是否为最后一帧,但是实际实现中却发现,当数据比较大需要分片时,服务端收到的数据就只有第一帧是有fin位为1,其他帧则整个帧都是数据段,也就是说,感觉这个fin位似乎用不上,至少我自己写的demo中是通过数据长度来判断是否到了最后一帧,完全没用到这个fin位是否为1来判断)

rsv1、rsv2、rsv3:各占一个位,用于扩展协商,基本上不怎么需要理,一般都是0

opcode:占四个位,可以表示0~15的十进制,0表示为附加数据帧,1表示为文本数据帧,2表示二进制数据帧,8表示发送一个连接关闭的数据帧,9表示ping,10表示pong,ping和pong都是用于心跳检测,当一端发送ping时,另一端必须响应pong表示自己仍处于响应状态。

masked:占一个位,表示是否进行掩码处理,客户端发送给服务端时为1,服务端发送给客户端时为0

payload length:占7位,或者7+16位、或者7+64位。如果第二个字节的后面七个位的十进制值小于或等于125,则直接用这七个位表示数据长度;如果该值为126,说明 125<数据长度<65535(16个位能描述的最大值,也就是16个1的时候),就用第三个字节及第四个字节即16个位来表示;如果该值为127,则说明数据长度已经大于65535,16个位也已经不足以描述数据长度了,就用第三到第十个字节这八个字节来描述数据长度。

masking key:当masked为1的时候才存在,用于对我们需要的数据进行解密。

payload data:我们需要的数据,如果masked为1,该数据会被加密,要通过masking key进行异或运算解密才能获取到真实数据。

帧定义解释完了,就可以根据数据来进行解析了,当有data过来的时候,先获取需要的数据信息,下面这段代码将获取到数据在data里的位置,以及数据长度,masking key以及opcode:


WebSocket.prototype.handleDataStat = function (data) {
 if (!this.stat) {
 var dataIndex = 2; //数据索引,因为第一个字节和第二个字节肯定不为数据,所以初始值为2
 var secondByte = data[1]; //代表masked位和可能是payloadLength位的第二个字节
 var hasMask = secondByte >= 128; //如果大于或等于128,说明masked位为1
 secondByte -= hasMask ? 128 : 0; //如果有掩码,需要将掩码那一位去掉
 var dataLength, maskedData;
 //如果为126,则后面16位长的数据为数据长度,如果为127,则后面64位长的数据为数据长度
 if (secondByte == 126) {
  dataIndex += 2;
  dataLength = data.readUInt16BE(2);
 } else if (secondByte == 127) {
  dataIndex += 8;
  dataLength = data.readUInt32BE(2) + data.readUInt32BE(6);
 } else {
  dataLength = secondByte;
 }
 //如果有掩码,则获取32位的二进制masking key,同时更新index
 if (hasMask) {
  maskedData = data.slice(dataIndex, dataIndex + 4);
  dataIndex += 4;
 }
 //数据量最大为10kb
 if (dataLength > 10240) {
  this.send("Warning : data limit 10kb");
 } else {
  //计算到此处时,dataIndex为数据位的起始位置,dataLength为数据长度,maskedData为二进制的解密数据
  this.stat = {
  index: dataIndex,
  totalLength: dataLength,
  length: dataLength,
  maskedData: maskedData,
  opcode: parseInt(data[0].toString(16).split("")[1] , 16) //获取第一个字节的opcode位
  };
 }
 } else {
 this.stat.index = 0;
 }
};

代码中均有注释,理解起来应该不难,直接看下一步,获取到数据信息后,就要对数据进行实际解析了:

经过上面handleDataStat方法的处理,stat中已经有了data的相关数据,先判断opcode,如果为9说明是客户端发起的ping心跳检测,直接返回pong响应,如果为10则为服务端发起的心跳检测。如果有masking key,则遍历数据段,对每个字节都与masking key的字节进行异或运算(网上看到一个说法很形象:就是轮流发生X关系),^符号就是进行异或运算啦。如果没有masking key则直接通过slice方法把数据截取下来。

获取到数据后,放进datas里保存,因为有可能数据被分片了,所以再将stat里的长度减去当前数据长度,只有当stat里的长度为0的时候,说明当前帧为最后一帧,然后通过Buffer.concat将所有数据合并,此时再判断一下opcode,如果opcode为8,则说明客户端发起了一个关闭请求,而我们获取到的数据则是关闭原因。如果不为8,则这数据就是我们需要的数据。然后再将stat重置为null,datas数组置空即可。至此,我们的数据解析就完成了。


WebSocket.prototype.dataHandle = function (data) {
 this.handleDataStat(data);
 var stat;
 if (!(stat = this.stat)) return;
 //如果opcode为9,则发送pong响应,如果opcode为10则置pingtimes为0
 if (stat.opcode === 9 || stat.opcode === 10) {
 (stat.opcode === 9) ? (this.sendPong()) : (this.pingTimes = 0);
 this.reset();
 return;
 }
 var result;
 if (stat.maskedData) {
 result = new Buffer(data.length-stat.index);
 for (var i = stat.index, j = 0; i < data.length; i++, j++) {
  //对每个字节进行异或运算,masked是4个字节,所以%4,借此循环
  result[j] = data[i] ^ stat.maskedData[j % 4];
 }
 } else {
 result = data.slice(stat.index, data.length);
 }
 this.datas.push(result);
 stat.length -= (data.length - stat.index);
 //当长度为0,说明当前帧为最后帧
 if (stat.length == 0) {
 var buf = Buffer.concat(this.datas, stat.totalLength);
 if (stat.opcode == 8) {
  this.close(buf.toString());
 } else {
  this.emit("message", buf.toString());
 }
 this.reset();
 }
};

完成了客户端发来的数据解析,还需要一个服务端发数据至客户端的方法,也就是按照上面所说的帧定义来组装数据并且发送出去。下面的代码中基本上每一行都有注释,应该还是比较容易理解的。


//数据发送
WebSocket.prototype.send = function (message) {
 if(this.state !== "OPEN") return;
 message = String(message);
 var length = Buffer.byteLength(message);
// 数据的起始位置,如果数据长度16位也无法描述,则用64位,即8字节,如果16位能描述则用2字节,否则用第二个字节描述
 var index = 2 + (length > 65535 ? 8 : (length > 125 ? 2 : 0));
// 定义buffer,长度为描述字节长度 + message长度
 var buffer = new Buffer(index + length);
// 第一个字节,fin位为1,opcode为1
 buffer[0] = 129;
// 因为是由服务端发至客户端,所以无需masked掩码
 if (length > 65535) {
 buffer[1] = 127;
// 长度超过65535的则由8个字节表示,因为4个字节能表达的长度为4294967295,已经完全够用,因此直接将前面4个字节置0
 buffer.writeUInt32BE(0, 2);
 buffer.writeUInt32BE(length, 6);
 } else if (length > 125) {
 buffer[1] = 126;
// 长度超过125的话就由2个字节表示
 buffer.writeUInt16BE(length, 2);
 } else {
 buffer[1] = length;
 }
// 写入正文
 buffer.write(message, index);
 this.socket.write(buffer);
};

最后还要实现一个功能,就是心跳检测:防止服务端长时间不与客户端交互而导致客户端关闭连接,所以每隔十秒都会发送一次ping进行心跳检测


//每隔10秒进行一次心跳检测,若连续发出三次心跳却没收到响应则关闭socket
WebSocket.prototype.checkHeartBeat = function () {
 var that = this;
 setTimeout(function () {
 if (that.state !== "OPEN") return;
 if (that.pingTimes >= 3) {
  that.close("time out");
  return;
 }
 //记录心跳次数
 that.pingTimes++;
 that.sendPing();
 that.checkHeartBeat();
 }, 10000);
};
WebSocket.prototype.sendPing = function () {
 this.socket.write(new Buffer(['0x89', '0x0']))
};
WebSocket.prototype.sendPong = function () {
 this.socket.write(new Buffer(['0x8A', '0x0']))
};

至此,整个websocket的实现就完成了,此demo只是大概实现了一下websocket而已,在安全之类方面肯定还是有很多问题,若是真正生产环境中还是用socket.io这类成熟的插件比较好。不过这还是很值得一学的。

以上内容就是小编给大家分享的浅析nodejs实现Websocket的数据接收与发送的全部内容,希望大家喜欢。

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

浅析nodejs实现Websocket的数据接收与发送

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

浅析nodejs实现Websocket的数据接收与发送

WebSocket是HTML5开始提供的一种浏览器与服务器间进行全双工通讯的网络技术。在WebSocket API中,浏览器和服务器只需要要做一个握手(handshaking)的动作,然后,浏览器和服务器之间就形成了一条快速通道。两者之间就
2022-06-04

C#如何实现套接字发送接收数据

这篇文章主要介绍了C#如何实现套接字发送接收数据,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。具体内容如下服务端namespace TestServer{ public
2023-06-21

php通过curl方式实现发送接收xml数据

通过cURL发送和接收XML数据是一种强大的PHP功能。本文详细介绍了如何使用PHP和cURL库发送和接收XML数据。从初始化cURL会话到执行请求和解析响应,本文涵盖了整个过程,并提供了示例代码和最佳实践。通过遵循本指南,开发人员可以轻松实现XML数据的交换,用于各种应用程序,例如API集成和数据传输。
php通过curl方式实现发送接收xml数据
2024-04-02

解决网络数据只发送不接收的实现方法

  网络数据是现实世界中最常用的数据类型之一。人与人之间的关系、城市之间的道路连接、科研论文之间的引用都组成了网络。树形结构表达了层次结构关系,而不具备层次结构的关系数据,可统称为网络数据。现在小编给大家带来的教程是:解决网络数据只发送不接收的实现方法。  网络的畅通主要就是表现在既有发送包,同时也有接收包,仅仅只有在
解决网络数据只发送不接收的实现方法
2024-04-18

vue项目嵌套iframe怎么实现发送、接收数据

这篇文章主要介绍“vue项目嵌套iframe怎么实现发送、接收数据”,在日常操作中,相信很多人在vue项目嵌套iframe怎么实现发送、接收数据问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”vue项目嵌套if
2023-06-30

golang WebSocket与JSON的结合:实现数据传输和解析

golang WebSocket与JSON的结合:实现数据传输和解析在现代的Web开发中,实时数据传输变得越来越重要。WebSocket是一种用于实现双向通信的协议,与传统的HTTP请求-响应模型不同,WebSocket允许服务器向客户端主
golang WebSocket与JSON的结合:实现数据传输和解析
2023-12-17

简述Android中实现APP文本内容的分享发送与接收方法

谨记(指定选择器Intent.createChooser()) 开始今天的内容前,先闲聊一下: (1)突然有一天头脑风暴,对很多问题有了新的看法和见解,迫不及待的想要分享给大家,文档已经写好了,我需要通过微信或者QQ,短信等社交工具发送给大
2022-06-06

[紫色]利用ASP发送和接收XML数据的处理方法284627实例

因为要做移动梦网WAP的一些接口,所以要用到这种方式,接下来会有ASP.net版本的,这个是ASP版本的,利用了MSXML2.XMLHTTP对像
2023-05-20

[紫色]利用ASP发送和接收XML数据的处理方法284815实例

因为要做移动梦网WAP的一些接口,所以要用到这种方式,接下来会有ASP.net版本的,这个是ASP版本的,利用了MSXML2.XMLHTTP对像
2023-05-21

前端HTTP发POST请求携带参数与后端接口接收参数的实现

近期在学习的时候,碰到一个关于post的小问题,故拿出来分享一下,下面这篇文章主要给大家介绍了关于前端HTTP发POST请求携带参数与后端接口接收参数的相关资料,需要的朋友可以参考下
2022-11-13

利用PHP邮件函数实现邮件发送和接收功能的方法总结

利用PHP邮件函数实现邮件发送和接收功能的方法总结随着互联网的普及,电子邮件成为了人们日常生活中必不可少的沟通工具之一。而在网站开发中,也常常需要实现邮件发送和接收的功能。PHP作为一种常用的服务器端脚本语言,提供了一系列强大的邮件函数,可
利用PHP邮件函数实现邮件发送和接收功能的方法总结
2023-11-20

利用PHP邮件处理函数实现邮件发送和接收功能的方法总结

利用PHP邮件处理函数实现邮件发送和接收功能的方法总结在当今互联网时代,电子邮件已成为了人们日常生活和工作中不可或缺的一部分。而在网站开发中,有时候我们也需要利用PHP来实现邮件的发送和接收功能。PHP提供了很多邮件处理函数,下面将对利用P
利用PHP邮件处理函数实现邮件发送和接收功能的方法总结
2023-11-20

异步协程开发技巧:实现高效的数据抓取与解析

异步协程开发技巧:实现高效的数据抓取与解析,需要具体代码示例随着互联网的迅猛发展,数据变得越来越重要,从中获取和解析数据成为许多应用的核心需求。而在数据抓取和解析过程中,提高效率是开发人员面临的重要挑战之一。为了解决这个问题,我们可以利用异
异步协程开发技巧:实现高效的数据抓取与解析
2023-12-09

利用MySQL开发实现数据同步与复制的项目经验解析

MySQL是目前世界上最流行的关系型数据库之一,广泛应用于各种类型的应用程序中。随着数据量增长和应用程序数量的增加,数据同步和复制的需求也越来越明显。在许多企业和组织中,数据库有时需要在不同地点和系统之间同步,以实现数据一致性。因此,利用M
利用MySQL开发实现数据同步与复制的项目经验解析
2023-11-02

通过MySQL开发实现数据分析与机器学习的项目经验分享

在现代科技时代,数据分析和机器学习技术的应用已经广泛渗透到了各个领域中,成为了许多企业和机构优化业务和提升效率的重要手段。而这些应用的实现离不开高效可靠的数据存储和处理,而MySQL作为一种经典的关系型数据库管理系统,被广泛应用于数据存储和
通过MySQL开发实现数据分析与机器学习的项目经验分享
2023-11-04

通过MySQL开发实现数据可视化与报表分析的项目经验分享

在当今数据大爆炸的时代,数据分析和数据可视化成为了企业决策的重要工具。作为一名开发人员,在MySQL数据库上开发实现数据可视化与报表分析的项目经验,我想和大家分享一下。首先,我想提到的是选择MySQL作为数据库的原因。MySQL是一款开源的
通过MySQL开发实现数据可视化与报表分析的项目经验分享
2023-11-04

编程热搜

目录