2009-01-31 67 views
3

我有这个令人讨厌的问题,即从Java(NIO)服务器(运行Linux)向客户端快速连续发送多个大型邮件将导致截断的数据包。消息必须很大并且发送得很快才能发生问题。这里的基本上是我的代码做什么(而不是实际的代码,但更多或更少发生了什么):Java NIO:快速发送大型邮件会导致截断的数据包和数据丢失

//-- 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的消息。但我不知道有什么办法强制发送缓冲区(或等到它发送完毕)。谢谢你的帮助!

回答

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(); 
} 
3

我还没有做过任何NIO编程,但根据Javadocs,如果SocketChannel处于非阻塞模式(与您的一样)并且套接字的输出缓冲区是sc.write(),则不会写入整个ByteBuffer充分。

因为你写得这么快,很可能是你的连接泛滥,你的网络或接收器跟不上。

我会很高兴,如果我可以以编程方式检查,如果还没有准备好更多的数据尚未

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

4

你是不是检查返回值:

sc.write(bb); 

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

+0

是的!期望能够通过网络以尽可能快的速度发送数据是不合理的。如果你没有阻止,你需要测试你的写入结果。 – erickson 2009-01-31 17:04:23

1

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

将阻塞设置为true并且您的代码应该按原样运行。其他人在此提出的检查发送计数和循环的建议也将起作用。