可以使用管道消除 TCP 碎片吗?

Posted

技术标签:

【中文标题】可以使用管道消除 TCP 碎片吗?【英文标题】:Can TCP fragmentation be eliminated using a Pipe? 【发布时间】:2013-12-11 22:05:06 【问题描述】:

TCP 网络消息可以分段。但是碎片消息很难解析,尤其是在传输超过一个字节的数据类型时。例如,如果 long 的某些字节我希望在第二个缓冲区中结束,buffer.getLong() 可能会失败。

如果可以即时重新组合多个 Channel,解析会容易得多。所以我想通过java.nio.channels.Pipe发送所有数据。

// count total length
int length = 0;
foreach (Buffer buffer: buffers) 
  length += buffer.remaining()


// write to pipe
Pipe pipe = Pipe.open();
pipe.sink().write(buffers);

// read back from pipe
ByteBuffer complete = ByteBuffer.allocateDirect(length)
if (pipe.source().read(complete) != length) 
  System.out.println("Fragmented!")

但这能保证完全填满缓冲区吗?或者管道会再次引入碎片吗?换句话说,条件的主体会达到吗?

【问题讨论】:

【参考方案1】:

TCP 碎片与您遇到的问题无关。流源上的 TCP 堆栈正在将对于单个数据包来说太大的消息分成多个数据包,并且它们正在到达并重新组合,可能与您期望的 long 不对齐。

无论如何,您将相当于字节数组(ByteBuffer)的内容视为输入流。您正在告诉 JVM 将“缓冲区中的其余内容”读入ByteBuffer。同时,long 的后半部分现在位于网络缓冲区中。您现在尝试通读的ByteBuffer 将永远不会拥有long 的其余部分。

考虑使用Scanner 读取long,它将阻塞直到可以读取long。

Scanner scanner= new Scanner(socket.getChannel());
scanner.nextLong();

还可以考虑使用DataInputStream 来读取long,尽管我无法判断它是否会阻塞,直到根据文档读取整个long

DataInputStream dis = new DataInputStream(socket.InputStream);
dis.readLong();

如果您可以控制服务器,请考虑使用 flush() 来防止您的数据包被缓冲并“分段”发送,或者使用 ObjectOutputStream/ObjectInputStream 作为更方便的 IO 方式。

【讨论】:

它确实会阻塞,直到收到一个完整的 long。 Channels 和 Buffers 的全部意义不在于替换旧的Stream-API 吗?所以回退到InputStream 对我来说似乎有些过时,尤其是当我想使用带有Selector 的异步通道时。与发送原始基本数据类型相比,对象序列化类将增加可避免的开销。 我添加了使用可与频道一起使用的扫描仪的建议。【参考方案2】:

没有。 Pipe 旨在由一个线程写入并由另一个线程读取。内部缓冲区只有 4k。如果你写的比它多,你就会停滞不前。

除了作为演示之外,它们实际上并没有太大用处。

我不明白:

例如,如果我期望的 long 的某些字节最终在第二个缓冲区中,则 buffer.getLong() 可能会失败。

什么第二个缓冲区?您应该在通道的生命周期内使用相同的接收缓冲区。将其作为SelectionKey 的附件,以便您在需要时可以找到它。

这个我也不明白:

如果可以动态重组多个 Channel,解析会容易得多

当然你的意思是多个缓冲区,但基本思想是首先只有一个缓冲区。

【讨论】:

使用相同的缓冲区确实是最简单的策略。但是,当在流中出现终止符号之前消息长度未知时,我无法预先分配缓冲区,并且必须在第一个缓冲区满后立即回退分配新缓冲区。 当 TCP 对消息进行分段时,它可能会在任何字节之后分段,也可以在流上推送的 long 之间进行分段。这就是为什么如果 long 未完全收到,getLong() 可能会以 BufferUnderflowException 失败。 @XZS 你肯定知道消息的最大长度是多少吗?我不知道您的第二条评论应该告诉我我还不知道的内容,除非它证明您不能使用两个缓冲区,因为 long 可能会在它们之间分开。基本问题是你必须不断地读入同一个缓冲区,直到你拥有你需要的一切,然后解析它。 不,消息的长度未知。一个空字节被放置在流上以表示它的结束。在遇到此符号之前,必须读取更多数据。当我不知道它需要多长时间时,如何分配一个足够大的缓冲区来存储整个消息?

以上是关于可以使用管道消除 TCP 碎片吗?的主要内容,如果未能解决你的问题,请参考以下文章

防止 Apache Beam / Dataflow 流 (python) 管道中的融合以消除管道瓶颈

如何在 sklearn 管道中获取通过特征消除选择的特征名称?

自动打开命名管道和 tcp\ip

为啥两个 HTTP 和 TCP 地址可以使用同一个端口,而两个 IPC 地址不能使用同一个命名管道?

管道编写器可以判断阅读器何时阻塞吗?

您可以制作自己的渲染管道吗?