3

我遇到了这个令人讨厌的问题,即从 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 缓冲区或“窗口大小”有关,或者这里的服务器只是提供比操作系统或网络适配器或其他东西更快的数据可以处理它。但是我该如何检查并防止它发生呢?如果我减少每次使用 sc.write() 的消息长度,然后增加重复次数,我仍然会遇到同样的问题。这似乎只是短时间内数据量的问题。我也没有看到 sc.write() 抛出任何异常(我知道在上面的示例中我没有检查,但在我的测试中有)。

如果我能以编程方式检查它是否还没有准备好接收更多数据,我会很高兴,并延迟,等待它准备好。我也不确定是否“sc.socket().setSendBufferSize(1024*1024);” 有任何影响,或者如果我需要在 Linux 方面进行调整。有没有办法真正“冲洗”出 SocketChannel?作为一种蹩脚的解决方法,例如,我可以尝试在任何时候尝试发送超过 10KB 的消息时显式强制发送缓冲的任何内容(这在我的应用程序中并不常见)。但我不知道有什么方法可以强制发送缓冲区(或等到它发送)。谢谢你的帮助!

4

5 回答 5

6

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();
}
于 2009-01-31T17:47:38.803 回答
5

您没有检查以下的返回值:

sc.write(bb);

这将返回写入的字节数,这可能小于缓冲区中可用的数据。由于 nio 的工作方式,您可能只需要在您的字节缓冲区上调用 remaining() 来查看是否还有剩余。

于 2009-01-31T16:30:13.937 回答
3

我没有做过任何 NIO 编程,但根据 Javadocs,如果 SocketChannel 处于非阻塞模式(如你的那样)并且套接字的输出缓冲区已满,sc.write() 将不会写入整个 ByteBuffer。

因为你写得太快了,很可能你的连接被淹没了,你的网络或接收器无法跟上。

如果我能以编程方式检查它是否还没有准备好接收更多数据,我会很高兴

您需要检查 sc.write() 的返回值以了解您的输出缓冲区是否已满。

于 2009-01-31T16:28:24.190 回答
1

不要假设您可以控制哪些数据最终在哪个数据包中。

更多信息:监视套接字以获取新数据然后处理该数据的最佳方法是什么?

于 2009-01-31T18:15:30.947 回答
1

您正在使用非阻塞模式: sc.configureBlocking( false );

将阻塞设置为true,您的代码应该可以正常工作。此处其他人提出的检查发送计数和循环的建议也将起作用。

于 2009-03-25T13:01:18.013 回答