#Stickpackage
StickPackage,NodeJs中TCP粘包、分包解决方案!
持续更新,源码地址,喜欢的话请点star,想订阅点watch
目录
- 安装
- 配置介绍
- API
- 更新记录
- 使用方法
- 案例演示
安装
npm i stickpackage
配置介绍
- [x] 提供对TCP粘包处理的解决方案
- [x] 默认缓冲512个字节,当接收数据超过512字节,自动以512倍数扩大缓冲空间
- [x] 本默认采用包头两个字节表示包长度
- [x] 默认采用大端接模式接收数据
- [x] 可以配置大端小端读取
- [x] 可以配置自定义包头长度
- [x] 支持自动拆解包
API
- stick(bufferSize) => 直接处理字节类型的包
bufferSize:设置stick处理粘包的缓存空间
- stick.setReadIntBE(type) => 设置为大端模式<依据数据包最大值选择合适type>
setReadIntBE(type) type:16 包头长度为2,short类型
setReadIntBE(type) type:32 包头长度为4,int类型
- stick.setReadIntLE => 设置为小端模式<依据数据包最大值选择合适type>
setReadIntLE(type) type:16 包头长度为2,short类型
setReadIntLE(type) type:32 包头长度为4,int类型
- stick.putData(buffer) => 向stick中推送需要处理粘包的字节流
- stick.onData(buffer) => 监听stick解包好的一个个完整消息(包头+包体),用户自己的数据存储在包体中,如果不想处理包头用msgCenter已经封装好
- msgCenter(options) => 可直接发送字符串消息,基于stick封装,屏蔽stick层需要自己组装包头和拆包头的步骤
options.bufferSize: 设置用户处理粘包的缓存空间
options.type:设置包头为16位或者32位模式(16|32)
options.bigEndian: 设置大端、小端字节流模式,默认为打断模式,为false时为小端模式(true|false)
- msgCenter.putMsg(msg) => 向消息中心推送字符串消息
- msgCenter.publish(msg) => 发布一个消息,返回一个被打包好的buffer(包头+包体),用户clent发包时使用
msgCenter.publish('123')
=> <Buffer 00 03 31 32 33> // 00 03 包长度 31 32 33 字符串123的ascii码
- msgCenter.onMsgRecv(msgHandleFun) => 处理经过粘包处理后的消息
msgHandleFun:业务上处理消息的函数
msgCenter.onMsgRecv(msg => {
console.log(`recv data: ` + msg.toString())
...do something
})
更新记录:
- 设置大端,小端接收,添加setReadIntBE,添加setReadIntLE方法
- 支持直接发送字符串消息,自动化组装包头
使用方法
- 服务端处理粘包
// 默认client.js 采用 msgCenter.publish('...') 向服务端发消息
// 以下是服务端收到消息后,进行粘包处理
const MsgCenter = require('stickpackage').msgCenter
const msgCenter = new MsgCenter()
// server 监听分包后的消息
msgCenter.onMsgRecv(data => {
console.log(`recv data: ` + data.toString())
})
// 把 tcp server 监听到的字节流,put到msgCenter中
msgCenter.putData(Buffer.from([0x00, 0x02, 0x31, 0x32, 0x00, 0x04, 0x31, 0x32, 0x33, 0x34]))
//=> recv data: 12
//=> recv data: 1234
- 发送二进制数据
// 默认client.js 采用 stick 配置的组包式向服务器发送消息
// 以下是服务端收到消息后,进行粘包处理
const Stick = require('stickpackage').stick;
const stick = new Stick(1024).setReadIntBE('16')
/*
* 包含两个数据包,10个字节,包头为short,两个字节:[0x00, 0x02],[ 0x00, 0x04]
* 数据包1:[0x00, 0x02, 0x66, 0x66]
* 数据包2:[0x00, 0x04, 0x88, 0x02, 0x11, 0x11]
*/
const data = Buffer.from([0x00, 0x02, 0x66, 0x66, 0x00, 0x04, 0x88, 0x02, 0x11, 0x11]);
/* 构造两个buffer
* data2_1包含: 第一个数据包的全部数据,第二个数据包的部分数据
* data2_2包含: 第二个数据包的剩余数据
*/
const data2_1 = Buffer.from([0x00, 0x00, 0x00, 0x02, 0x66, 0x66, 0x00, 0x04, 0x88, 0x02, 0x11]);
const data2_2 = Buffer.from([0x11]);
// 设置收到完整数据触发器
stick.onData(function (data) {
console.log('receive data,length:' + data.length);
console.log(data)
});
stick.putData(data);
stick.putData(data2_1);
stick.putData(data2_2);
// 运行结果:
// receive data,length:4 <Buffer 00 02 66 66>
// receive data,length:6 <Buffer 00 04 88 02 11 11>
// receive data,length:2 <Buffer 00 00> receive data, length:4 < Buffer 00 02 66 66> receive data, length:6< Buffer 00 04 88 02 11 11>
案例演示
- tcp client和tcp server 之间通过stick进行粘包处理通信,详细内容见example文件夹
- [tcp-msg]本demo主要演示TCP中处理粘包的方法,不需要自己组装包头,直接发送和接收文本消息,组包解包操作本类库已经封装在底层
// Client.js
const net = require('net')
const stick = require('../../index')
const msgCenter = new stick.msgCenter()
const client = net.createConnection({ port: 8080, host: '127.0.0.1' }, function () {
const msgBuffer = msgCenter.publish('username=123&password=1234567,qwe')
client.write(msgBuffer)
})
client.on('data', function (data) {
console.log(data.toString())
})
client.on('end', function () {
console.log('disconnect from server')
})
// Server.js
const net = require('net')
const stick = require('../../index')
const tcp_server = net.createServer(function (socket) {
const msgCenter = new stick.msgCenter()
socket.on('data', function (data) {
msgCenter.putData(data)
})
msgCenter.onMsgRecv(function (data) {
console.log('recv data: ' + data.toString())
})
socket.on('close', function (error) {
console.log('client disconnected')
})
socket.on('error', function (error) {
console.log(`error:客户端异常断开: ${error}`)
})
})
tcp_server.on('error', function (err) {
throw err
})
tcp_server.listen(8080, function () {
console.log('tcp_server listening on 8080')
})
- [tcp-buffer]本demo主要演示TCP中直接处理字节流粘包,展示出如何自己组装包头包体和解包,如不向自己进行组装包头解包操作,请看demo tcp-msg
// Clinet.js
const net = require('net')
const client = net.createConnection({ port: 8080, host: '127.0.0.1' }, function () {
const body = Buffer.from('username=123&password=1234567,qwe')
// 写入包头
const headBuf = new Buffer(4)
headBuf.writeUInt32BE(body.byteLength, 0)
console.log('data length: ' + headBuf.readInt32BE())
// 发送包头
client.write(headBuf)
// 发送包内容
client.write(body)
console.log('data body: ' + body.toString())
})
client.on('data', function (data) {
console.log(data.toString())
})
client.on('end', function () {
console.log('disconnect from server')
})
// Server.js
const net = require('net')
const stick_package = require('../../index').stick
const tcp_server = net.createServer(function (socket) {
socket.stick = new stick_package(1024).setReadIntBE('32')
socket.on('data', function (data) {
socket.stick.putData(data)
})
socket.stick.onData(function (data) {
// 解析包头长度
const head = new Buffer(4)
data.copy(head, 0, 0, 4)
// 解析数据包内容
const body = new Buffer(head.readInt32BE())
data.copy(body, 0, 4, head.readInt32BE())
console.log('data length: ' + head.readInt32BE())
console.log('body content: ' + body.toString())
})
socket.on('close', function (error) {
console.log('client disconnected')
})
socket.on('error', function (error) {
console.log(`error:客户端异常断开: ${error}`)
})
})
tcp_server.on('error', function (err) {
throw err
})
tcp_server.listen(8080, function () {
console.log('tcp_server listening on 8080')
})
源码地址,喜欢的话请点star,想订阅点watch
什么破玩意儿!。。。还解决方案??通信另一边是别人写好的咋办??两边都是自己写的话,用的着你这个吗??
什么叫“粘包”?TCP本来就是流式传输,要确定消息边界,无非就是 定长(首部带长度) 和 定位(规定分隔符) 这两种基本模式,地球人都知道的东西就不要在这忽悠人了。。。
@myy 说的你好牛逼一样
@lvgithub 我说的话确实不好听。。。不过你想想看,如果你已经上了初中,还会去天天背汉语拼音字母表么?
@myy 我估计你连项目都没看,就来乱喷吧。
- 处理粘包,简单的说是处理消息边界,往细节说,如果通过循环队列利用缓存空间,节省内存,高效率的处理效率等等。
- 并不是很多人都有处理过的经验,对你这种牛人来说这容易,但不代表就没人需要,而且封装后,可以帮助很多人节省时间,也可以提供给大家参考,你觉得有毛病?别以为自己很牛逼就看不起所有人。
- 如果你很有水平,请和大家分享如果写出高质量的代码,或者说出项目的不足,而不要只是草草的几句话去给别人下结论,对你和对他人都不好。
- 你说的这话 “TCP本来就是流式传输,要确定消息边界,无非就是 定长(首部带长度) 和 定位(规定分隔符) 这两种基本模式” 你话中的这些理论知识难道就是地球人都不知道,需要你来评论里介绍吗,你认为就你这个评价就很有价值是吧?
- 我看了你对论坛里别人问题的答复,都是这一副你牛逼的样子,假设你很牛逼,就拿出真本事出来,不要通过贬低别人来满足你自己的虚荣心。
- 如果我分享的知识是破玩意,那我请问你的评论就不是破玩意了吗?
最近也在做这个东西,研究tcp链接。
请教下各位,如何确定一次写流已经完毕了。
比如socket.write(“超大的数据”), 如果数据很大,服务端会分段读取,怎么确保哪次是读取结束。
那么到真的要客户端发送数据介结束的标志吗
@axetroy 超大的数据具体是多大?
首先,我不觉得我有多牛逼,起码我懂一些常识。也没有谁规定非要自己分享什么才可以批评别人。 其次,我在本站的回答都不是随便灌水的,基本都切中要害, 并没有贬低谁。 最后,你写的东西我看了,是真不咋的。。。
对于最大只有64K的数据,根本用不着循环队列什么的,简单问题复杂化,反复的分配复制分配复制内存,反而降低效率。况且nodejs socket内部本来就有缓冲区,可以按需读取,根本用不着重复造轮子。。不多说了,认真研究下nodejs,别老闭门造车了。
@lvgithub 比如有2个G的流,分割N次接收
@myy 那你给指导下楼下,他的这个问题,用nodejs socket 怎么处理
- “socket内部本来就有缓冲区,可以按需读取,根本用不着重复造轮子。” 写出具体的解决方案,别每次都说些空旷的话,这些知识点,看文档的人都知道,主要是要结合实际应用。
- “反复的分配复制内存,反而降低效率”,分配复制内存什么意思?是分配还是复制
- 其次问你利用同一块内存,循环使用,怎么就比每次都去申请新的内存慢了?
@axetroy 我说的很清楚了,tcp本身没有任何方法可以确定“你所需要的数据边界”,你必须自己设计一种识别方法,所有基于tcp的应用层协议都是这么干的。
我使用了下 不知所谓 完全不知道啥玩意
@xivistudios 你是需要解决什么问题
@lvgithub 倒不是说这东西不可用,首先,确实有效,不过功能确实是重复了,还有接口设计,我完全没看懂接口设计是什么跟什么,还有函数语义,而且竟然,我跟demo一样写,反倒不行,我自己看了下你的代码,自己写的,反倒可以了,你说扯淡不,算了,也不是贬低你,那时候搞了半天,被你这东西搞得头昏脑胀,debug了半天,还以为是我写错了,所以,我干脆自己写一个了
平常都不会自定义这么底层的数据。没遇到这种场景。
我是支持楼主的分享精神,批评者倒是写个牛逼的来教育我们啊,要不上点干货或改进或技巧也行,至少光BB真的不能让人服贴
来自酷炫的 CNodeMD
@MiYogurt 看行业了,在做智能硬件等底层的服务都只能用过TCP 来接收硬件客户端发来的数据
@zy445566 谢谢
@xivistudios 分享的目的在于一起学习,共享资源,有问题大家一起维护。而不是比什么技术牛不牛,高大上或者贬低啥的
参考了一些文章,包括楼主的项目。自己也写了一个简单的分享一下互相学习。https://github.com/fantasysea/nodesocket