在网络编程中,我们需要一种功能,即如果一个或多个I/O条件满足时,进程就被通知到,这能力就叫做I/O复用;

在Linux中,实现I/O复用的方法有几种,如多进程,多线程,信号驱动等,常见的是select函数(系统调用)实现;此函数允许进程指示的内核等待多个事件中的一个发生,并仅在一个或多个事件发生或经过指定时间后才唤醒进程。

#include<sys/select.h>

#include<unistd>

int select(int maxfd, fd_set *read_set, fd_set *write_set, fd_set except_set, const struct timeval *timeout);

maxfd 需要检测的文件描述符个数加1,即0~maxfd-1的描述符被检测;

select()的机制中提供一fd_set的数据结构,实际上是一long类型的数组,每一个数组元素都能与一打开的文件句柄建立联系,建立联系的工作由程序员完成,当调用select()时,由内核根据IO状态修改fd_set的内容,由此来通知执行了select()的进程哪些socket或文件可读可写。

read_set, write_set, except_set 参数分别对应需要检测的可读描述符集,可写描述符集,异常描述符集;

1.这个文件描述符集合监视文件集中的任何文件是否有数据可读,当select函数返回的时候,read_set将清除其中不可读的文件描述符,只留下可读的文件描述符

2.这个文件描述符集合监视文件集中的任何文件是否有数据可写,当select函数返回的时候,write_set将清除其中不可写的文件描述符,只留下可写的文件描述符

3. 这个文件集将监视文件集中的任何文件是否发生错误,其实,它可用于其他的用途,例如,监视带外数据OOB,带外数据使用MSG_OOB标志发送到套接字上。当select函数返回的时候,except_set将清除其中的其他文件描述符,只留下标记有OOB数据的文件描述符。

对于文件描述符集,Linux系统以宏的形式提供了简单的方法对他们进行操作:

void FD_ZERO(fd_set *fdset); 将fdset中所有位清零

void FD_SET(int fd, fd_set *fdset); 将fdset中的fd位开放;

void FD_CLR(int fd, fd_set *fdset); 将fdset中的fd位关闭

void FD_ISSET(int fd, fd_set *fdset); 检测fdset中fd位是否置位

注意事项

(1)对于可写性的检查,最好放在需要写数据的时候进行检查。如果和可读性放在同一个地方进行检查,那么select很可能每次都会因为可写性检查成功而返回。

(2)select()调用会清空传递给它的集合参数中的内容,也就是会清空read_set, write_set, except_set这三个指针参数所指定的描述符集合。因此,在每次调用select()之前,必须重新初始化并把需要监视的描述符填写到相应的描述符集合中。select()调用也会清空timeout指针所指向的struct timeval结构,所以在每次调用select()之前也要重新填充timeout指针所指向的struct timeval结构

timeout参数告诉内核等待一组指定的描述字中任一个准备好可花费多长时间,即select函数的最大等待时间。

#include<sys/time.h>

struct timeval

{long tv_sec;  秒数

long tv_usec;  毫秒数

};

timeout 有三种方法设置select函数的超时控制:NULL,select函数将一直阻塞,直到有文件描述符就绪,或者进程发生错误与收到某种信号时返回;0,select函数仅仅检测文件描述符集的状态,然后立即返回;非0,在指定的时间内事件没有发生,select函数超时返回(不阻塞);

如果将select函数的参数全部设为0,将和sleep函数功能一样

Select函数返回值:如果在程序监测的文件描述符集中有一个或多个文件描述符发生了事件,则返回发生事件的文件描述符个数,3个文件描述符集的内容已经被设置成发生事件的文件描述符。

正值:表示监视的文件集中有文件描述符符合要求

零值:表示select监视超时

负值:表示发生了错误,错误值由errno指定

accept函数的非阻塞实现

    当服务器程序调用了listen函数后,此时就可以接受客服端的connect请求,3次握手后,服务器把建立的连接存入队列中,等待accept函数的调用。每调用一次accept,程序就从队列中取出一个已建立连接的socket。默认方式下,accept处于阻塞状态,将套接字文件属性设置为非阻塞时,accept处于非阻塞状态(其实与该套接字相关的系统调用都是非阻塞的了)。

    如果将正在listen的socket设置到read_set中,调用select,如果有客户端connect,select将返回正值,通过宏FD_ISSET可检测到该socket可读,此时再用accept接受新的socket,进行读写。

connect函数的非阻塞实现(TCP

    a. 将打开的socket设为非阻塞的。

    b. 发connect调用,这时返回-1,但是errno被设为EINPROGRESS,意即connect仍旧在进行还没有完成。

    c. 将打开的socket放进被监视的可写(注意不是可读)文件集合中,用select进行监视,如果可写,用getsockopt 函数来得到error的值,如果为零则connect成功。

select检测对方Socket连接关闭

    使用select监视是否有数据可读,当监视到可读返回正值后,使用recv函数对套接字进行读操作,recv函数返回正值表示正常的读操作,若返回0或者-1表示对方连接已关闭。

    另外关于主动写Socket时对方突然关闭连接的处理,则可以简单地捕捉信号SIGPIPE并作出相应关闭本地Socket等等的处理。SIGPIPE的解释是:写入无读者方的管道。