SocketChannel.write() 的 Java NIO 线程问题

问题描述 投票:0回答:6

有时,当通过 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() 调用我不需要线程开销。

我希望这是有道理的......我真的可以使用一些建议。谢谢!

java sockets nio
6个回答
1
投票

您不需要 sleep(),因为写入将立即返回或阻塞。 如果第一次没有写入,您可以有一个执行器将写入传递给它。 另一种选择是使用一个小的线程池来执行写入。

但是,最好的选择可能是使用选择器(如建议的那样),以便您知道套接字何时准备好执行另一次写入。


1
投票

我对 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 之上提供了一个很好的抽象,并且带来了一些小“惊喜”。


1
投票

在 Java NIO 之前,您必须为每个套接字使用一个线程以获得良好的性能。这对于所有基于套接字的应用程序来说都是一个问题,而不仅仅是 Java。所有操作系统都添加了对非阻塞 IO 的支持来克服这个问题。 Java NIO 实现基于

Selectors

请参阅 Ron Hitchens 的 Java NIO 和这篇 On Java 文章来开始使用。但请注意,这是一个复杂的主题,它仍然会给您的代码带来一些多线程问题。谷歌“非阻塞 NIO”了解更多信息。


0
投票
对于数百个连接,您可能不需要费心使用 NIO。好的老式阻塞套接字和线程就可以了。

使用 NIO,您可以在

OP_WRITE

 中注册选择键的兴趣,当有空间写入更多数据时,您会收到通知。


0
投票
假设您已经有一个使用循环 选择器.select();确定哪些套接字已准备好进行 I/O。

    创建套接字通道后将其设置为非阻塞,sc.configureBlocking(false);
  • 写入(可能是部分)缓冲区并检查是否还有剩余内容。缓冲区本身负责当前位置和剩余量。
类似

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

    摆脱休眠的 while 循环

0
投票
我现在面临着一些同样的问题:


    如果您有少量连接,但传输量较大,我只需创建一个线程池,并让写入线程阻塞。

  • 如果你有很多连接,那么你可以使用完整的Java NIO,并在accept()ed套接字上注册OP_WRITE,然后等待选择器进入。
Ron Hitchens

Java NIO 书包含了这一切。 还: http://www.exampledepot.com/egs/java.nio/NbServer.html?l=rel
一些在线研究让我相信 NIO 相当矫枉过正,除非你有很多传入连接。否则,如果它只是一些大型传输 - 那么只需使用写入线程。它可能会有更快的响应。许多人对蔚来汽车没有按照他们想要的速度回复感到不满。由于您的写入线程本身会阻塞,因此不会对您造成伤害。

© www.soinside.com 2019 - 2024. All rights reserved.