我最近一直在用非阻塞套接字编写一个基于 Java NIO 的服务器,但在写出数据时遇到了一些问题。我现在知道在某些情况下,非阻塞写入无法写入 ByteBuffer 中的部分或全部字节。
我目前处理这种情况的方法是倒带或压缩缓冲区,然后尝试在下一次选择迭代中再次发送它。这怎么会导致显着的性能损失,我必须快速发送数据。
我曾尝试使用类似的东西:
ByteBuffer bb = ...;
SocketChannel sc = ...;
while(bb.remaining() > 0) {
sc.write(bb);
}
但问题在于它可能写入 0 个字节并仍然退出 while 循环。我不确定为什么,但似乎 write() - 方法将达到 ByteBuffer 的限制,无论它是否实际发送了所有字节。
我使用这种写法时遇到的另一个问题是,有时在重负载下会导致缓冲区溢出异常,即使我没有尝试阻塞写也是如此。
我迫切需要一些关于如何正确执行阻塞写入以及什么情况可能导致 SocketChannel.write(ByteBuffer) 溢出缓冲区的建议(当达到限制时它不应该停止吗?)。
提前致谢。
编辑:我仍然没有找到 sc.write(bb) 将缓冲区中的位置设置为 bb.limit() 的原因,即使它写入了 0 个字节。我唯一的办法仍然是在写入尝试失败后倒带缓冲区。
最佳答案
使用非阻塞 IO 时,您通常追求低延迟或高吞吐量。
不要通过压缩在缓冲区内移动数据,而是分配所需长度的缓冲区(通常是为了适应一条消息,单独的消息头)。并在将内容完全写入套接字后处理它们。如果您不能接受 GC,您可以使用 2 倍增长大小的缓冲区池。
如果吞吐量是您的主要关注点,那么不要尝试直接写入套接字,而是使用 Selector 注册套接字可写性,并在套接字可写时尝试写入。为此,您需要维护一个等待发送的缓冲区队列。保持注册套接字可写性,直到该队列变空。您最好使用 scatter/gather在这种情况下键入 IO,因为它会最大限度地减少应用程序中的系统调用数量。
write requester thread:
ioloop.submit(buffer[]{msgheaderbuf, msgbodybuf}, sock);
selector thread (ioloop):
submit(buffer[] bufs, socket sock):
queue.enqueue(bufs);
selector.register(sock, WRITABLE);
selector.wakeup();
如果延迟很重要,那么首先尝试直接写入套接字,只有在写入失败时才将数据排入队列并注册套接字可写性,如上所述。这将需要额外的互斥锁来保护套接字不被直接写入和从选择器线程内写入(由于可写性事件触发)。
write requester thread:
ioloop.submit(buffer[]{msgheaderbuf, msgbodybuf}, sock);
selector thread (ioloop):
submit(buffer[] bufs, socket sock):
size = sock.write(bufs);
while (!bufs.empty() && !bufs[0].remaining()): bufs.pop_front();
if (bufs.empty()) return;
queue.enqueue(bufs);
selector.register(sock, WRITABLE);
selector.wakeup();
关于java - NIO 阻塞写入不起作用,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/14249353/