I/O复用-Select函数

I/O复用的意义

为了构建并发服务器,一般需要为每个客户端连接都创建一个进程进行通信,不过创建和维护进程的开销是巨大的,需要大量的运算和内存空间,每个进程都有独立的内存空间,所以相互交换数据也要求采用相对复杂的方法,比如管道等。

这时I/O复用就能有效的改善这种情况,他可以使一个进程同时为多个客户端提供服务,当然这里所说的同时并不是真正意义上的同时发生。

Select实现复用服务器端

实现原理

select函数可以将多个文件描述符集中在一起统一监视。根据函数返回的值找出发生事件的描述符。

函数详解

select函数使用方法与一般函数区别较大,定义如下:

int select(int maxfd, fd_set *readset, fd_set *writeset, fd_set *exceptset,

const struct timeval *timeout);

/* 成功时返回大于0的值,为几代表有几个套接字状态发生了变化,失败返回 -1 */

 

参数中有3个fd_set数组,这个数组是存有0和1的位数组,readset、writeset、exceptset这三个分别代表对套接字数组监视的事件,如readset数组中值如下:

readset

| 0 | 1 | 0 | 0 | 1 |

 

表明select函数监听了套接字1和4的读事件,也就是说如果套接字1和4变得可读,那么select就会成功返回。另外两个数组道理也是一样。

处理fd_set位数组,c已经提供了基础的函数:

位操作函数

FD_ZERO (fd_set *fdset); /* 将数组位全部置为0 */

FD_SET (int fd ,fd_set *fdset); /* 往数组中注册套接字fd,即将该位置为1 */

FD_CLR (int fd ,fd_set *fdset); /* 从数组中清除套接字fd,即将该位置为0 */

FD_ISSET (int fd ,fd_set *fdset); /* 查询数组中是否已经注册了套接字fd,是返回true */

文件描述中的监视范围与select的第一个函数有关,因此需要得到fd_set数组的注册元素大小,然后作为maxfd值传入函数。

另外一个重要的参数就是timeout,select默认在发生监视的事件时才返回,不然会一直阻塞,为了防止阻塞时间过长,就可以通过传递timeout设置超时时间,超过timeout后函数会返回0;

struct timeval

{

long tv_sec; /* seconds */

long tv_usec; /* microseconds */

}

 

查看结果

select返回后如何判断哪些描述符发生了变化呢?还是通过fd_set数组,函数完成后,fd_set除了发生事件的描述符,其他的都会被置为0,因此返回后位数组中还是为1的描述符就是发生事件的描述符,查找的方式就是循环fd_set数组,使用FD_ISSET函数判断是否该位为1,然后根据3个数组分别进行相应的读写操作即可。

需要注意的是,当需要select循环监听时,select每次成功返回后,都需要重新设置监听位,因此上一次返回后将位数组都打乱了。

Apache默认使用的就是select机制,对应的Nginx使用的是Epoll,这个后面会再做分析。

以上就是select函数的使用和原理,可以称为I/O复用的核心。

猜你喜欢

转载自blog.csdn.net/github_38392025/article/details/81772975