有时,当通过 SocketChannel.write() 发送大量数据时,底层 TCP 缓冲区会被填满,我必须不断重试 write() 直到数据全部发送完毕。
所以,我可能会有这样的事情:
public void send(ByteBuffer bb, SocketChannel sc){
sc.write(bb);
while (bb.remaining()>0){
Thread.sleep(10);
sc.write(bb);
}
}
问题在于,大 ByteBuffer 和底层 TCP 缓冲区溢出的偶尔问题意味着对 send() 的调用将阻塞一段意外的时间。在我的项目中,有数百个客户端同时连接,一个套接字连接引起的延迟可能会使整个系统陷入困境,直到解决一个 SocketChannel 的延迟。当发生延迟时,可能会导致项目其他区域速度减慢的连锁反应,因此低延迟非常重要。
我需要一个解决方案,能够透明地解决 TCP 缓冲区溢出问题,并且在需要多次调用 SocketChannel.write() 时不会导致所有内容阻塞。我考虑过将 send() 放入一个扩展 Thread 的单独类中,以便它作为自己的线程运行并且不会阻塞调用代码。但是,我担心为我维护的每个套接字连接创建线程所需的开销,尤其是 99% 的情况下,SocketChannel.write() 第一次尝试就会成功,这意味着不需要线程在那里。 (换句话说,只有在使用 while() 循环时才需要将 send() 放在单独的线程中——仅在存在缓冲区问题的情况下,也许是 1% 的时间)如果存在缓冲区问题只有 1% 的情况下,其他 99% 的 send() 调用我不需要线程开销。
我希望这是有道理的......我真的可以使用一些建议。谢谢!
您不需要 sleep(),因为写入将立即返回或阻塞。 如果第一次没有写入,您可以有一个执行器将写入传递给它。 另一种选择是使用一个小的线程池来执行写入。
但是,最好的选择可能是使用选择器(如建议的那样),以便您知道套接字何时准备好执行另一次写入。
我对 Java NIO 的了解越多,它就越让我兴奋。不管怎样,我认为这篇文章解决了你的问题......
http://weblogs.java.net/blog/2006/05/30/tricks-and-tips-nio-part-i-why-you-must-handle-opwrite
听起来这家伙有一个比睡眠循环更优雅的解决方案。
我很快就得出结论,单独使用 Java NIO 太危险了。在可能的情况下,我想我可能会使用 Apache MINA,它在 Java NIO 之上提供了一个很好的抽象,并且带来了一些小“惊喜”。
在 Java NIO 之前,您必须为每个套接字使用一个线程以获得良好的性能。这对于所有基于套接字的应用程序来说都是一个问题,而不仅仅是 Java。所有操作系统都添加了对非阻塞 IO 的支持来克服这个问题。 Java NIO 实现基于
Selectors
。
请参阅 Ron Hitchens 的 Java NIO 和这篇 On Java 文章来开始使用。但请注意,这是一个复杂的主题,它仍然会给您的代码带来一些多线程问题。谷歌“非阻塞 NIO”了解更多信息。
使用 NIO,您可以在
OP_WRITE
中注册选择键的兴趣,当有空间写入更多数据时,您会收到通知。
sc.write(bb);
if(sc.remaining() == 0)
//we're done with this buffer, remove it from the select set if there's nothing else to send.
else
//do other stuff/return to select loop
Java NIO 书包含了这一切。
还:
http://www.exampledepot.com/egs/java.nio/NbServer.html?l=rel
一些在线研究让我相信 NIO 相当矫枉过正,除非你有很多传入连接。否则,如果它只是一些大型传输 - 那么只需使用写入线程。它可能会有更快的响应。许多人对蔚来汽车没有按照他们想要的速度回复感到不满。由于您的写入线程本身会阻塞,因此不会对您造成伤害。