阻塞/非阻塞socket的设计思想

1. 核心行为区别

阻塞 recv 和非阻塞 recv 的区别是:如果没有任何可读取的数据,阻塞 recv 会阻塞等待直到有数据可读(1 Byte 也行),非阻塞 recv 会立即返回 -1 并将错误码设置为 EAGAIN(或 EWOULDBLOCK)。

  • recv 的区别在于没有数据可读的时候。

阻塞 send 和非阻塞 send 的区别是:如果缓冲区的空间不足以发送全部数据,阻塞 send 会阻塞直到数据全部被写入缓冲区,非阻塞 send 会尽可能多地写入数据,然后返回。

  • send 的区别在于缓冲区空间不足以写入的时候。

2. 为什么会有区别?

因为 recv 是被动的,而 send 是主动的。 对于 recv,如果不赶紧返回给用户,程序可能就卡死了,因为不知道剩下的数据什么时候来。 对于 write,如果用户要写长数据比如 2000 bytes,阻塞模式下内核认为用户有足够的耐心等待缓冲区排空,所以 write 承诺“要么不返回,返回就一定帮你写入完”。 而非阻塞 write 则把控制权交给了用户,哪怕只发送了 1 byte 的数据,也会返回并告知用户。

从另一个角度看,recv 的设计是为了应对不确定性,而 send 的设计是为了应对背压 (Backpressure)

3. 设计思想解析

这种设计体现了什么设计思想呢?

3.1 契约式设计 (Design by Contract)

阻塞和非阻塞本质上是内核与应用程序之间达成的不同契约。

  • 阻塞模式是“简单契约”:内核承诺“没做完我不会返回”。其核心思想是隐藏复杂性,代价是需要付出等待时间。
  • 非阻塞模式是“性能契约”:内核承诺“能做多少做多少,不能做也会及时告知”。核心思想是暴露复杂性,进而让应用程序有机会优化并发性能。

3.2 责任划分与控制反转

在阻塞模式下,等待的责任在内核,内核负责挂起和唤醒线程; 在非阻塞模式下,等待的责任转移到了用户态。 这体现了控制反转 (IoC) 思想,将等待的责任从内核转移到了用户层,由用户逻辑(配合 epoll 等多路复用技术)来决定如何高效利用等待时间。

设计思想从“我要读数据”变成了“请在有数据可读时通知我”。