第五章第一节 select系统调用
前言
I/O复用使得程序同时监听多个文件描述符。通常,网络程序在下列情形下需要使用I/O复用技术。
- 客户端要同时处理多个socket;
- 客户端要同时处理用户输入与网络连接;
- TCP服务器要同时处理监听socket与连接socket;
- 服务器要同时处理TCP请求与UDP请求;
- 服务器要同时监听多个端口,或处理多个服务;
I/O复用虽然同时监听多个文件描述符,但它本身是阻塞的。
select系统调用
在一段指定时间内,监听用户感兴趣的文件描述符上的可读、可写与异常等事件。select系统调用原型如下。
1 | /* |
其中,fd_set结构体定义如下。
1 | typedef struct{ |
fd_set是一个__fd_mask数组,数组中每个元素的每一位标记一个文件描述符,因此select能同时处理的文件描述符的总量不能超过1024。可使用如下宏来访问fd_set结构体的位。
1 | /* |
此外,timeval结构体定义如下。
1 | struct timeval{ |
注意:在select调用失败时,timeout的值是不确定的。
select给我们提供了一个微妙级的定时方式,若向timeout变量的tv_sec成员与tv_usec成员传递0,则select将立即返回。若timeout设置为NULL,则select将一直阻塞直到某个文件描述符就绪。
若程序在select等待期间接收到信号,则select立即返回-1,并设置errno为EINTR。
由于内核修改了3个fd_set,因此网络程序下次调用select之前需要重置这3个fd_set。
文件描述符就绪条件
网络编程中,如下情况socket可读。
- socket内核接收缓冲区中的字节数大于等于其低水位标记SO_RCVLOWAT,此时程序可无阻塞地读这个socket,读操作返回的字节数大于0;
- socket通信的对方关闭连接,对socket的读操作返回0;
- 监听socket上有新的连接请求;
- socket上有未处理的错误,调用getsockopt来读取错误;
如下情况socket可写。
- socket内核发送缓冲区中的空闲字节数大于等于其低水位标记SO_SNDLOWAT,此时程序可无阻塞地写这个socket,写操作返回的字节数大于0;
- socket写操作被关闭(对写操作被关闭的socket执行写操作将触发一个SIGPIPE信号);
- socket使用非阻塞connect连接成功或失败(超时)后;
- socket上有未处理的错误,调用getsockopt来清除错误;
socket能处理的异常情况只有一种:socket上接收了带外数据。
处理带外数据
socket接收到普通数据与带外数据都将使select返回,但socket处于不同的就绪状态:前者处于可读状态(FD_ISSET(fd, rfds)为1)、后者处于异常状态(FD_ISSET(fd, efds)为1)。