我遇到了一个令人讨厌的问题,即从Java(NIO)服务器(运行Linux)向客户端快速连续发送多个大型消息会导致数据包被截断。消息必须很大并且必须非常迅速地发送,以使问题发生。这基本上是我的代码正在做的事情(不是实际的代码,而是或多或少发生的事情):

//-- setup stuff: --
Charset charset = Charset.forName("UTF-8");
CharsetEncoder encoder = charset.newEncoder();
String msg = "A very long message (let's say 20KB)...";

//-- inside loop to handle incoming connections: --
ServerSocketChannel ssc = (ServerSocketChannel)key.channel();
SocketChannel sc = ssc.accept();
sc.configureBlocking(false);
sc.socket().setTcpNoDelay(true);
sc.socket().setSendBufferSize(1024*1024);

//-- later, actual sending of messages: --
for (int n=0; n<20; n++){
  ByteBuffer bb = encoder.encode(CharBuffer.wrap(msg+'\0'));
  sc.write(bb);
  bb.rewind();
}

因此,如果数据包足够长并且尽快发送(即在没有延迟的情况下这样的循环中),那么在另一端,它通常会发出如下信息:
[COMPLETE PACKET 1]
[COMPLETE PACKET 2]
[COMPLETE PACKET 3]
[START OF PACKET 4][SOME OR ALL OF PACKET 5]

发生数据丢失,数据包开始一起运行,因此数据包5的开始(在本示例中)到达的消息与数据包4的开始相同。它不仅被截断,还一起运行消息。

我想这与TCP缓冲区或“窗口大小”有关,或者这里的服务器提供数据的速度比OS,网络适配器或某些设备可以处理的速度更快。但是,如何检查并防止它发生呢?如果我减少每次使用sc.write()的消息的长度,但是增加重复次数,我仍然会遇到相同的问题。这似乎只是短时间内的数据量问题。我也没有看到sc.write()抛出任何异常(我知道在上面的示例中我没有检查,但是在我的测试中)。

如果能以编程方式检查它是否尚未准备好用于更多数据,并延迟一下,等到准备好,我将感到高兴。我也不确定“sc.socket()。setSendBufferSize(1024 * 1024);”有任何效果,或者是否需要在Linux方面进行调整。有没有一种方法可以真正“清空” SocketChannel?作为一种me脚的解决方法,例如,当我尝试发送大于10KB的消息时,我可以尝试显式强制完全发送缓冲的所有内容(在我的应用程序中通常不是如此)。但是我不知道有什么方法可以强制发送缓冲区(或等待直到缓冲区发送完毕)。谢谢你的帮助!

最佳答案

sc.write()无法发送部分或全部数据的原因有很多。您必须检查返回值和/或缓冲区中剩余的字节数。

for (int n=0; n<20; n++){
  ByteBuffer bb = encoder.encode(CharBuffer.wrap(msg+'\0'));
  if(sc.write(bb) > 0 && bb.remaining() == 0) {
    // all data sent
  } else {
    // could not send all data.
  }
  bb.rewind();
}

08-04 16:27