编写稳定可靠的TCP长连接应用。
封装TCP连接的拆解包逻辑,支持Length-Based
和Line-Based
两种协议格式。
注意,本模块只支持node 8.x以上。
- 长连接
- 连接保持(心跳检测)
- 支持多请求多响应
- 可扩展协议
- 可扩展编解码器
我们对一个tcp包协议格式进行了如下约定:
|-----------------------------------------------|
| 1 byte | 4 bytes | 1 byte | N bytes |
|-----------------------------------------------|
| reqType | packetId | packetType | custom body |
|-----------------------------------------------|
- reqType: Number, 请求类型。0 - 请求 1 - 响应
- packetId: Number, 包id。
- packetType: Number, 包类型。0 - 普通包 1 - 心跳包
- custom body: 自定义包体。
其中,自定义包体就是用户发送的数据,它可以支持Length-Based
和Line-Based
两种子协议。
默认内置。适用于用户定义的包长度是确定的场景。原理很简单,先用一个field标识包体的长度,紧接着就是相应长度的包体。field本身长度支持自定义设置,默认是4字节。
|------------------|---------------|
| 4 bytes | N bytes |
|------------------|---------------|
| PACKET LENGTH(N) | HEADER | BODY |
|------------------|---------------|
适用于用户定义的包长度不确定的场景。它用一种边界符来表示包的结束。最典型的例子,http
协议格式就是Line-Based
的协议,它的边界符(或者说分隔符)是CRLF(\r\n)
。边界符支持自定义设置,默认是CRLF
。
|------------------|-----------|
| N bytes | delimiter |
|------------------|-----------|
| PACKET BODY | \r\n |
|------------------|-----------|
$ npm i @luckydrq/tcp-net -S
client和server应该使用相同的协议,保持拆解包逻辑一致。
const net = require('net');
const { Connection } = require('@luckydrq/tcp-net');
net.createServer(socket => {
const conn = new Connection({
socket,
// protocol: new LengthBasedProtocol(), default
});
// 发送一个packet
conn.write('hello world');
// 收到packet
conn.on('packet', ({ packetId, body }) => {
console.log(packetId); // 1
console.log(JSON.parse(body)); // { name: 'xuezu' }
});
}).listen();
const { Connection } = require('@luckydrq/tcp-net');
const conn = new Connection({ host, port });
// 发送一个packet
conn.write({ name: 'xuezu' });
// 收到一个packet
conn.on('packet', ({ packetId, body }) => {
console.log(packetId); // 1
console.log(body); // hello world
});
内置了心跳检测,默认每隔5秒发起一次心跳检测,响应超时时间为3秒,超时3次则关闭连接。支持配置,在构造函数中传入:
const conn = new Connection({
heartbeatInterval: 5 * 1000, // 心跳间隔
heartbeatTimeout: 3 * 1000, // 超时时间
heartbeatLimit: 3, // 允许超时次数
});
conn.on('heartbeatTimeout', () => {
console.log('连接已经关闭');
});
通常需要对数据做编解码,本例子展示如何接入一个自定义编解码器。如果不提供,默认只做基本的序列化。
自定义实现一个transCoder,只需要实现.encode
和.decode
方法即可,也可以只实现其中之一,不过通常编解码逻辑都是对应的。
const transCoder = {
encode(buf) {
// 调用hessian编码
// 注意,需要返回Buffer对象
return hessian.encode(buf);
},
decode(data) {
// 可以返回任意类型的数据
return hessian.decode(buf);
},
};
net.createServer(socket => {
const conn = new Connection({
socket,
protocol: new LengthBasedProtocol({ transCoder }), // 在协议中传入transCoder
});
// 收到packet
conn.on('packet', ({ body }) => console.log(body));
}).listen();
const conn = new Connection({
host,
port,
protocol: new LengthBasedProtocol({ transCoder }),
});
// 发一个packet
conn.write({ name: 'xuezu' });