Linux socket非阻塞模式需完整实践逻辑:创建时可用SOCK_NONBLOCK标志(2.6.17+),或fcntl设置O_NONBLOCK;系统调用立即返回,须依返回值分支处理——recv返回EAGAIN/EWOULDBLOCK表示无数据,其他errno需错误处理;send可能部分发送,需循环或缓存;必须配合epoll等I/O多路复用,避免忙轮询。

Linux socket 非阻塞模式不是“加个标志就完事”,而是涉及创建、调用语义、错误处理和配套机制的一整套实践逻辑。核心在于:系统调用不再等待,必须主动判断返回值并做相应分支处理。
创建时直接启用非阻塞标志
Linux 2.6.17+ 内核支持在 socket() 调用中直接指定 SOCK_NONBLOCK,一步到位:
int sockfd = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0);- 该方式简洁安全,避免了创建后二次设置的竞态风险
- 注意:若需兼容老内核(
运行时动态切换非阻塞状态
对已存在的 socket(如监听套接字或已连接 fd),可用 fcntl 修改其行为:
- 先读取当前标志:
int flags = fcntl(sockfd, F_GETFL, 0); - 再添加非阻塞位:
fcntl(sockfd, F_SETFL, flags | O_NONBLOCK); - 要恢复阻塞,只需清除该位:
fcntl(sockfd, F_SETFL, flags & ~O_NONBLOCK); - 务必检查 fcntl 返回值,失败时 errno 可能为 EBADF 或 EINVAL
recv/send 的返回值必须严格判别
非阻塞 socket 下,成功、暂时无数据、真正出错三者共存于同一返回值形态中,不能只看 -1:
-
recv()返回 >0:有数据可读,正常处理 - 返回 0:对端已关闭连接(FIN)
- 返回 -1 且
errno == EAGAIN || errno == EWOULDBLOCK:缓冲区空,应稍后重试(非错误) - 返回 -1 且其他 errno(如 ECONNRESET、EPIPE):连接异常,需清理资源
-
send()同理:返回值可能小于请求长度,需循环发送或缓存未发数据
必须搭配 I/O 多路复用使用
单纯非阻塞 + 忙轮询(busy-poll)效率极低,实际工程中几乎总与 epoll / poll / select 结合:
- epoll_wait 返回就绪 fd 列表后,再对每个 fd 执行非阻塞 recv/send
- 避免“就绪但无数据”误判:例如 epoll 边缘触发(ET)模式下,必须一次性读完所有可用数据,直到 recv 返回 EAGAIN
- 不要在非就绪 fd 上盲目调用 recv —— 这会立即返回 EAGAIN,徒增开销



















