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

Nodejs中Tcp封包和解包的示例分析

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

北京

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

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

看不清楚,换张图片

免费获取短信验证码

Nodejs中Tcp封包和解包的示例分析

这篇文章给大家分享的是有关Nodejs中Tcp封包和解包的示例分析的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。

1、粘包问题解决方案及对比

很简单,既然消息没有边界,那我们在消息往下传之前给它加一个边界识别就好了。

  • 发送固定长度的消息

  • 使用特殊标记来区分消息间隔

  • 把消息的尺寸与消息一块发送

第一种方案不够灵活;第二种有风险,如果数据内刚好有该特殊字符会出问题;第三种方案虽然要增加对消息头的解析,不过相对而言还是要安全一些。

2、分包与拆包

既然使用第三种方案,就必然涉及到封包和拆包的问题。

首先肯定需要定义数据包的结构,这类似Http包一样,有包头和包体。包头其实上是个大小固定的结构体,其中有个结构体成员变量表示包体的长度,其他的结构体成员可根据需要自己定义。根据包头长度固定以及包头中含有包体长度的变量就能正确的拆分出一个完整的数据包。包体则存放数据内容。

Nodejs中Tcp封包和解包的示例分析

在发送端,需要进行封包。封包就是给一段数据加上包头,这样一来数据包就分为包头和包体两部分内容了。

在接受端,则需要进行拆包。主要流程如下:

1. 为每一个连接动态分配一个缓冲区,同时把此缓冲区和SOCKET关联.
2. 当接收到数据时首先把此段数据存放在缓冲区中.
3. 判断缓存区中的数据长度是否够一个包头的长度,如不够,则不进行拆包操作.
4. 根据包头数据解析出里面代表包体长度的变量.
5. 判断缓存区中除包头外的数据长度是否够一个包体的长度,如不够,则不进行拆包操作.
6. 取出整个数据包.这里的"取"的意思是不光从缓冲区中拷贝出数据包,而且要把此数据包从缓存区中删除掉.删除的办法就是把此包后面的数据移动到缓冲区的起始地址.

其中对于缓冲区的设计,主要由俩种:

1. 采用动态变化的缓冲区暂存,根据数据大小调整缓冲区大小。这个方案有个缺点,为了避免缓冲区不断增长,每次解析出一个完整包后需要将缓冲区残留的数据拷贝到缓冲区首部,这增加了系统负载。
2. 采用环形缓冲区,定义两个指针,分别指向有效数据的头和尾.在存放数据和删除数据时只是进行头尾指针的移动

Nodejs中Tcp封包和解包的示例分析 Nodejs中Tcp封包和解包的示例分析

3、网络字节序和本机字节序

定义了消息结构之后,发送端和接收端还需要统一字节序。我们知道,不同机器的本机字节序不同,绝大多数X86机器都是小端字节序,然后还是由少数机器是大端存储的。因此在数据流进行传输时,必须先统一字节序。一般约定在传输时采用网络字节序(大端),统一用unicode编码。

Nodejs中Tcp封包和解包的示例分析 

4、代码实现

了解以上知识之后,我们现在之后要做什么了。发送端按定义的协议规则封包,接受端把接收到的buffer放入缓冲区,当缓冲区内有完整包时开始拆包。封包拆包过程需要注意,读写超过一个字节的数据时需要按大端字节序读取。下面看node的代码实现(只提供核心实现片段):

1)发送端封包:

let head = new Buffer(4);
let jsonStr = JSON.stringify(json);
let body = new Buffer(jsonStr);
//超过一字节的大端写入
head.writeInt32BE(body.byteLength, 0);
let buffer = Buffer.concat([head, body]);

2)接收端收到buffer入缓冲区:

let dataReadStart = 0; //新数据的起始位置
let dataLength = buffer.length; // 要拷贝数据的长度
let availableLen = _bufferLength - _dataLen; // 缓冲区剩余可用空间

// buffer剩余空间不足够存储本次数据
if (availableLen < dataLength) {
 let newLength = Math.ceil((_dataLen + dataLength) / _bufferLength) * _bufferLength;
 let _tempBuffer = Buffer.alloc(newLength);
 
 // 将旧数据复制到新buffer并且修正相关参数
 if (_writePointer < _readPointer) { // 数据存储在旧buffer的尾部+头部的顺序
  let dataTailLen = _bufferLength - _readPointer;
  _buffer.copy(_tempBuffer, 0, _readPointer, _readPointer + dataTailLen);
  _buffer.copy(_tempBuffer, dataTailLen, 0, _writePointer);
 } else { // 数据是按照顺序进行的完整存储
  _buffer.copy(_tempBuffer, 0, _readPointer, _writePointer);
 }
 _bufferLength = newLength;
 _buffer = _tempBuffer;
 _tempBuffer = null;
 _readPointer = 0;
 _writePointer = _dataLen;

 //存储新到来的buffer
 buffer.copy(_buffer, _writePointer, dataReadStart, dataReadStart + dataLength);
 _dataLen += dataLength;
 _writePointer += dataLength;

} else if (_writePointer + dataLength > _bufferLength) {
// 空间够用情况下,但是数据会冲破缓冲区尾部,部分存到缓冲区旧数据后,一部分存到缓冲区开始位置
 // 缓冲区尾部剩余空间的长度
 let bufferTailLength = _bufferLength - _writePointer;

 // 数据尾部位置
 let dataEndPosition = dataReadStart + bufferTailLength;
 buffer.copy(_buffer, _writePointer, dataReadStart, dataEndPosition);

 // data剩余未拷贝进缓存的长度
 let restDataLen = dataLength - bufferTailLength;
 buffer.copy(_buffer, 0, dataEndPosition, dataLength);

 _dataLen = _dataLen + dataLength;
 _writePointer = restDataLen

} else { // 剩余空间足够存储数据,直接拷贝数据到缓冲区
 buffer.copy(_buffer, _writePointer, dataReadStart, dataReadStart + dataLength);
 _dataLen = _dataLen + dataLength;
 _writePointer = _writePointer + dataLength
}

3)取出缓冲区所有完整数据包(收到的buffer入缓冲区后)

let _dataHeadLen = 4;
timer && clearInterval(timer);
timer = setInterval(()=>{
 // 缓冲区数据不够解析出包头
 if (_dataLen < _dataHeadLen) {
  console.log('数据长度小于包头规定长度,等待数据......')
  clearInterval(timer);
 }
 // 解析包头长度
 // 尾部最后剩余可读字节长度
 let restDataLen = _bufferLength - _readPointer;
 let dataLen = 0;
 let headBuffer = Buffer.alloc(_dataHeadLen);
 // 数据包为分段存储,不能直接解析出包头,先拼接
 if (restDataLen < _dataHeadLen) {
  // 取出第一部分头部字节
  _buffer.copy(headBuffer, 0, _readPointer, _bufferLength)
  // 取出第二部分头部字节
  let unReadHeadLen = _dataHeadLen - restDataLen;
  _buffer.copy(headBuffer, restDataLen, 0, unReadHeadLen)
  dataLen = headBuffer.readUInt32BE(0);

 } else {
  _buffer.copy(headBuffer, 0, _readPointer, _readPointer + _dataHeadLen);
  dataLen = headBuffer.readUInt32BE(0);;
 }

 // 数据长度不够读取,直接返回
 if (_dataLen - _dataHeadLen < dataLen) {
  log.info("缓冲区已有body数据长度小于包头定义body的长度,等待数据......")
  clearInterval(timer);

 } else { // 数据够读,读取数据包 
  let package = Buffer.alloc(dataLen);
  // 数据是分段存储,需要分两次读取
  if (_bufferLength - _readPointer < dataLen) {
   let firstPartLen = _bufferLength - _readPointer;
   // 读取第一部分,直接到字符尾部的数据
   _buffer.copy(package, 0, _readPointer, firstPartLen + _readPointer);
   // 读取第二部分,存储在开头的数据
   let secondPartLen = dataLen - firstPartLen;
   _buffer.copy(package, firstPartLen, 0, secondPartLen);
   _readPointer = secondPartLen; //更新可读起点

  } else { // 直接读取数据
   _buffer.copy(package, 0, _readPointer, _readPointer + dataLen);
   _readPointer += dataLen; //更新可读起点
  }

  _dataLen -= readData.length; //更新数据长度
  // 已经读取完所有数据
  if (_readPointer === _writePointer) {
   clearInterval(timer)
  }

  //开始解包
  callback(package);
   
 }
}, 50);

4)拆包得到数据

let headBytes = 4;
let head = new Buffer(headBytes);
buffer.copy(head, 0, 0, headBytes);
let dataLen = head.readUInt32BE();
const body = new Buffer(dataLen);
buffer.copy(body, 0, headBytes, headBytes + dataLen)

let content = null;
try {
 const str = body.toString('utf-8');
 if(str === ''){
  content = null;
 }else{
  content = JSON.parse(body);
 }
} catch (e) {
 log.error('head指定body长度有问题')
}
//传递给业务层
callback(content);

感谢各位的阅读!关于“Nodejs中Tcp封包和解包的示例分析”这篇文章就分享到这里了,希望以上内容可以对大家有一定的帮助,让大家可以学到更多知识,如果觉得文章不错,可以把它分享出去让更多的人看到吧!

免责声明:

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

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

Nodejs中Tcp封包和解包的示例分析

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

下载Word文档

猜你喜欢

实用Nodejs npm包:koa-csrf的示例分析

这篇文章将为大家详细讲解有关实用Nodejs npm包:koa-csrf的示例分析,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。JS是什么JS是JavaScript的简称,它是一种直译式的脚本语言,其解释
2023-06-14

js中闭包的示例分析

这篇文章主要介绍了js中闭包的示例分析,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。1、说明闭包是具有很多变量和这些变量的环境的表现式(通常是函数),这些变量也是该表现式的一
2023-06-14

nodejs中cookie和session的示例分析

小编给大家分享一下nodejs中cookie和session的示例分析,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!用惯了框架中的插件,最近在重温node基础模块
2023-06-07

Java和IDEA中文件打包的示例分析

这篇文章主要介绍Java和IDEA中文件打包的示例分析,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!问题:想在IDEA中引用相对路径,但是找不到文件。项目目录结构当前项目的路径为:D:\source\java\tes
2023-06-20

react中useEffect闭包的示例分析

这篇文章主要介绍react中useEffect闭包的示例分析,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!问题代码看一段因为useEffect导致的闭包问题代码const btn = useRef();const [
2023-06-15

Java IO中包装流的示例分析

这篇文章主要介绍了Java IO中包装流的示例分析,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。根据功能分为节点流和包装流(处理流)节点流:可以从或向一个特定的地方(节点)读
2023-06-26

Python中包与模块的示例分析

这篇文章主要为大家展示了“Python中包与模块的示例分析”,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下“Python中包与模块的示例分析”这篇文章吧。什么是 Python 的包与模块包的定义:简
2023-06-29

Java Object类和包装类的示例分析

这篇文章给大家分享的是有关Java Object类和包装类的示例分析的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。查看源代码方法在IDEA中双击 shift 键,可以搜索相关类名,查看源代码点击Structure
2023-06-29

php中包含字符的示例分析

这篇文章将为大家详细讲解有关php中包含字符的示例分析,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。php是什么语言php,一个嵌套的缩写名称,是英文超级文本预处理语言(PHP:Hypertext Pre
2023-06-14

linux中tcpdump抓取HTTP包的示例分析

这篇文章将为大家详细讲解有关linux中tcpdump抓取HTTP包的示例分析,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。tcpdumptcpdump是linux系统自带的抓包工具,主要通过命令行的方式
2023-06-10

PHP中拆红包算法的示例分析

这篇文章给大家分享的是有关PHP中拆红包算法的示例分析的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。PHP拆红包算法代码如下:/** * 拆分红包 * @param SendRedPackageR
2023-06-15

编程热搜

目录