创建TCP通信与粘包问题解决

news2025/2/25 21:13:57

创建TCP通信

nodeJS的Net模块实现了底层通信接口

通信过程

  • 创建服务端:接收和回写客户端数据
  • 创建客户端:发送和接收服务端数据
  • 数据传输: 内置服务事件和方法读写数据

通信事件

  • listing事件:调用server.listen方法之后触发
  • connection事件:新的连接建立时触发
  • close事件: 当server关闭时触发
  • error事件: 当错误出现时触发

通信事件&方法

  • data事件:当接收到数据时触发该事件
    • 每当某一端调用write方法来发送数据的时候,那么另一端就可以通过on来监听data事件去消耗数据。其实也就是从可读流里拿数据的操作
  • write方法:在socket上发送数据,默认ut8编码
    • 它和data方法是相对的,net模块所创建的都是基于流的操作,所以他本身就是可读流和可写流的集合。data可以去消费数据,write就可以用来写入数据。
  • end操作:当socket的一端发送FIN包时触发,结束可读端

基于net创建服务端和客户端的TCP通信

自此我们就知道了,在node中通过net模块可以创建一个基于流操作的tcp通信,依据内置的方法可以创建一个服务端和一个客户端然后再去监听具体的事件,当某些事件被触发的时候,我们就可以去利用相应的方法来生产和消费数据。

  • server.js
const net = require('net')

// 创建服务端实例
const server = net.createServer()

const PORT = 1234
const HOST = 'localhost'

server.listen(PORT, HOST)

server.on('listening', () => {
  console.log(`服务端已经开启在 ${HOST}: ${PORT}`)
})

// 接收消息 回写消息
server.on('connection', (socket) => {
  socket.on('data', (chunk) => {
    const msg = chunk.toString()
    console.log(msg)

    // 回数据
    socket.write(Buffer.from('您好' + msg))
  })
})

server.on('close', () => {
  console.log('服务端关闭了')
})

server.on('error', (err) => {
  if (err.code == 'EADDRINUSE') {
    console.log('地址正在被使用')
  }else{
    console.log(err)
  }
})
  • client.js
const net = require('net')

const client = net.createConnection({
  port: 1234, 
  host: '127.0.0.1'
})

client.on('connect', () => {
  client.write('小星星')
})

client.on('data', (chunk) => {
  console.log(chunk.toString())
})

client.on('error', (err) => {
  console.log(err)
})

client.on('close', () => {
  console.log('客户端断开连接')
})

TCP数据粘包

通信包含数据发送端和接收端.发送端先将数据放在一个缓存区,累积完数据之后再统一发送.接收端同样将接收的数据先放到缓存中,然后再执行数据的获取和使用。 这样的好处是能减少IO操作带来的性能消耗,但是对于数据的使用就会产生粘包的问题。比如客户端向服务端发送连续的数据:
在这里插入图片描述
服务端接收之后会把他们放在一起再返回给客户端。
在这里插入图片描述
最后客户端接收到的数据就是下面这样了,后面的小星星就被粘在一起了。
在这里插入图片描述
主要的解决方法就是加入时间间隔,比如每过1s发送一条数据。这样虽然能解决问题,但是如果在传输的过程中有很多的数据,而且还涉及网络贷款与用户的等待时长都是有等待上限的,不应该这样去做处理。那么现实当中是如何处理的呢?答案是通过拆包与封包
在这里插入图片描述
还有一个问题就是数据是放在缓存中的,那么什么时候发送数据呢?这个就取决于TCP的拥塞机制了。而TCP拥塞机制决定发送时机。

数据的封包与拆包解决粘包问题

它的核心思想就是按照提前规定好的规则去打包,将来接收到数据的时候再使用特点规则去拆包即可。我们这里使用的是长度编码的方式约定通信双方的数据传输方式。

首先将被传输的数据分为定长消息头和不定长的消息体两个部分。其中头部使用header来表示,消息体使用body来表示
在这里插入图片描述
同时再讲头部分为序列号和消息长度两个部分,序列号去区分不同的消息包,消息长度确定一次取多少长度的内容。在这里插入图片描述

数据传输的过程

  • 进行数据编码,将数据编码成二进制。然后将这条数据按上述规则封装成一个包。
  • 而服务端拿到这个包之后就按照约定好的长度拆解数据,获取指定长度的内容。然后把二进制解码成字符串,这边就要使用buffer了,这边涉及通过具体位置进行读写的两个操作,如下

Buffer数据读写

  • writeInt16BE:将value从指定位置
  • readInt16BE:从指定位置开始取数据
    这里32BE的就先不说了

封包与拆包

封包与拆包类实现

myTransform.js
class MyTransformCode{
  constructor() {
    this.packageHeaderLen = 4 // header的空间大小(4个字节)
    this.serialNum = 0 // header的第一部分:描述包的编号
    this.serialLen = 2 // header的第二部分:制定解码时获取包的长度
  }

  // 封包
  encode(data, serialNum) {
    const body = Buffer.from(data)

    // 01 先按照指定的长度来申请一片内存空间做为 header 来使用
    const headerBuf = Buffer.alloc(this.packageHeaderLen)

    // 02 写入编号
    headerBuf.writeInt16BE(serialNum || this.serialNum)
    // 03 跳过序列号,写入解码时获取包的长度
    headerBuf.writeInt16BE(body.length, this.serialLen)

    if (serialNum == undefined) {
      // 没有传编号,就自增
      this.serialNum++
    }
    // 合并消息体 封包完成
    return Buffer.concat([headerBuf, body])
  }

  // 拆包
  decode(buffer) {
    // 拆出头信息
    const headerBuf = buffer.slice(0, this.packageHeaderLen)
    // 拆出体信息
    const bodyBuf = buffer.slice(this.packageHeaderLen)

    return {
      serialNum: headerBuf.readInt16BE(),
      bodyLength: headerBuf.readInt16BE(this.serialLen),// 跳过编号位置
      body: bodyBuf.toString()
    }
  }

  // 获取包长度的方法
  getPackageLen(buffer) {
    // 长度小于头部长度
    if (buffer.length < this.packageHeaderLen) {
      return 0
    } else {
      // 返回实际长度 = 消息头长度 + 消息体长度
      return this.packageHeaderLen + buffer.readInt16BE(this.serialLen)
    }
  }
}

module.exports = MyTransformCode

封包与拆包类使用

server.js
const net = require('net')
const MyTransform = require('./myTransform.js')

const server = net.createServer()

let overageBuffer = null
let ts = new MyTransform()

server.listen('1234', 'localhost')

server.on('listening', () => {
  console.log('服务端运行在 localhost:1234')
})

server.on('connection', (socket) => {
  socket.on('data', (chunk) => {
    if (overageBuffer) {
      chunk = Buffer.concat([overageBuffer, chunk])
    }
    let packageLen = 0
    while(packageLen = ts.getPackageLen(chunk)) {
      const packageCon = chunk.slice(0, packageLen)
      chunk = chunk.slice(packageLen)

      const ret = ts.decode(packageCon)
      console.log(ret)

      socket.write(ts.encode(ret.body, ret.serialNum))
    }
    overageBuffer = chunk
  })
})
client.js
const net = require('net')
const MyTransform = require('./myTransform.js')

let overageBuffer = null 
let ts = new MyTransform()

const client = net.createConnection({
  host: 'localhost', 
  port: 1234
})

client.write(ts.encode('小星星1'))
client.write(ts.encode('小星星2'))
client.write(ts.encode('小星星3'))
client.write(ts.encode('小星星4'))
client.write(ts.encode('小星星5'))

client.on('data', (chunk) => {
  if (overageBuffer) {
    chunk = Buffer.concat([overageBuffer, chunk])
  }
  let packageLen = 0
  while(packageLen = ts.getPackageLen(chunk)) {
    const packageCon = chunk.slice(0, packageLen)
    chunk = chunk.slice(packageLen)

    const ret = ts.decode(packageCon)
    console.log(ret)
  }
  overageBuffer = chunk
})

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/39578.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

介绍 10 个有用的 Flutter 软件包

介绍 10 个有用的 Flutter 软件包 原文 https://genotechies.medium.com/introducing-10-useful-flutter-packages-1252c4b75fa7 前言 FLutter 软件包使您的开发简单快速。然而&#xff0c;有利有弊。有时候&#xff0c;如果从头开始开发这个特性将是有益的&#xff0c;因为可定…

Hive日分区表如何快速导入到StarRocks

1、背景 业务现状&#xff1a;集团使用FineBI做数据呈现及报表分析工具&#xff0c;经过近两年的BI建设&#xff0c;供应链域及营销域的BI建设已初具规模并体系化。数仓规模60TB&#xff0c;FineBI数据集约8000个&#xff0c;BI挂出报表数约1600个&#xff0c;报表月增幅在40左…

非凡社群管理之社群管理有什么内容

社群作为一个非常重要的私域流量池&#xff0c;它本身就是一个提升用户价值的利器。但如果管理不好社群&#xff0c;那么也是无济于事的。 社群小助手提示&#xff1a;高效管理社群&#xff0c;以下这五个方面要做好。 一&#xff0c;社群为用户解决问题&#xff0c;让群成员都…

iwebsec靶场 SQL注入漏洞通关笔记6- 宽字节注入

系列文章目录 iwebsec靶场 SQL注入漏洞通关笔记1- 数字型注入_mooyuan的博客-CSDN博客 iwebsec靶场 SQL注入漏洞通关笔记2- 字符型注入&#xff08;宽字节注入&#xff09;_mooyuan的博客-CSDN博客 iwebsec靶场 SQL注入漏洞通关笔记3- bool注入&#xff08;布尔型盲注&#…

【ML特征工程】第 7 章 :通过K-Means 模型堆叠进行非线性特征化

&#x1f50e;大家好&#xff0c;我是Sonhhxg_柒&#xff0c;希望你看完之后&#xff0c;能对你有所帮助&#xff0c;不足请指正&#xff01;共同学习交流&#x1f50e; &#x1f4dd;个人主页&#xff0d;Sonhhxg_柒的博客_CSDN博客 &#x1f4c3; &#x1f381;欢迎各位→点赞…

计算机视觉之目标检测(object detection)《1》

在计算机视觉领域&#xff0c;除了识别图像并分类之外&#xff0c;我们很多时候想关注图像里面一些感兴趣的目标&#xff0c;比如视频监控中寻找一个或多个嫌疑犯&#xff1b;无人驾驶需要识别车辆、行人、红绿灯、路障等等&#xff0c;都是需要去及时掌握画面中的不同目标。我…

古瑞瓦特能源通过聆讯:上半年营收23亿 IDG投资9亿持股6.5%

雷递网 雷建平 11月26日古瑞瓦特能源&#xff08;简称&#xff1a;“Growatt Technology”&#xff09;日前递交招股书&#xff0c;准备在香港上市。上半年营收23.45亿古瑞瓦特能源于2011年在深圳成立&#xff0c;是一家分布式能源解决方案提供商&#xff0c;专注于可持续能源发…

Crack:GoJS 2.2.18 -2022-09-08 update

使用 JavaScript 和 TypeScript 为 Web 构建图表 流程图 构建交互式流程图或流程图。让您的用户使用 JSON 模型输出构建、修改和保存图表。状态图 可视化状态图和其他行为图。创建具有实时更新的图表以监控状态&#xff0c;或创建交互式图表以进行规划。桑基图 GoJS 允许对链接…

史上最全MATLAB误差分析工具箱,不看别后悔 【矢量化代码、效率嘎嘎快、支持计算50种指标】

在拟合、插值、模拟预测等计算中&#xff0c;往往需要通过不同指标参数来分析实际值与计算值之间差异依次衡量相关方法的可行性。常用的表征指标有残差平方和(SSE)、均方差(MSE)、均方根差(RMSE)、平均绝对误差(MAE)和决定系数R方(R-Squared)等等。 考虑到误差分析在实际应用中…

Kafka部署安装及简单使用

一、环境准备 1、jdk 8 2、zookeeper 3、kafka 说明&#xff1a;在kafka较新版本中已经集成了zookeeper&#xff0c;所以不用单独安装zookeeper&#xff0c;只需要在kafka文件目录中启动zookeeper即可 二、下载地址 Apache Kafka 三、部署 1、启动zookeeper -- 启动 .…

CSDN第11次竞赛题解与总结

CSDN第11次竞赛题解与总结前言建议题解T1圆小艺扩展完整代码T2K皇把妹完整代码T3筛选宝物完整代码T4圆桌完整代码总结前言 2022/11/27 CSDN第11次竞赛 由「壹合原码 & CSDN」联合主办 本次奖励还是不错的 (毕竟有赞助商)&#xff0c;前三十名都有奖励&#xff0c;连以前第…

跑步10年回望

回顾跑步这10年有点遗憾&#xff0c;最终还是决定放弃参加2022年厦马&#xff0c;因为要求更早到厦门&#xff0c;也担心回福州后影响小朋友上课&#xff0c;权衡之下还是决定申请退赛。本想在这次活动上实现全马破4的目标&#xff0c;却只能晒个退赛截图。。。今年是厦马20年&…

【敲级实用】:某小伙写了一个的办公脚本后~变精神了~

文章目录&#x1f4ef;小哔哔✏️注册有道智云✏️咋滴调用&#xff1f;✏️使用前的小操作✏️源代码专栏Python零基础入门篇&#x1f525;Python网络蜘蛛&#x1f525;Python数据分析Django基础入门宝典&#x1f525;小玩意儿&#x1f525;Web前端学习tkinter学习笔记Excel自…

基于储能电站服务的冷热电多微网系统双层优化配置(Matlab代码实现)

&#x1f468;‍&#x1f393;个人主页&#xff1a;研学社的博客 &#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜…

ETCD快速入门-01 ETCD概述

1.ETCD概述 1.1 ETCD概述 etcd是一个高可用的分布式的键值对存储系统&#xff0c;常用做配置共享和服务发现。由CoreOS公司发起的一个开源项目&#xff0c;受到ZooKeeper与doozer启发而催生的项目&#xff0c;名称etcd源自两个想法&#xff0c;即Linux的/etc文件夹和d分布式系…

一篇快速搞懂python模块、包和库

个人主页&#xff1a;天寒雨落的博客_CSDN博客-初学者入门C语言,python,数据库领域博主 &#x1f4ac; 热门专栏&#xff1a;python_天寒雨落的博客-CSDN博客 ​每日赠语&#xff1a;没有窘迫的失败&#xff0c;就不会有自豪的成功&#xff1b;失败不可怕&#xff0c;只要能从失…

用DIV+CSS技术设计的凤阳旅游网站(web前端网页制作课作业)HTML+CSS+JavaScript

&#x1f468;‍&#x1f393;学生HTML静态网页基础水平制作&#x1f469;‍&#x1f393;&#xff0c;页面排版干净简洁。使用HTMLCSS页面布局设计,web大学生网页设计作业源码&#xff0c;这是一个不错的旅游网页制作&#xff0c;画面精明&#xff0c;排版整洁&#xff0c;内容…

Android App开发语音处理之系统自带的语音引擎、文字转语音、语音识别的讲解及实战(超详细 附源码)

需要源码请点赞关注收藏后评论区留下QQ~~~ 一、系统自带的语音引擎 语音播报的本质是将书面文字转换成自然语言的音频流&#xff0c;这个转换操作被称作语音合成&#xff0c;又称TTS&#xff08;从文本到语音&#xff09;在转换过程中&#xff0c;为了避免机械合成的呆板和停顿…

一款客服系统有哪些必备的功能模块?

为了提升客户服务质量&#xff0c;和客户更好地进行沟通&#xff0c;越来越多的企业配置了客服系统。那一款优秀的客服系统需要配置哪些功能模块呢&#xff1f; 1、支持多渠道接入 新媒体的快速发展使得企业有机会通过更多的渠道和客户进行联系&#xff0c;比如公众号、微博、…

java环境安装与配置

这篇文章只是为了以后我配置环境方便而写 1&#xff0c;点击网址&#xff0c;进入Oracle官网 然后参照Java JDK下载安装及环境配置超详细图文教程 2&#xff0c;安装之后如果目录里没有jre文件夹 参考Jdk中没有jre文件夹怎么办&#xff1f; ①简单点就是&#xff0c;管理员模式…