├── .gitignore ├── .npmrc ├── examples ├── tcpBuffer │ ├── readme.md │ ├── client.js │ └── server.js ├── tcpMsg │ ├── readme.md │ ├── client.js │ └── server.js ├── readme.md ├── sample │ └── index.js ├── tsSample │ └── index.ts └── tcpSample │ └── index.js ├── assets └── README │ └── schematic.png ├── publish.sh ├── index.js ├── typings └── stick.d.ts ├── test ├── test.MsgCenter.js ├── pack.test.js └── test.stick.js ├── package.json ├── .eslintrc.js ├── docs ├── API.md ├── GettingStarted.md └── principle.drawio ├── LICENSE.md ├── lib ├── msgCenter.js └── core.js ├── README.md └── README_EN.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | logs 3 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | registry=https://registry.npmjs.org/ -------------------------------------------------------------------------------- /examples/tcpBuffer/readme.md: -------------------------------------------------------------------------------- 1 | # 本demo主要演示TCP中直接处理字节流粘包,展示出如何自己组装包头包体和解包,如不向自己进行组装包头解包操作,请看demo tcp-msg -------------------------------------------------------------------------------- /assets/README/schematic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lvgithub/stick/HEAD/assets/README/schematic.png -------------------------------------------------------------------------------- /examples/tcpMsg/readme.md: -------------------------------------------------------------------------------- 1 | # 本demo主要演示TCP中处理粘包的方法,不需要自己组装包头,直接发送和接收文本消息 2 | # 组包解包操作本类库已经封装底层,如何自行组包请看demo tcp-buffer 3 | -------------------------------------------------------------------------------- /publish.sh: -------------------------------------------------------------------------------- 1 | 2 | npm publish --registry=https://registry.npmjs.org 3 | npm publish --registry=https://npm.pkg.github.com/lvgithub -------------------------------------------------------------------------------- /examples/readme.md: -------------------------------------------------------------------------------- 1 | 2 | # Examples 3 | 4 | * [*sample.js*](https://github.com/lvgithub/stick/blob/master/examples/sample/index.js) 5 | * [*tcpSample.js*](https://github.com/lvgithub/stick/blob/master/examples/tcpSample/index.js) 6 | * [*typescript-sample.js*](https://github.com/lvgithub/stick/blob/master/examples/tsSample/index.ts) 7 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const stick = require('./lib/core'); 4 | const msgCenter = require('./lib/msgCenter'); 5 | 6 | module.exports = { 7 | stick, 8 | msgCenter, 9 | Stick: stick, 10 | MaxBodyLen: { 11 | '32K': 2, // max: 32767 body:16M 12 | '2048M': 4 // max: 2147483647 13 | } 14 | }; 15 | -------------------------------------------------------------------------------- /typings/stick.d.ts: -------------------------------------------------------------------------------- 1 | export enum MaxBodyLen { 2 | '32K', 3 | '2048M' 4 | } 5 | 6 | declare function callback(buf: Buffer): void 7 | export class Stick { 8 | constructor(bufferSize: number) 9 | putData(buf: Buffer) 10 | onData(callback) 11 | onBody(callback) 12 | makeData(body: string): Buffer 13 | setMaxBodyLen(length: MaxBodyLen) 14 | } -------------------------------------------------------------------------------- /test/test.MsgCenter.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const MsgCenter = require('../index').msgCenter; 4 | const msgCenter = new MsgCenter(); 5 | 6 | msgCenter.onMsgRecv(msg => { 7 | console.log('recv data: ' + msg.toString()); 8 | }); 9 | 10 | msgCenter.putMsg('234'); 11 | msgCenter.putData(Buffer.from([0x00, 0x02, 0x31, 0x32, 0x00, 0x04, 0x31, 0x32, 0x33, 0x34])); 12 | 13 | // console.log(msgCenter.publish('123')) 14 | -------------------------------------------------------------------------------- /examples/sample/index.js: -------------------------------------------------------------------------------- 1 | // example/sample.js 2 | 'use strict'; 3 | const Stick = require('../../index').Stick; 4 | const stick = new Stick(1024); 5 | 6 | const log = (...info) => console.log(new Date(), '', ...info); 7 | 8 | stick.onBody(function (body) { 9 | log('body:', body.toString()); 10 | }); 11 | 12 | const user = { name: 'liuwei', isVip: true }; 13 | const data = stick.makeData(JSON.stringify(user)); 14 | 15 | // 在真实的应用场景中这部分代码在客户端中通过TCP 发送给服务端 16 | // 服务端把接受到的 data,通过 putData 把数据放入 stick 17 | stick.putData(data); 18 | -------------------------------------------------------------------------------- /examples/tcpMsg/client.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const net = require('net'); 4 | const stick = require('../../index'); 5 | const msgCenter = new stick.msgCenter(); 6 | 7 | const client = net.createConnection({ port: 8080, host: '127.0.0.1' }, function () { 8 | const msgBuffer = msgCenter.publish('username=123&password=1234567,qwe'); 9 | 10 | client.write(msgBuffer); 11 | }); 12 | 13 | client.on('data', function (data) { 14 | console.log(data.toString()); 15 | }); 16 | client.on('end', function () { 17 | console.log('disconnect from server'); 18 | }); -------------------------------------------------------------------------------- /examples/tsSample/index.ts: -------------------------------------------------------------------------------- 1 | import { Stick, MaxBodyLen } from '../../typings/stick'; 2 | 3 | const stick = new Stick(256); 4 | 5 | // 设置 body 最大值为 32k 6 | stick.setMaxBodyLen(MaxBodyLen["32K"]); 7 | 8 | const log = (...info: any) => console.log(new Date(), '', ...info); 9 | 10 | stick.onBody((body: Buffer) => { 11 | log('body:', body.toString()); 12 | }); 13 | 14 | const user = { name: 'liuwei', isVip: true }; 15 | const data = stick.makeData(JSON.stringify(user)); 16 | 17 | // 在真实的应用场景中这部分代码在客户端中通过TCP 发送给服务端 18 | // 服务端把接受到的 data,通过 putData 把数据放入 stick 19 | stick.putData(data); -------------------------------------------------------------------------------- /examples/tcpMsg/server.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const net = require('net'); 4 | const stick = require('../../index'); 5 | 6 | const tcp_server = net.createServer(function (socket) { 7 | const msgCenter = new stick.msgCenter(); 8 | 9 | socket.on('data', data => msgCenter.putData(data)); 10 | socket.on('close', () => console.log('client disconnected')); 11 | socket.on('error', error => console.log(`error:客户端异常断开: ${error}`)); 12 | 13 | msgCenter.onMsgRecv(data => console.log('recv data: ' + data.toString())); 14 | }); 15 | 16 | 17 | tcp_server.on('error', err => console.log(err)); 18 | tcp_server.listen(8080, () => console.log('tcp_server listening on 8080')); 19 | 20 | 21 | -------------------------------------------------------------------------------- /examples/tcpBuffer/client.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const net = require('net'); 4 | 5 | const client = net.createConnection({ port: 8080, host: '127.0.0.1' }, function () { 6 | const body = Buffer.from('username=123&password=1234567,qwe'); 7 | 8 | // 写入包头 9 | const headBuf = new Buffer(4); 10 | headBuf.writeUInt32BE(body.byteLength, 0); 11 | console.log('data length: ' + headBuf.readInt32BE()); 12 | 13 | // 发送包头 14 | client.write(headBuf); 15 | // 发送包内容 16 | client.write(body); 17 | 18 | console.log('data body: ' + body.toString()); 19 | 20 | }); 21 | 22 | client.on('data', function (data) { 23 | console.log(data.toString()); 24 | }); 25 | client.on('end', function () { 26 | console.log('disconnect from server'); 27 | }); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@lvgithub/stick", 3 | "version": "3.1.11", 4 | "description": "The solution of sticky package problem of TCP for Node.Js !", 5 | "types": "./typings/stick.d.ts", 6 | "main": "index.js", 7 | "scripts": { 8 | "test": "jest" 9 | }, 10 | "repository": "https://github.com/lvgithub/stick.git", 11 | "keywords": [ 12 | "stick", 13 | "stick package", 14 | "stick data", 15 | "tcp", 16 | "node", 17 | "粘包", 18 | "缓存" 19 | ], 20 | "author": "liuwei", 21 | "license": "MIT", 22 | "bugs": { 23 | "url": "https://github.com/lvgithub/stick/issues" 24 | }, 25 | "homepage": "https://github.com/lvgithub/stick#readme", 26 | "devDependencies": { 27 | "eslint": "^7.27.0", 28 | "jest": "^29.7.0" 29 | } 30 | } -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 'env': { 3 | 'browser': true, 4 | 'commonjs': true, 5 | 'es6': true, 6 | 'jest': true 7 | }, 8 | 'extends': 'eslint:recommended', 9 | 'globals': { 10 | 'Buffer': true, 11 | 'test': true 12 | }, 13 | 'parserOptions': { 14 | 'ecmaVersion': 2018, 15 | 'sourceType': 'module' 16 | }, 17 | 'rules': { 18 | 'no-console': 'off', 19 | 'indent': [ 20 | 'error', 21 | 4 22 | ], 23 | 'linebreak-style': [ 24 | 'error', 25 | 'unix' 26 | ], 27 | 'quotes': [ 28 | 'error', 29 | 'single' 30 | ], 31 | 'semi': [ 32 | 'error', 33 | 'always' 34 | ] 35 | } 36 | }; -------------------------------------------------------------------------------- /docs/API.md: -------------------------------------------------------------------------------- 1 | - ## Class: Stick 2 | 3 | - **new Stick(bufferSize: number)** 4 | 5 | Stick 类用于处理数据包,从流中解析出用户定义的一块快数据 6 | 7 | * **bufferSize** Stick处理数据包初始化的缓存大小,默认 512 Bytes 8 | 9 | 10 | 11 | - **putData(buf: Buffer)** 12 | 13 | 往 stick 中put 收到的数据流 14 | 15 | 16 | 17 | - **onData(callback)** 18 | 19 | 当收到的数据流中包含了完整的数据块,触发回调返回数据块(`header`+`body`) 20 | 21 | * **callback**: (buf: Buffer): void 22 | 23 | 24 | 25 | - **onBody(callback)** 26 | 27 | 当收到的数据流中包含了完整的数据块,触发回调返回数据内容(`body`) 28 | 29 | 30 | 31 | - **makeData(body: string): Buffer** 32 | 33 | 用于客户端中,帮助生成符合 Stick 协议的数据块(`data`) 34 | 35 | 36 | 37 | - **setMaxBodyLen(length: MaxBodyLen)** 38 | 39 | 设置`body` 的最大长度,提供两种配置见 ** MaxBodyLen** 40 | 41 | * #### **Enum: MaxBodyLen** 42 | 43 | * **32K** 最大32kb 44 | * **2048M** 最大 2048M -------------------------------------------------------------------------------- /examples/tcpSample/index.js: -------------------------------------------------------------------------------- 1 | // example/tcpSample.js 2 | 'use strict'; 3 | 4 | const net = require('net'); 5 | const { Stick, MaxBodyLen } = require('../../index'); 6 | const stick = new Stick(1024); 7 | 8 | // 设置最大传输body大小为 32K,即 header用两个 Byte,最大表示的值为 32767 9 | stick.setMaxBodyLen(MaxBodyLen['32K']); 10 | 11 | // stick 会解析好一个个数据包,按照接收的顺序输出 12 | stick.onBody(body => { 13 | console.log('body:', body.toString()); 14 | }); 15 | 16 | // server端 17 | const server = net.createServer(socket => { 18 | // socket 接收到的 片段 put 到 stick 中处理 19 | socket.on('data', data => { 20 | stick.putData(data); 21 | }); 22 | 23 | 24 | server.close(); 25 | }); 26 | server.listen(8080); 27 | 28 | // client 端 29 | const client = net.createConnection({ port: 8080, host: 'localhost' }, () => { 30 | // 客户端通过 stick 打包内容 31 | const data = stick.makeData(JSON.stringify({ userName: 'liuwei' })); 32 | // 然后把打包对的内容通过 TCP 发送给 服务端 33 | client.write(data); 34 | client.destroy(); 35 | }); -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 liuwei 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /docs/GettingStarted.md: -------------------------------------------------------------------------------- 1 | ```javascript 2 | // example/tcpSample.js 3 | 'use strict'; 4 | const net = require('net'); 5 | const { Stick, MaxBodyLen } = require('../index'); 6 | const stick = new Stick(1024); 7 | 8 | // 设置最大传输body大小为 32K,即 header用两个 Byte,最大表示的值为 32767 9 | stick.setMaxBodyLen(MaxBodyLen['32K']); 10 | 11 | // stick 会解析好一个个数据包,按照接收的顺序输出 12 | stick.onBody(body => { 13 | console.log('body:', body.toString()); 14 | }); 15 | // server端 16 | const server = net.createServer(socket => { 17 | 18 | // socket 接收到的 片段 put 到 stick 中处理 19 | socket.on('data', data => { 20 | stick.putData(data); 21 | }); 22 | 23 | 24 | 25 | server.close(); 26 | }); 27 | server.listen(8080); 28 | 29 | // client 端 30 | const client = net.createConnection({ port: 8080, host: 'localhost' }, () => { 31 | 32 | // 客户端通过 stick 打包内容 33 | const data = stick.makeData(JSON.stringify({ userName: 'liuwei' })); 34 | 35 | // 然后把打包对的内容通过 TCP 发送给 服务端 36 | client.write(data); 37 | 38 | client.destroy(); 39 | }); 40 | ``` 41 | 42 | ## Output 43 | 44 | ``` 45 | $ node example/sample.js 46 | body: {"userName":"liuwei"} 47 | ``` 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /examples/tcpBuffer/server.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const net = require('net'); 4 | const stick_package = require('../../index').stick; 5 | 6 | const tcp_server = net.createServer(function (socket) { 7 | socket.stick = new stick_package(1024).setReadIntBE('32'); 8 | socket.on('data', function (data) { 9 | socket.stick.putData(data); 10 | }); 11 | 12 | socket.stick.onData(function (data) { 13 | // 解析包头长度 14 | const head = new Buffer(4); 15 | data.copy(head, 0, 0, 4); 16 | 17 | // 解析数据包内容 18 | const body = new Buffer(head.readInt32BE()); 19 | data.copy(body, 0, 4, head.readInt32BE()); 20 | 21 | console.log('data length: ' + head.readInt32BE()); 22 | console.log('body content: ' + body.toString()); 23 | }); 24 | 25 | socket.on('close', () => { 26 | console.log('client disconnected'); 27 | }); 28 | 29 | socket.on('error', error => { 30 | console.log(`error:客户端异常断开: ${error}`); 31 | }); 32 | }); 33 | 34 | tcp_server.on('error', function (err) { 35 | throw err; 36 | }); 37 | tcp_server.listen(8080, function () { 38 | console.log('tcp_server listening on 8080'); 39 | }); -------------------------------------------------------------------------------- /test/pack.test.js: -------------------------------------------------------------------------------- 1 | // test.pack.js 2 | 'use strict'; 3 | const Stick = require('../index').stick; 4 | const stick = new Stick(1024).setReadIntBE('16'); 5 | 6 | // 初始化header内存 7 | const headerBuf = Buffer.alloc(2); 8 | // 初始化数据 9 | const dataBuf = Buffer.from('ab'); 10 | // 写入数据长度 11 | headerBuf.writeInt16BE(dataBuf.length, 0); 12 | 13 | // 14 | const buffer = Buffer.concat([headerBuf, dataBuf]); 15 | const buffer2 = Buffer.concat([headerBuf, Buffer.from('cd')]); 16 | 17 | let count = 1; 18 | test('test stick onData', async (done) => { 19 | stick.onData(function (data) { 20 | count === 1 && expect(data.toString()).toBe(buffer.toString()); 21 | count === 2 && expect(data.toString()).toBe(buffer2.toString()); 22 | count++; 23 | done(); 24 | }); 25 | stick.putData(buffer); 26 | stick.putData(buffer2); 27 | }); 28 | let count2 = 1; 29 | 30 | test('test stick onBody', async (done) => { 31 | stick.onBody(function (data) { 32 | count2 === 1 && expect(data.toString()).toBe('ab'); 33 | count2 === 2 && expect(data.toString()).toBe('cd'); 34 | count2++; 35 | done(); 36 | }); 37 | stick.putData(buffer); 38 | stick.putData(buffer2); 39 | }); 40 | 41 | -------------------------------------------------------------------------------- /lib/msgCenter.js: -------------------------------------------------------------------------------- 1 | 2 | 'use strict'; 3 | 4 | const Stick = require('./core'); 5 | 6 | module.exports = function msgCenter(option) { 7 | 8 | option = option || {}; 9 | const bufferSize = option.bufferSize || 1024; 10 | const type = option.type || 16; 11 | const bigEndian = option.bigEndian || true; 12 | const stick = bigEndian ? new Stick(bufferSize).setReadIntBE(type) : new Stick(bufferSize).setReadIntLE(type); 13 | 14 | // 向stick 队列中推送消息 15 | this.putMsg = function (msg) { 16 | stick.putMsg(msg); 17 | }; 18 | 19 | // 向stick 队列中推送字节流 20 | this.putData = function (data) { 21 | stick.putData(data); 22 | }; 23 | 24 | this.publish = function (msg) { 25 | return stick.publish(msg); 26 | }; 27 | 28 | this.onMsgRecv = function (msgHandleFun) { 29 | 30 | stick.onData(function (data) { 31 | 32 | const headLen = stick.dataHeadLen; 33 | const head = new Buffer(headLen); 34 | data.copy(head, 0, 0, headLen); 35 | 36 | const dataLen = head[stick.readIntMethod](); 37 | const body = new Buffer(dataLen); 38 | data.copy(body, 0, headLen, headLen + dataLen); 39 | 40 | msgHandleFun(body); 41 | }); 42 | }; 43 | 44 | }; 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /test/test.stick.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Stick = require('../index').stick; 4 | const stick = new Stick(1024).setReadIntBE('16'); 5 | 6 | // 构造一个buffer,包含两个数据包,10个字节 7 | const data = Buffer.from([0x00, 0x02, 0x61, 0x62, 0x00, 0x04, 0x65, 0x6, 0x66, 0x66]); 8 | 9 | /* 构造两个buffer 10 | * data2_1包含: 11 | * 第一个数据包的全部数据 12 | * 第二个数据包的部分数据 13 | * data2_2包含: 14 | * 第二个数据包的剩余数据 15 | */ 16 | // const data2_1 = Buffer.from([0x00, 0x00, 0x00, 0x02, 0x66, 0x66, 0x00, 0x04, 0x88, 0x02, 0x11]); 17 | // const data2_2 = Buffer.from([0x11]); 18 | 19 | // // 设置收到完整数据触发器 20 | stick.onData(function (data) { 21 | console.log('receive data,length:' + data.length); 22 | console.log(data.toString()); 23 | }); 24 | const buf = new Buffer(2); 25 | buf.writeInt16BE(2, 0); 26 | 27 | console.log('buf',buf); 28 | // console.log('Log:传入两个包,一次Put[验证一次性Put数据包]'); 29 | stick.putData(data);//receive data,length:4 30 | // console.log('Log:传入两个包,分两次Put[验证分两次Put数据包]'); 31 | // stick.putData(data2_1); 32 | // stick.putData(data2_2);// 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> 33 | 34 | 35 | // // 构造一个512个字节长度的数据。用来测试缓存满的情况 36 | // // let bytes6 = Buffer.from([0x01, 0xfe]); 37 | // // let bytes7 = Buffer.alloc(510).fill(33); 38 | 39 | // // 构造一个513个字节长度的数据。用来测试超出缓存的情况 40 | // // let bytes8 = Buffer.from([0x01, 0xff]); 41 | // // let bytes9 = Buffer.alloc(511).fill(33); 42 | // // console.log('log:传入512个字节的数据包[验证缓冲全满情况]'); 43 | // // stick.putData(bytes6); 44 | // // stick.putData(bytes7); 45 | 46 | // // console.log('log:传入513个字节的数据包[验证缓冲扩增情况]'); 47 | // // stick.putData(bytes8); 48 | // // stick.putData(bytes9); 49 | 50 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 | ![issues](https://img.shields.io/github/issues/lvgithub/stick) 5 | ![forks](https://img.shields.io/github/forks/lvgithub/stick) 6 | ![stars](https://img.shields.io/github/stars/lvgithub/stick) 7 | ![linces](https://img.shields.io/github/license/lvgithub/stick) 8 | ![npm](http://img.shields.io/npm/v/@lvgithub/stick.svg?style=flat-square) 9 | ![david](https://img.shields.io/david/lvgithub/stick?style=flat-square) 10 | ![downloads](https://img.shields.io/npm/dm/@lvgithub/stick.svg?style=flat-square) 11 | 12 |
13 | 14 | 15 | 16 | # 🌈 Introduction 17 | 18 | 我们使用 TCP 通信的时候,由于TCP是面向流的,因此需要对流进行解析。也就是所谓的拆包,把流解析为一段段我们所需要的数据。本方案为 Node.Js 实现的一个处理方案。 19 | 20 | 对要发送的数据按协议编码,把数据 `data` 分为 `header` +`body `两部分,header 默认固定长度(_2 byte_),`header`描述的是 `body` 数据的长度。由于`header`定长,因此可以通过`header`,解析出 `body` 的内容。 21 | 22 | 默认 `header` 我们使用 `2 Byte` 的存储空间,即`Int16`最大表示的 `body` 长度为 `32767`,也就是`16M`。 23 | 24 | ![Schematic](https://github.com/lvgithub/stick/blob/master/assets/README/schematic.png) 25 | 26 | 如上图,我们看先取出数据流的前两位,读取到内容 `0x00, 0x02`转化为整数的长度是 2,再读取出`body`第3、4位 `0x61, 0x62`。 27 | 28 | ## Links 29 | 30 | 🌈 [Install](https://www.npmjs.com/package/@lvgithub/stick) 31 | 32 | 👀 [Getting Started](https://github.com/lvgithub/stick/blob/master/docs/GettingStarted.md) 33 | 34 | 😊 [API Reference](https://github.com/lvgithub/stick/blob/master/docs/API.md) 35 | 36 | 😸 [Examples](https://github.com/lvgithub/stick/blob/master/examples/readme.md) 37 | 38 | 🌍 [Solve the problem of "sticking packets" for TCP network transmission (Classic)](https://topic.alibabacloud.com/a/solve-the-problem-font-colorredoffont-quotsticking-font-colorredpacketsfontquot-for-tcp-network-transmission-classic_8_8_31915399.html) 39 | 40 | ## Other Language 41 | 42 | 现实场景中客户端是其他语言编写的比如C语言运行在单片机上,这时候大家可以基原理图自行打包,规则所示: 43 | 44 | ```shell 45 | data = header(body.length) + body 46 | ``` 47 | 48 | ## [License](http://opensource.org/licenses/MIT) 49 | MIT 50 | -------------------------------------------------------------------------------- /README_EN.md: -------------------------------------------------------------------------------- 1 | # Stick 2 | 3 | ## The solution of sticky package problem of TCP for Node.Js ! 4 | 5 | [If you like, you can subscribe to start or watch](https://github.com/lvgithub/stick) 6 | 7 | [中文文档](https://github.com/lvgithub/stick/blob/master/README_ZH.md) 8 | 9 | --- 10 | 11 | ## Table of Contents 12 | * Install 13 | * Characteristics 14 | * Configure 15 | * APIs 16 | * Changelog 17 | * Usage 18 | * Examples 19 | 20 | --- 21 | 22 | ## Install 23 | ``` 24 | npm i stickpackage 25 | ``` 26 | 27 | --- 28 | ## Trait 29 | * low memory, using circular queue data structure, unpack, queue memory according to user packet size configuration。 30 | * high concurrency, 1500 packets per second at present, 300 bytes per packet, less than 30M in memory, and less than 40% for single core CPU 31 | 32 | --- 33 | 34 | ## Configuration 35 | 36 | * [x] Provide solutions for TCP sticky processing 37 | * [x] The default buffer is 512 bytes, and when the data is over 512 bytes, the buffer space is automatically expanded with a 512 multiple 38 | * [x] By default, two bytes in package header are used to represent the packet length 39 | * [x] The main connection mode to receive data by default 40 | * [x] You can configure the big end of the small end of reading 41 | * [x] You can configure custom package header length 42 | * [x] Support automatic disassembly package 43 | * Dealing with back pressure 44 | 45 | --- 46 | 47 | ## Run the demo 48 | ``` 49 | git clone https://github.com/lvgithub/stick.git 50 | npm install 51 | 52 | cd example/tcp-buffer/ 53 | 54 | #in terminal: 55 | node server.js 56 | 57 | #in other terminal: 58 | node client.js 59 | 60 | #then client will connect to the server and sending data to each other 61 | ``` 62 | 63 | ## API 64 | * stick(bufferSize) => Processing bytes type packages 65 | ``` 66 | bufferSize: Setting the size of the cache space 67 | ``` 68 | * stick.setReadIntBE(type) => In big endian mode is set to the maximum packet size on the basis of "choose the right type 69 | ``` 70 | setReadIntBE(type) type:16 short(len:2) 71 | setReadIntBE(type) type:32 int(len:4) 72 | ``` 73 | * stick.setReadIntLE => In small endian mode is set to the maximum packet size on the basis of "choose the right type 74 | ``` 75 | setReadIntLE(type) type:16 short(len:2) 76 | setReadIntLE(type) type:32 int(len:4) 77 | ``` 78 | * stick.putData(buffer) => Put data to stick 79 | * stick.onData(buffer) => Get the all data 80 | * msgCenter(options) => Processing string type packages 81 | ``` 82 | options.bufferSize: Setting the size of the cache space 83 | options.type:Set header length (16|32) 84 | options.bigEndian: The default buffer is false 85 | ``` 86 | * msgCenter.putMsg(msg) => Put msg data to stick 87 | * msgCenter.publish(msg) => Send a msg 88 | ``` 89 | msgCenter.publish('123') 90 | => // 00 03 (data len) 31 32 33 (123) 91 | ``` 92 | * msgCenter.onMsgRecv(msgHandleFun) => Get all data 93 | ``` 94 | msgHandleFun: 95 | msgCenter.onMsgRecv(msg => { 96 | console.log(`recv data: ` + msg.toString()) 97 | ...do something 98 | }) 99 | ``` 100 | 101 | --- 102 | 103 | ## Updates 104 | 105 | * Add endian mode config,setReadIntBE,setReadIntLE 106 | * Add send string msg 107 | 108 | --- 109 | 110 | ## Usage 111 | * stick data 112 | ``` 113 | const MsgCenter = require('stickpackage').msgCenter 114 | const msgCenter = new MsgCenter() 115 | 116 | msgCenter.onMsgRecv(data => { 117 | console.log(`recv data: ` + data.toString()) 118 | }) 119 | 120 | msgCenter.putData(Buffer.from([0x00, 0x02, 0x31, 0x32, 0x00, 0x04, 0x31, 0x32, 0x33, 0x34])) 121 | //=> recv data: 12 122 | //=> recv data: 1234 123 | 124 | ``` 125 | --- 126 | 127 | * send buffer data 128 | ``` 129 | const Stick = require('stickpackage').stick; 130 | const stick = new Stick(1024).setReadIntBE('16') 131 | 132 | /* 133 | * data1:[0x00, 0x02, 0x66, 0x66] 134 | * data2:[0x00, 0x04, 0x88, 0x02, 0x11, 0x11] 135 | */ 136 | const data = Buffer.from([0x00, 0x02, 0x66, 0x66, 0x00, 0x04, 0x88, 0x02, 0x11, 0x11]); 137 | 138 | const data2_1 = Buffer.from([0x00, 0x00, 0x00, 0x02, 0x66, 0x66, 0x00, 0x04, 0x88, 0x02, 0x11]); 139 | const data2_2 = Buffer.from([0x11]); 140 | 141 | stick.onData(function (data) { 142 | console.log('receive data,length:' + data.length); 143 | console.log(data) 144 | }); 145 | 146 | stick.putData(data); 147 | stick.putData(data2_1); 148 | stick.putData(data2_2); 149 | 150 | // result: 151 | // receive data,length:4 152 | // receive data,length:6 153 | // receive data,length:2 receive data, length:4 < Buffer 00 02 66 66> receive data, length:6< Buffer 00 04 88 02 11 11> 154 | ``` 155 | --- 156 | 157 | ## Demo([See the example folder](https://github.com/lvgithub/stick/tree/master/example)) 158 | * tcp-msg 159 | ``` 160 | // Client.js 161 | const net = require('net') 162 | const stick = require('../../index') 163 | const msgCenter = new stick.msgCenter() 164 | 165 | const client = net.createConnection({ 166 | port: 8080, 167 | host: '127.0.0.1' 168 | }, function () { 169 | const msgBuffer = msgCenter.publish('username=123&password=1234567,qwe') 170 | client.write(msgBuffer) 171 | }) 172 | 173 | client.on('data', function (data) { 174 | console.log(data.toString()) 175 | }) 176 | 177 | client.on('end', function () { 178 | console.log('disconnect from server') 179 | }) 180 | ``` 181 | ``` 182 | // Server.js 183 | const net = require('net') 184 | const stick = require('../../index') 185 | 186 | const tcp_server = net.createServer(function (socket) { 187 | const msgCenter = new stick.msgCenter() 188 | 189 | socket.on('data', function (data) { 190 | msgCenter.putData(data) 191 | }) 192 | 193 | msgCenter.onMsgRecv(function (data) { 194 | console.log('recv data: ' + data.toString()) 195 | }) 196 | 197 | socket.on('close', function (error) { 198 | console.log('client disconnected') 199 | }) 200 | 201 | socket.on('error', function (error) { 202 | console.log(`error: ${error}`) 203 | }) 204 | }) 205 | 206 | tcp_server.on('error', function (err) { 207 | throw err 208 | }) 209 | 210 | tcp_server.listen(8080, function () { 211 | console.log('tcp_server listening on 8080') 212 | }) 213 | ``` 214 | * tcp-buffer 215 | ``` 216 | // Clinet.js 217 | const net = require('net') 218 | 219 | const client = net.createConnection({ port: 8080, host: '127.0.0.1' }, function () { 220 | const body = Buffer.from('username=123&password=1234567,qwe') 221 | 222 | const headBuf = new Buffer(4) 223 | headBuf.writeUInt32BE(body.byteLength, 0) 224 | console.log('data length: ' + headBuf.readInt32BE()) 225 | 226 | client.write(headBuf) 227 | client.write(body) 228 | 229 | console.log('data body: ' + body.toString()) 230 | }) 231 | 232 | client.on('data', function (data) { 233 | console.log(data.toString()) 234 | }) 235 | 236 | client.on('end', function () { 237 | console.log('disconnect from server') 238 | }) 239 | ``` 240 | ``` 241 | // Server.js 242 | const net = require('net') 243 | const stick_package = require('../../index').stick 244 | 245 | const tcp_server = net.createServer(function (socket) { 246 | socket.stick = new stick_package(1024).setReadIntBE('32') 247 | socket.on('data', function (data) { 248 | socket.stick.putData(data) 249 | }) 250 | 251 | socket.stick.onData(function (data) { 252 | 253 | const head = new Buffer(4) 254 | data.copy(head, 0, 0, 4) 255 | 256 | const body = new Buffer(head.readInt32BE()) 257 | data.copy(body, 0, 4, head.readInt32BE()) 258 | 259 | console.log('data length: ' + head.readInt32BE()) 260 | console.log('body content: ' + body.toString()) 261 | }) 262 | 263 | socket.on('close', function (error) { 264 | console.log('client disconnected') 265 | }) 266 | 267 | socket.on('error', function (error) { 268 | console.log(`error: ${error}`) 269 | }) 270 | }) 271 | 272 | tcp_server.on('error', function (err) { 273 | throw err 274 | }) 275 | 276 | tcp_server.listen(8080, function () { 277 | console.log('tcp_server listening on 8080') 278 | }) 279 | ``` 280 | --- 281 | 282 | ## License 283 | 284 | [MIT](http://opensource.org/licenses/MIT) 285 | 286 | Copyright (c) 2017-present, ximen (G.doe) 287 | -------------------------------------------------------------------------------- /docs/principle.drawio: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | -------------------------------------------------------------------------------- /lib/core.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const EventEmitter = require('events').EventEmitter; 3 | const Assert = require('assert'); 4 | 5 | const StackBuffer = function (bufferSize) { 6 | const bufferLength = bufferSize; 7 | const _event = new EventEmitter(); 8 | let _dataWritePosition = 0;//数据存储起始位置 9 | let _dataReadPosition = 0;//数据存储结束位置 10 | let _dataHeadLen = 2;//数据包头长度 11 | let _dataLen = 0;//已经接收数据的长度 12 | 13 | let _bufferLength = bufferLength || 512; //buffer默认长度 14 | let _buffer = Buffer.alloc(bufferLength || _bufferLength);//申请内存 15 | 16 | let _readIntMethod = 'readInt16BE'; 17 | let _writeIntMethod = 'writeInt16BE'; 18 | 19 | this.readIntMethod = _readIntMethod; 20 | this.writeIntMethod = _writeIntMethod; 21 | this.dataHeadLen = _dataHeadLen; 22 | 23 | /** 24 | * 设置大端接收 25 | * type:16 包头长度为2,short类型 26 | * type:32 包头长度为4,int类型 27 | */ 28 | this.setReadIntBE = function (type) { 29 | type = Number(type); 30 | const isValid = (type === 16 || type === 32); 31 | Assert(isValid, ' invalid type(must be 16 or 32)'); 32 | 33 | _readIntMethod = 'readInt' + type + 'BE'; 34 | _writeIntMethod = 'writeInt' + type + 'BE'; 35 | _dataHeadLen = (type === 16) ? 2 : 4; 36 | 37 | this.readIntMethod = _readIntMethod; 38 | this.writeIntMethod = _writeIntMethod; 39 | this.dataHeadLen = _dataHeadLen; 40 | 41 | return this; 42 | }; 43 | 44 | /** 45 | * 设置小端接收 46 | * type:16 包头长度为2,short类型 47 | * type:32 包头长度为4,int类型 48 | */ 49 | this.setReadIntLE = function (type) { 50 | type = Number(type); 51 | const isValid = (type === 16 || type === 32); 52 | Assert(isValid, ' invalid type(must be 16 or 32)'); 53 | 54 | _readIntMethod = 'readInt' + type + 'LE'; 55 | _writeIntMethod = 'writeInt' + type + 'LE'; 56 | _dataHeadLen = (type === 16) ? 2 : 4; 57 | 58 | this.readIntMethod = _readIntMethod; 59 | this.writeIntMethod = _writeIntMethod; 60 | this.dataHeadLen = _dataHeadLen; 61 | 62 | return this; 63 | }; 64 | 65 | this.setMaxBodyLen = (length, isLE = false) => { 66 | if (length !== 2 && length !== 4) { 67 | throw new Error('length must is number 2 or 4'); 68 | } 69 | if (!isLE) { 70 | this.setReadIntBE(length * 8); 71 | } else { 72 | this.setReadIntLE(length * 8); 73 | } 74 | return this; 75 | }; 76 | 77 | // 数据包接收完整后触发事件 78 | this.onData = function (callback) { 79 | _event.on('data', callback); 80 | }; 81 | 82 | 83 | // 只返回body 84 | this.onBody = function (callback) { 85 | _event.on('data', data => { 86 | const headLen = _dataHeadLen; 87 | const head = Buffer.alloc(headLen); 88 | data.copy(head, 0, 0, headLen); 89 | 90 | const dataLen = head[_readIntMethod](); 91 | const body = Buffer.alloc(dataLen); 92 | data.copy(body, 0, headLen, headLen + dataLen); 93 | callback(body); 94 | }); 95 | }; 96 | 97 | // 数据包错误触发事件 98 | this.onError = function (callback) { 99 | _event.on('error', callback); 100 | }; 101 | 102 | // 往buffer填入数据 103 | this.putData = function (data) { 104 | // console.log('收到数据'); 105 | if (data === undefined) { 106 | return; 107 | } 108 | 109 | //要拷贝数据的起始位置 110 | let dataSatrt = 0; 111 | // 要拷贝数据的结束位置 112 | let dataLength = data.length; 113 | // 缓存剩余可用空间 114 | let availableLen = this.getAvailableLen(); 115 | 116 | // buffer剩余空间不足够存储本次数据 117 | if (availableLen < dataLength) { 118 | // 以512字节为基数扩展Buffer空间 119 | let exLength = Math.ceil((_dataLen + dataLength) / 512) * 512; 120 | let tempBuffer = Buffer.alloc(exLength); 121 | //_buffer.copy(tempBuffer); 122 | _bufferLength = exLength; 123 | 124 | // 数据存储进行了循环利用空间,需要进行重新打包 125 | // 数据存储在buffer的尾部+头部的顺序 126 | if (_dataWritePosition < _dataReadPosition) { 127 | let dataTailLen = _bufferLength - _dataReadPosition; 128 | _buffer.copy(tempBuffer, 0, _dataReadPosition, _dataReadPosition + dataTailLen); 129 | _buffer.copy(tempBuffer, dataTailLen, 0, _dataWritePosition); 130 | } 131 | // 数据是按照顺序进行的完整存储 132 | else { 133 | _buffer.copy(tempBuffer, 0, _dataReadPosition, _dataWritePosition); 134 | } 135 | 136 | _buffer = tempBuffer; 137 | tempBuffer = null; 138 | 139 | _dataReadPosition = 0; 140 | _dataWritePosition = _dataLen; 141 | data.copy(_buffer, _dataWritePosition, dataSatrt, dataSatrt + dataLength); 142 | _dataLen += dataLength; 143 | _dataWritePosition += dataLength; 144 | } 145 | // 数据会冲破buffer尾部 146 | else if (_dataWritePosition + dataLength > _bufferLength) { 147 | /* 分两次存储到buffer: 148 | * 1、存储在原数据尾部 149 | * 2、存储在原数据头部 150 | */ 151 | // buffer尾部剩余空间的长度 152 | let bufferTailLength = _bufferLength - _dataWritePosition; 153 | if (bufferTailLength < 0) { 154 | console.log('程序有漏洞,bufferTailLength < 0 '); 155 | } 156 | // 数据尾部位置 157 | let dataEndPosition = dataSatrt + bufferTailLength; 158 | data.copy(_buffer, _dataWritePosition, dataSatrt, dataEndPosition); 159 | 160 | _dataWritePosition = 0; 161 | dataSatrt = dataEndPosition; 162 | 163 | // data剩余未拷贝进缓存的长度 164 | let unDataCopyLen = dataLength - bufferTailLength; 165 | data.copy(_buffer, _dataWritePosition, dataSatrt, dataSatrt + unDataCopyLen); 166 | // 记录数据长度 167 | _dataLen = _dataLen + dataLength; 168 | // 记录buffer可写位置 169 | _dataWritePosition = _dataWritePosition + unDataCopyLen; 170 | } 171 | // 剩余空间足够存储数据 172 | else { 173 | // 拷贝数据到buffer 174 | data.copy(_buffer, _dataWritePosition, dataSatrt, dataSatrt + dataLength); 175 | 176 | if (_dataWritePosition > _bufferLength) { 177 | console.log('程序有漏洞'); 178 | } 179 | // 记录数据长度 180 | _dataLen = _dataLen + dataLength; 181 | // 记录buffer可写位置 182 | _dataWritePosition = _dataWritePosition + dataLength; 183 | } 184 | // 读取数据 185 | getData(); 186 | }; 187 | 188 | 189 | // 往buffer填入消息 190 | this.putMsg = function (msg) { 191 | const bodyBuf = Buffer.from(msg); 192 | const headBuf = Buffer.alloc(_dataHeadLen); 193 | 194 | headBuf[_writeIntMethod](bodyBuf.byteLength, 0); 195 | 196 | this.putData(headBuf); 197 | this.putData(bodyBuf); 198 | }; 199 | 200 | this.putBody = body => { 201 | this.putMsg(body); 202 | }; 203 | 204 | // 往buffer填入消息 205 | this.publish = function (msg) { 206 | const bodyBuf = Buffer.from(msg); 207 | const headBuf = Buffer.alloc(_dataHeadLen); 208 | 209 | headBuf[_writeIntMethod](bodyBuf.byteLength, 0); 210 | 211 | const msgBuf = Buffer.alloc(headBuf.length + bodyBuf.length); 212 | headBuf.copy(msgBuf, 0, 0, headBuf.length); 213 | bodyBuf.copy(msgBuf, headBuf.length, 0, bodyBuf.length); 214 | 215 | return msgBuf; 216 | }; 217 | 218 | this.makeData = this.publish; 219 | 220 | // 获取数据 221 | function getData() { 222 | // eslint-disable-next-line no-constant-condition 223 | while (true) { 224 | // 没有数据可读,不够解析出包头 225 | if (getDataLen() <= _dataHeadLen) { 226 | break; 227 | } 228 | // 解析包头长度 229 | // 尾部最后剩余可读字节长度 230 | let buffLastCanReadLen = _bufferLength - _dataReadPosition; 231 | let dataLen = 0; 232 | let headBuffer = Buffer.alloc(_dataHeadLen); 233 | // 数据包为分段存储,不能直接解析出包头 234 | if (buffLastCanReadLen < _dataHeadLen) { 235 | // 取出第一部分头部字节 236 | _buffer.copy(headBuffer, 0, _dataReadPosition, _buffer.length); 237 | // 取出第二部分头部字节 238 | let unReadHeadLen = _dataHeadLen - buffLastCanReadLen; 239 | _buffer.copy(headBuffer, buffLastCanReadLen, 0, unReadHeadLen); 240 | // 默认大端接收数据 241 | dataLen = headBuffer[_readIntMethod]() + _dataHeadLen; 242 | } 243 | else { 244 | _buffer.copy(headBuffer, 0, _dataReadPosition, _dataReadPosition + _dataHeadLen); 245 | dataLen = headBuffer[_readIntMethod](); 246 | dataLen += _dataHeadLen; 247 | } 248 | // 数据长度不够读取,直接返回 249 | if (getDataLen() < dataLen) { 250 | break; 251 | } 252 | // 数据够读,读取数据包 253 | else { 254 | let readData = Buffer.alloc(dataLen); 255 | // 数据是分段存储,需要分两次读取 256 | if (_bufferLength - _dataReadPosition < dataLen) { 257 | let firstPartLen = _bufferLength - _dataReadPosition; 258 | // 读取第一部分,直接到字符尾部的数据 259 | _buffer.copy(readData, 0, _dataReadPosition, firstPartLen + _dataReadPosition); 260 | // 读取第二部分,存储在开头的数据 261 | let secondPartLen = dataLen - firstPartLen; 262 | _buffer.copy(readData, firstPartLen, 0, secondPartLen); 263 | _dataReadPosition = secondPartLen; 264 | } 265 | // 直接读取数据 266 | else { 267 | _buffer.copy(readData, 0, _dataReadPosition, _dataReadPosition + dataLen); 268 | _dataReadPosition += dataLen; 269 | } 270 | 271 | try { 272 | // console.log('emit data'); 273 | _event.emit('data', readData); 274 | _dataLen -= readData.length; 275 | // 已经读取完所有数据 276 | if (_dataReadPosition === _dataWritePosition) { 277 | break; 278 | } 279 | } catch (e) { 280 | _event.emit('error', e); 281 | } 282 | } 283 | } 284 | } 285 | 286 | // 获取缓存数据长度 287 | function getDataLen() { 288 | let dataLen = 0; 289 | // 缓存全满 290 | if (_dataLen === _bufferLength && _dataWritePosition >= _dataReadPosition) { 291 | dataLen = _bufferLength; 292 | } 293 | // 缓存全部数据读空 294 | else if (_dataWritePosition >= _dataReadPosition) { 295 | dataLen = _dataWritePosition - _dataReadPosition; 296 | } 297 | else { 298 | dataLen = _bufferLength - _dataReadPosition + _dataWritePosition; 299 | } 300 | 301 | if (dataLen !== _dataLen) { 302 | console.log('程序有漏洞,dataLen长度不合法'); 303 | } 304 | return dataLen; 305 | } 306 | 307 | // 获取buffer可用的空间长度 308 | this.getAvailableLen = function () { 309 | return _bufferLength - _dataLen; 310 | }; 311 | }; 312 | 313 | module.exports = exports = StackBuffer; 314 | --------------------------------------------------------------------------------