select
select 函数允许进程指示内核等待多个事件,并在有一个或多个时间或经历指定时间后唤醒进程。
1. select
#include <sys/select.h>
int select(int nfds, fd_set *restrict readfds, fd_set *restrict writefds,
fd_set *restrict errorfds, struct timeval *restrict timeout);
- 作用:指示内核等待多个事件,并在有一个或多个时间或经历指定时间后唤醒进程。
- 参数:
- nfds: 待测试的最大描述符 +1;
- readfds: 监听读的 fd 集合;
- writefds: 监听写得 fd 集合;
- errorfds: 监听异常的 fd 集合;
- timeout: 等待超时时间。
- NULL: 永远等下去;
- 非 NULL 且不为值 0: 等待指定时间;
- 非 NULL 且值为 0: 不等待,检查描述符后立即返回。(轮询)
- 返回:
- 返回:
- 成功:就绪描述符数目
- 超时:0
- 出错:-1
2. 描述符就绪条件
- 读就绪:
- 该套接字接收缓冲区的数据字节数
>=
套接字缓冲区低水位标记
的当前大小。- 高于水位,认为可读。
- 该套接字的读半部关闭 (接收了 FIN 的 TCP 连接)。
- 返回 EOF
- 该套接字是一个监听套接字且已完成的连接数不为 0。
- 此时 accept 通常不阻塞。
- 其上有一个套接字错误待处理。
- 读操作不阻塞、返回 -1,并设置 errno。
- 待处理错误,可通过 getsockoptvidkSO_ERROR 套接字获取并清除。
- 该套接字接收缓冲区的数据字节数
- 写就绪:
- 发送缓冲区的数据字节数
>=
套接字缓冲区低水位标记的当前大小。 - 该连接的写半部关闭。
- 此时写,会产生
SIGPIPE
信号。
- 此时写,会产生
- 使用非阻塞 connect 的套接字已建立连接,或者 connect 已经以失败告终。
- 其上有一个套接字错误待处理。
- 发送缓冲区的数据字节数
- 异常就绪:(?!)
- 套接字存在带外数据或者仍处于带外标记。
- 注意:当某个套接字上发生错误时,它将由 select 标记为
可读又可写
。
- 注意:当某个套接字上发生错误时,它将由 select 标记为
- 套接字存在带外数据或者仍处于带外标记。
- 汇总:
条件 可读吗? 可写吗? 异常吗? 有数据可读 + 关闭连接的读一半 + 给监听套接字准备好新连接 + 有可用于写得空间 + 关闭连接的写一半 + 待处理错误 + + TCP 带外数据 +
3. shutdown
终止网络连接的通常方法是 close 函数,但是 close 有两个限制:(可以用 shutdown 来避免)。
- close 把描述符的引用计数 -1,仅在计数为 0 时才关闭套接字。
- close 终止读和写两个方向的数据传送。
#include <sys/socket.h>
int shutdown(int sockfd, int howto);
-
作用:关闭网络连接。
-
参数:
- howto:
- SHUT_RD: 关闭连接的读一半。
- SHUT_WR: 关闭连接的写一半。
- SHUT_RDWR: 读写都关闭。(与调用两次 shutdown 等效。)
- howto:
-
返回:
- 成功:0
- 失败:Exxx 值
A. 注意、拓展
- timeval 结构允许指定微秒级的分辨率,然而内核支持的真实分辨率往往粗糙得多。
- timeval 结构能支持 select 不支持的值。
- 如果 select 的三个 fds 都设置为 NULL,则可以得到一个比 sleep 函数更为精确的定时器。
- 使用 select 的最常见错误:
- 忘记对最大描述符 +1;
- 忘记描述符集是
值——结果
参数。
- 接收低水位标记和发送低水位标记的作用是什么?
- 目的在于:允许应用进程控制在 select 返回可读/可写条件之前,有多少数据可读或有多大空间可用于写。
- 例如:如果我们知道除非至少存在 64 字节数据,否则我们的应用程序无事可做,那么可以将接受低水位标记设置为 64。防止过早唤醒。
- 任何 UDP 套接字只要其
发送低水位标记``<=
发送缓冲区大小,就总是可写的。(因为 UDP 不需要连接)