TCP粘包问题
1: 在通讯的过程中,我们可能有发送多个数据包,数据包A,数据包B,数据包C,此时我们最好的期望是每次收到数据包A,数据包B,数据包C。但是TCP底层为了传送性能,可能会一次把ABC所有数据一起传过来,这个时候收到的是A+B+C,这个时候上层傻眼了,无法区分A,B,C这个叫做---粘包;
包体协议
1:要解决ABC数据包无法正确的拆分出A,B,C三个数据,我们需要在ABC之间插入长度/分解标志,这样根据长度和分解标志来解析出ABC的数据包;
2: 打入长度信息两种方式: (1)“数据长度” + 包体 (2)(包体 + 特定的结尾符号)
3: 本例采用 数据长度 +包体的方式。 数据长度2个字节,超过2个字节大小的数据,上层可以分多次发送;
4: 这里的包,是上层的应用协议的包,与TCP的包是两回事;
TCP 封包拆包实现
// 根据封包协议我们读取包体的长度;
read_pkg_size: function(pkg_data, offset) {
if (offset > pkg_data.length - 2) { // 没有办法获取长度信息的;
return -1;
}
var len = pkg_data.readUInt16LE(offset);
return len;
},
// 把一个要发送的数据,封包 2个字节的长度 + 数据
// data string 二进制的buffer
package_data: function(data) {
var buf = Buffer.allocUnsafe(2 + data.length);
buf.writeInt16LE(2 + data.length, 0);
buf.fill(data, 2);
return buf;
},
client_sock.on("data", function(data) {
console.log(data);
if (last_pkg != null) { // 上一次剩余没有处理完的半包;
var buf = Buffer.concat([last_pkg, data], last_pkg.length + data.length);
last_pkg = buf;
}
else {
last_pkg = data;
}
var offset = 0;
var pkg_len = netpkg.read_pkg_size(last_pkg, offset);
if (pkg_len < 0) {
return;
}
while(offset + pkg_len <= last_pkg.length) { // 判断是否有完整的包;
// 根据长度信息来读取我们的数据,架设我们穿过来的是文本数据
var cmd_buf = Buffer.allocUnsafe(pkg_len - 2); // 2个长度信息
last_pkg.copy(cmd_buf, 0, offset + 2, offset + pkg_len);
console.log("recv Cmd: ", cmd_buf); // cmdbuf ,用户发过来的命令的数据;
console.log(cmd_buf.toString("utf8"));
offset += pkg_len;
if (offset >= last_pkg.length) { // 正好我们的包处理完了;
break;
}
pkg_len = netpkg.read_pkg_size(last_pkg, offset);
if (pkg_len < 0) {
break;
}
}
// 能处理的数据包已经处理完成了,保存 0.几个包的数据
if (offset >= last_pkg.length) {
last_pkg = null;
}
else { // offset, length这段数据拷贝到新的Buffer里面
var buf = Buffer.allocUnsafe(last_pkg.length - offset);
last_pkg.copy(buf, 0, offset, last_pkg.length);
last_pkg = buf;
}
// end