Linux下必须用fcntl对每个socket fd(包括accept返回的新fd)单独设置非阻塞,Windows下必须用ioctlsocket;非阻塞connect需检查EINPROGRESS/WSAEWOULDBLOCK并监听可写事件后调getsockopt(SO_ERROR)确认成功;非阻塞IO必须配合select/poll/epoll等事件通知机制。

Linux 下用 fcntl 设置非阻塞是唯一可靠方式;Windows 下必须用 ioctlsocket,混用或漏设都会导致 connect、accept、recv 等调用行为失控。
Linux:用 fcntl 设置非阻塞,但不能只设一次
很多人在 socket() 后调一次 fcntl(fd, F_SETFL, O_NONBLOCK) 就以为万事大吉——这是最大误区。实际中:
-
accept()返回的新 socket fd 默认仍是阻塞的,必须对每个新 fd 单独调用fcntl - 正确写法是先读当前标志:
int flags = fcntl(fd, F_GETFL, 0),再按位或设置:fcntl(fd, F_SETFL, flags | O_NONBLOCK),否则会清掉O_APPEND等已有标志 - 对普通磁盘文件 fd 设置
O_NONBLOCK无效(read/write仍阻塞),仅对 socket、pipe、tty 等设备类 fd 有意义 - 失败时检查
errno:EBADF表示 fd 已关闭,EINVAL表示该 fd 不支持非阻塞(如某些老旧字符设备)
Windows:必须用 ioctlsocket,WSASetBlockingHook 不可用
Windows 没有 fcntl,也不能用 setsockopt(SO_NONBLOCK)(该选项根本不存在)。唯一标准方式是:
- 调用
ioctlsocket(sock, FIONBIO, &nonBlocking),其中nonBlocking是unsigned long类型,值为1(启用)或0(禁用) - 返回值为
SOCKET_ERROR时,用WSAGetLastError()获取错误码:WSAENOTSOCK表示句柄非法,WSAEINVAL表示参数错误 - 注意:
FIONBIO是 Windows 特有宏,不可与 Linux 的O_NONBLOCK混淆;跨平台封装时建议用预处理器区分
connect() 在非阻塞模式下返回 EINPROGRESS 或 WSAEWOULDBLOCK 是正常现象
阻塞模式下 connect() 成功才返回 0;非阻塞下它几乎总“失败”,但这不表示出错:
立即学习“C++免费学习笔记(深入)”;
- Linux:返回 -1 且
errno == EINPROGRESS→ 连接正在后台进行,需用select()或poll()监听该 socket 的可写事件(POLLOUT) - Windows:返回
SOCKET_ERROR且WSAGetLastError() == WSAEWOULDBLOCK→ 同样需监听可写事件 - 判断连接是否真正成功:可写事件触发后,调用
getsockopt(..., SO_ERROR, ...)检查错误值是否为 0;若为 0 才算连上 - 直接忽略这个状态、或当成错误关闭 socket,会导致所有异步连接被误杀
非阻塞 IO 必须搭配事件通知机制,不能裸跑轮询
设成非阻塞只是第一步。如果每次 recv() 都返回 EAGAIN 就立刻重试,CPU 会 100% 占用:
-
select()、poll()、epoll_wait()是配套必需品:它们告诉你“哪个 fd 此刻可以安全read或write”,之后再调非阻塞系统调用捞数据 - 即使
select()返回可读,recv()仍可能返回EAGAIN—— 因为内核状态在两次系统调用之间可能变化(尤其多线程环境),必须把EAGAIN当作流程分支处理,而非错误 - 用
poll()替代select()可避开FD_SETSIZE限制(默认通常 1024),且无需每次重置fd_set
最容易被忽略的是:每个新 accept 出来的 fd 都得重新走一遍非阻塞设置流程;还有就是 connect() 后不检查 SO_ERROR 就发数据,大概率触发 Broken pipe 或静默丢包。


















