TCP 粘包
#nodejs
目录
1. TCP 粘包问题
多个数据包重组时发生
TCP 粘包是因为 TCP 是面向流
的协议,传输数据时会将数据拆分成==多个数据包==进行传输,接收方
收到数据后会按序重组
。这可能导致两种情况:
- ==多个小数据包合并成一个大数据包==
- ==一个大数据包被分割成多个小数据包==
发送数据:
包1: "Hello" | 包2: "World"
可能的接收情况:
情况1(粘包): "HelloWorld"
情况2(拆包): "Hel" "loWorld"
2. TCP 粘包解决方案
2.1. 固定长度
// 固定长度方案
const net = require('net');
const server = net.createServer((socket) => {
const FIXED_LENGTH = 10;
let buffer = Buffer.alloc(0);
socket.on('data', (data) => {
buffer = Buffer.concat([buffer, data]);
// 当数据足够时,按固定长度解析
while (buffer.length >= FIXED_LENGTH) {
const message = buffer.slice(0, FIXED_LENGTH);
buffer = buffer.slice(FIXED_LENGTH);
console.log('收到消息:', message.toString());
}
});
});
2.2. 分隔符
// 使用分隔符方案
const net = require('net');
const server = net.createServer((socket) => {
let buffer = '';
const DELIMITER = '\n';
socket.on('data', (data) => {
buffer += data;
let index;
while ((index = buffer.indexOf(DELIMITER)) !== -1) {
const message = buffer.slice(0, index);
buffer = buffer.slice(index + 1);
console.log('收到消息:', message);
}
});
});
2.3. 消息头标识长度
// 消息头标识长度方案
const net = require('net');
class Protocol {
static pack(message) {
const body = Buffer.from(message);
const header = Buffer.alloc(4);
header.writeUInt32BE(body.length);
return Buffer.concat([header, body]);
}
static unpack(buffer) {
if (buffer.length < 4) return null;
const length = buffer.readUInt32BE(0);
if (buffer.length < length + 4) return null;
const body = buffer.slice(4, length + 4);
const remaining = buffer.slice(length + 4);
return {
message: body.toString(),
remaining: remaining
};
}
}
// 服务端示例
const server = net.createServer((socket) => {
let buffer = Buffer.alloc(0);
socket.on('data', (data) => {
buffer = Buffer.concat([buffer, data]);
let result;
while ((result = Protocol.unpack(buffer))) {
console.log('收到消息:', result.message);
buffer = result.remaining;
}
});
});
// 客户端示例
const client = new net.Socket();
client.connect(3000, '127.0.0.1', () => {
const message = Protocol.pack('Hello World');
client.write(message);
});
3. UDP 粘包问题 → 每个数据包都是独立
UDP 不存在粘包问题,因为:
- UDP 是基于消息的协议,每个数据包都是独立的
- UDP 不保证数据包的顺序和可靠性
- 每个 UDP 数据包都有明确的界限
// UDP 示例
const dgram = require('dgram');
// UDP 服务器
const server = dgram.createSocket('udp4');
server.on('message', (msg, rinfo) => {
// UDP 每个消息都是完整的,不会粘包
console.log(`收到消息: ${msg} 来自 ${rinfo.address}:${rinfo.port}`);
});
server.bind(3000);
// UDP 客户端
const client = dgram.createSocket('udp4');
// 发送多个消息
client.send('Hello', 3000, 'localhost');
client.send('World', 3000, 'localhost');
4. 总结比较
- TCP 粘包原因:
- TCP 是
流式
协议 - TCP 有缓冲区机制
- ==数据包的发送和接收==不一定一一对应
- TCP 是
- UDP 无粘包原因:
- UDP 是消息边界协议
- 每个数据包都是==独立==的
- 一次接收一个完整的数据包
- TCP 粘包解决方案:
- 固定长度
- 分隔符
- 消息头+消息体
- 自定义协议
5. 建议
- TCP 应用建议使用消息头+消息体方案
- 对于简单应用可以使用分隔符方案
- UDP 应用需要考虑数据包大小限制和丢包处理