【Linux编程】IO复用之select详解

IO复用技术使得程序能够同时监听多个文件描述符,这对提高程序的性能至关重要。

Linux下实现IO复用的系统调用主要有select、poll和epoll,本文主要介绍select,后两个将在后续文章介绍。

尽量使select讲解的简单易懂,便于自己日后复习和有需要的新手朋友。


select系统调用

select系统调用的主要用途是:在指定的一段时间内,轮询监听用户感兴趣的文件描述符,即用户添加记录到fd_set中的文件描述符,当监听到任何文件描述符有可读,可写和异常事件发生时,就会返回。
轮询:即一遍又一遍的循环监听从0到我们记录的这些文件描述符中的最大值。
fd_set:是一个结构体,这个结构体中仅包含一个整型数组,这个整型数组的每个元素的每个位代表一个文件描述符,所以select能同时处理的文件描述符的最大数量由这个数组的总位数决定,这个最大值为头文件中定义的一个常量FD_SETSIZE。

select系统调用原型

#include 
int select(int nfd, fd_set *readfd, fd_set *writefd, fd_set *exceptfd, struct timeval *timeout)

1)nfd 参数指定了被监听的文件描述符的总数,它通常是readfdwritefdexceptfd这三个描述符集中的最大描述符编号加1,因为文件描述符是从0开始计数的。

2)readfdwritefdexceptfd分别指向可读,可写,异常事件对应的文件描述符集合。当一开始使用select函数时,我们会在这三个描述符集合中记录我们感兴趣的文件描述符,轮询等待这些描述符有事件产生,select调用返回时,原先我们记录的文件描述符就消失,然后内核会把有可读,可写或异常事件发生的文件描述符记录在这三个描述符集合中。至于事件发生的判断在文章后给出。

3)timeout参数用来设置select函数的超时时间,表示select愿意等待多长时间。它是一个timeval结构类型的指针:
struct timeval{
        long    tv_sec;     //秒数
        long    tv_usec;    //微秒数
};
当timeout==NULL时:select会无限等待,直到所监听的文件描述符有事件发生或被捕捉到一个信号。
当timeout->tv_sec==0 && timeout->tv_usec==0时:select根本不等待,测试所有指定的描述符并立即返回。
当timeout->tv_sec!=0 || timeout->tv_usec!=0时:等待指定的时间。在指定时间内有事件发生或捕捉到信号时会立即返回。

select系统调用的返回值

1)返回-1,表示出错,比如在select超时时间内捕捉到一个信号。
2)返回0,表示超时时间到了,但没有一个描述符有事件发生。此时readfdwritefdexceptfd三个描述符集会被内核置0。
3)返回值大于0,表示有事件发生的描述符总数,如果一个文件描述符同时发生了读写事件,则会对其计数两次。

fd_set类型的处理

由于对fd_set结构体里数组的每一位的操作过于麻烦,所以Linux定义了一系列宏来访问修改fd_set中的位:
#include 
FD_ZERO(fd_set *fdset);               //清除fdset的所有位
FD_SET(int fd, fd_set *fdset);        //设置fdset的位fd
FD_CLR(int fd, fd_set *fdset);        //清除fdset的位fd
FD_ISSET(int fd, fd_set *fdset);      //测试fdset的位fd是否被设置

文件描述符事件就绪条件

主要讨论网络编程中的事件就绪条件

socket可读事件就绪:

1)内核接收缓冲区中的字节数大于等于其低水位标记,该低水位标记由socket选项SO_RCVLOWAT选项来设置。此时我们可以无阻塞的读到数据,读操作返回的字节数大于0。
2)通信对方关闭了连接。此时读操作返回0。
3)服务端监听socket文件描述符上有新的连接请求。
4)socket上有未处理的错误。此时我们可以使用getsockopt来读取和清除该错误。

socket可写事件就绪:

1)内核发送缓冲区中的空闲字节数大于等于其低水位标记,该低水位标记由socket选项SO_SNDLOWAT选项来设置。此时我们可以无阻塞的写数据,写操作返回的字节数大于0。
2)socket的写操作被关闭。对写操作被关闭的socket执行写操作会触发一个SIGPIPE信号。
3)socket使用非阻塞connect连接成功或者失败(超时)之后。
4)socket上有未处理的错误。此时我们可以使用getsockopt来读取和清除该错误。

socket异常事件就绪:

对网络编程而言,异常情况只有一种:socket上接收到带外数据。会在后续文章中介绍。

select一个小例子

#include 
#include 
#include 
#include 
int main(){	
	fd_set fdset;
	char buf[100];
	while(1){
		FD_ZERO(&fdset);
		FD_SET(0,&fdset);
		int ret = select(1,&fdset,0,0,NULL);
                if(FD_ISSET(0, &fdset)){
		      ret=read(0, buf, 100);
                      printf("读到了%d个数据:%s", ret, buf);
                }
	}
        return 0;
}
 
   这个例子的作用就是,监听标准输入上的可读事件,当我们在键盘上输入数据时,就会触发可读事件,select就会返回,内核会清空fdset描述符集,然后在fdset描述符集合中设置有事件发生的描述符,即标准输入描述符0,接着用FD_ISSET判断文件描述符0是否在fdset中被设置。在while循环中每次select前都要重新记录fdset,因为每次select返回后,内核都会设置fdset描述符集。 
   

你可能感兴趣的:(Linux网络编程)