TCP 可以自由地将线路上的数据分解成任意大小的数据包。根据不同的实现或物理传输,大小可以不同。您无法确切知道这将如何发生,也不应该具体取决于它是如何实现的。它甚至可能会根据您的数据采用的路径而有所不同。
此外,.on('data', ...)
事件只是为您提供到目前为止已到达的所有数据。虽然数据包的顺序得到保证,但不能保证如果您写入一组特定的字节,它们都会以相同的方式到达data
事件。它们可以被分解成更小的碎片,并且可能以更小的碎片形式到达。当 TCP 之上没有真正的协议时,这就是 TCP 较低级别上发生的情况。
因此,如果您通过 TCP 发送大量数据,则必须发明自己的协议才能知道何时获得完整的数据集。有多种不同的方案可以实现此目的。
分隔符。实际数据中不会出现的某种分隔符,表示一组数据的结尾。您读取并解析数据,直到获得分隔符,然后您就知道您拥有可以处理的完整数据集。 HTTP 协议使用换行符作为分隔符。有时使用零字节作为分隔符。
先发送长度。对于二进制数据,通常首先发送数据的长度,然后接收者知道他们正在读取多少字节的数据,直到获得完整的数据集。
现有协议。像 webSocket 协议这样的东西可以让你发送任意大小的消息,它会自动将它们包装成包含长度信息的数据包,以便它们可以自动为你重新组合成原始数据集,而无需你自己执行此操作。还有数千种其他协议,其中一种可能非常适合您的需求,您可以只使用现有的实现,而无需编写自己的实现。
你有某种机制知道你何时收到一组完整的数据,然后你可以设置你的data
事件处理程序读取数据,将其收集到缓冲区并观察数据的结尾(使用您选择的任何机制)。当您看到一组的末尾时,您可以将其与可能在其之后到达的任何其他数据分开,然后对其进行处理。
因此,假设您使用零字节作为分隔符,并且您已确保零不能也不会出现在实际数据中。然后,你会设置一个data
像这样的处理程序:
let accumulatedData = Buffer.alloc(0);
socket.on('data', data => {
// check for delimiter character
let offset = data.indexOf(0);
if (offset !== -1) {
// get the whole message into one Buffer
let msg = Buffer.concat(accumulatedData, data.slice(0, offset));
// put rest of data into the accumulatedData buffer as part of next piece of data
// skip past the delimiter
accumulatedData = data.slice(offset + 1);
// emit that we now have a whole msg
socket.emit('_msg', msg);
} else {
// no delimiter yet, just accumulate the data
accumulatedData = Buffer.concat(accumulatedData, data);
}
});
// if any accumulated data still here at end of socket
// notify about it
// this is optional as it may be a partial piece of data (no delimiter)
socket.on('end', () => {
if (accumulatedData.length) {
socket.emit('_msg', accumulatedData);
}
});
// this is my own event which is emitted when a whole message is available
// for processing
socket.on('_msg', msg => {
// code here to process whole msg
});
注意:此实现删除了消息末尾的分隔符