可以使用管道消除 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 管道中获取通过特征消除选择的特征名称?