【发布时间】:2015-06-26 12:28:19
【问题描述】:
现在这个问题困扰了我一段时间。
在我工作的应用程序中,我在非阻塞模式下使用 SocketChannel 与嵌入式设备进行通信。 现在我收到偶尔损坏的数据。 在某些 PC 上它不会发生,现在它发生在我的身上。 但是当我在程序中改变太多时,问题就消失了。
这么多可能会产生影响。时机,网络接口硬件,win7,java版本,公司防火墙,...
数据读取归结为这段代码:
byteBuffer.compact();
socketChannel.read(byteBuffer); // <<< problem here ?
byteBuffer.flip();
if( byteBuffer.hasRemaining() ){
handleData( byteBuffer );
}
This is run in the same thread as the writing, when the selector wakes up and the interest op OP_READ is set.
此代码是唯一引用 byteBuffer 的地方。写入时,socketChannel 仅在同一线程中使用。
我对代码进行了检测,因此当错误发生时,我可以打印出最后几个 read() 调用的内容。同时我分析了Wireshark上的网络流量。我添加了很多断言来检查字节缓冲区的完整性。
在 Wireshark 中,接收到的流看起来不错。没有 DUP-ACK 或其他可疑的东西。最后一次 read() 调用与 Wireshark 中的数据完全匹配。
在 Wireshark 中,我看到许多小型 TCP 帧以 10 毫秒的间隔接收 90 字节的有效载荷数据。通常,Java 线程在数据刚到达时也会读取所有 10 毫秒的数据。
当涉及到问题时,Java 线程有点延迟,因为读取发生在 300 毫秒后,读取返回大约 3000 字节,这是合理的。但数据已损坏。
数据看起来像,如果它被复制到缓冲区并且同时接收的数据已经覆盖了第一个数据。
现在我不知道如何继续。我无法创建一个小例子,因为这种情况很少发生,而且我不知道需要的确切条件。
谁能给个提示?
如何证明它是不是Java lib?
哪些条件也很重要?
谢谢 弗兰克
2015 年 6 月 29 日:
现在我能够构建一个用于复制的示例。
Sender 正在使用阻塞 IO,首先等待连接,然后每 2ms 发送 90 个字节的块。前 4 个字节是运行计数器,其余不设置。 Sender 使用 setNoTcpDelay(true)。
接收器正在使用非阻塞 IO。首先它连接到发送器,然后在选择键准备好时读取通道。有时,读取循环会执行 Thread.sleep(300)。
如果它们通过环回在同一台 PC 上运行,这对我来说一直有效。如果我将 Sender 放在另一台 PC 上,直接通过 LAN 连接,则会触发错误。用 Wireshark 检查,流量和发送的数据看起来不错。
要运行,首先在一台 PC 上启动 Sender,然后(在编辑主机地址后)启动 Receiver。
只要它有效,它大约每 2 秒打印一行。如果失败,它会打印有关最近 5 次 read() 调用的信息。
我发现是什么触发:
- 发送方已配置setNoTcpDelay(true)
- 接收方有时在执行 read() 之前有一个 Thread.sleep(300)。
谢谢 弗兰克
【问题讨论】:
-
虽然 Java 中可能存在错误,但这种可能性极小……鉴于大量其他 Java 8 程序员(显然)没有遇到此类问题。它更有可能是您的代码中的错误。该怎么办?好吧,如果您的代码太难简化为 MCVE,并且太大而无法向我们展示,那么最好的办法是让同事帮助您。
-
如果代码全部在一个线程上执行,那么它不太可能是正常意义上的“竞态条件”。 (我不知道这是否有帮助......)
-
从您提到的情况来看 - 可能存在问题 - 您使用数据的方式。这只是一个猜测。出于某种原因,您提到的内容未解释 - 当读取数据的 java 线程在 300 毫秒后被安排时,您收到了很多 90 字节的数据包背靠背。如果您假设以 90 字节的块读取它们,请明确以 90 字节读取。然后消耗更多 - 当它们超过 90 个字节时。我不知道确切的 API,但可能这应该指向某个方向?
-
也许问题是你读到
buf而不是byteBuffer?说真的,我们应该如何在不知道您对缓冲区的真正用途的情况下为您提供帮助? IE。handleData( byteBuffer )是做什么的? -
但是当我在程序中改变太多时,问题就消失了。这是你能得到的最模棱两可和不具体的。
标签: java multithreading tcp java-8 nonblocking