Java中的非阻塞套接字写入与阻塞套接字写入比较

6
有人为什么会更喜欢阻塞写而不是非阻塞写呢?我理解的是,只有在希望在写入方法返回后确保对方收到TCP数据包时,才需要使用阻塞写。但我甚至不确定这是否可能。您需要刷新并且必须清空底层操作系统写套接字缓冲区。那么,非阻塞套接字写入有什么劣势吗?从性能角度来看,底层写套接字缓冲区越大,是否不好呢?我的理解是,底层套接字写缓冲区越小,就越容易遇到慢速/有问题的客户端,在应用程序级别下,必须在底层套接字缓冲区已满且isWritable()返回false时丢弃/排队数据包。
2个回答

7
我的理解是,只有在确保写入方法返回后另一端收到TCP包时,您才需要阻塞式写入。
您的理解是不正确的。它并不能确保这一点。
阻塞式写入会阻塞,直到所有数据已传输到套接字发送缓冲区,并异步传输到网络中。如果读取方较慢,他的套接字接收缓冲区将会填满,这最终会导致您的套接字发送缓冲区也填满,从而导致一个阻塞式写入被阻塞,从而阻塞整个线程。非阻塞IO提供了一种检测和处理这种情况的方式。

简而言之,如果采用阻塞式写入,您将会被阻塞;如果采用非阻塞式写入,则可以使用isWritable()调用来查找基础套接字写缓冲区是否已满,并在不阻塞的情况下决定要执行什么操作。因此,对于我的问题(虽然有些危险),答案是:非阻塞式写入比阻塞式写入更好。您拥有更多控制权和相同的性能。 - chrisapotek
@achrisapotek 精确地说,isWritable() 意味着最后一次 select() 触发了 OP_WRITE 事件,并且在此时套接字发送缓冲区中有空间。对于你的问题,答案是非阻塞写不会阻塞。这是否更好取决于你的需求。 - user207421
如果在触发OP_WRITE时有空间,稍后在循环内部isWritable()返回true时仍会有空间。除非有另一个线程正在写入,这几乎没有意义。很难想象出阻塞写入更可取的情况。当然,你总是可以想出一个,但非阻塞写入给你选择做什么的选项:等待(阻塞),排队数据包,丢弃数据包或杀死客户端。那样更好!!! - chrisapotek
2
@chrisapotek。 (1) 缓冲区有空间 (2) OP_WRITE 触发了 select() (3) isWritable() 返回 true (4) 你进入了那个循环 (5) 第一次写入填满了套接字发送缓冲区 (5) ByteBuffer 中还有更多数据,所以 (6) 你再次迭代循环,写入返回零,与 (5) 中相同数量的数据仍然存在,所以 (7) 你再次迭代循环... 阻塞写入优选的情况是当你对每个客户端使用一个线程时。 - user207421

3
非阻塞写的问题在于,如果写操作未完成,可能没有任何有用的事情可做。你可能会陷入类似以下的循环:
// non-blocking write
while(bb.remaining() > 0) sc.write(bb);

或者

// blocking write
sc.write(bb);

第一个方法可能会占用CPU,而第二个方法则更可取。

最大的问题在于读取。一旦您决定是要使用阻塞或非阻塞读取,您的写入方式也必须相同。不幸的是,无法将它们设置为不同的方式。如果您想使用非阻塞读取,则必须使用非阻塞写入。


select()OP_WRITE是“无事可做”的解决方案。 - user207421
使用选择器进行非阻塞写操作可能更有效率。然而,在我看来,这种情况应该非常罕见,可能并不重要。 - Peter Lawrey
@chrisapotek 这完全是自相矛盾的。select() 在 OP_WRITE 上返回 意味着 套接字发送缓冲区中有空间。没有别的。 - user207421
@EJP:所以Peter描述的循环不可能发生,因为键不会在缓冲区满时被选择。如果调用isWritable()并返回true,则可以进行写入,否则必须排队或丢弃,由您决定。 - chrisapotek
@chrisapotek 循环的一次或多次迭代后,缓冲区可能已满。您还需要明确isWritable()本身不会查看缓冲区状态,只有前面的select()才会这样做。 - user207421
显示剩余6条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接