文章目录
一、poll 模型简介
从字面含义来看,poll 就是对集合中的文件描述符进行调查。
1.没有文件描述符数量限制
poll 和 select 在本质上没有差别,管理多个文件描述符也是进行轮询,根据描述符的状态进行处理,但是 poll 没有最大文件描述符数量的限制
2.fdset 采用数组
select 的 fdset(存放文件描述符的集合)采用 bitmap,select 默认大小为 1024,可以修改但是没有必要;
poll 采用了数组,这个数组的大小根据业务的需求去定义大小。那么意思就是可以定义为无穷大吗?最多能打开多少,要查看系统的参数,不同的配置可以打开的最大文件数不一样。
ulimit -n
open files :最大的打开的文件数量,Linux下一切皆文件。
3.文件描述符数量增大,开销增大
poll 和 select 同样存在一个缺点就是,文件描述符的数组被从用户态整体复制到内核态的地址空间;无论这些文件描述符是否有事件,它的开销随着文件描述符数量的增大而线性增大。
poll 也要遍历整个描述符数组才能得到有事件的描述符。
二、poll 函数和参数
1.poll 函数
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
(1)struct pollfd *fds :结构体数组
(2)nfds_t nfds :数组里面有效的元素的个数,相当于select 里面的最大有效文件描述符个数,或者说需要监视的描述符的个数。
(3)int timeout :超时机制
2. 文件描述符数组
(1)int fd :文件描述符
(2)short events :让内核去监视的事件,就像是 select 中让内核去监视读事件,写事件,异常事件
(3)short revents :返回的事件。调用 poll 函数,当监视的文件描述符有事件发生,内核修改的是 revents,而不修改 events 和 fd,这样做events 和 fd 就可以重用;poll 返回的是 revents 。
struct pollfd
{
int fd;
short events;
short revents;
};
3.初始化文件描述符数组
初始化文件描述符数组时,是把所有的文件描述符置为 -1;
为什么呢?去查看一下帮助:当把文件描述符数组交给 poll 的时候,当文件描述符的值为 -1 时,poll 就会忽略。也就是说 poll 会忽略值为 -1 的文件描述符。
4.pollin 和 pollout
pollin :表示有数据可读,就相当于读事件
pollout:表示可写,写事件
pollin 的使用:
下面代码的意思就是,将监听的socket 放到 poll 监视的数组中,当监听的socket 有事件发生时,发生的是读事件;表示 poll 有数据可以读了或者说poll 的处理的方式先读出来。
就相当于 select 中将文件描述符放到读集合中,当这个文件描述符有事件发送,select 就去这个文件描述读取数据。那么为什么不是写呢,因为已经把事件分类了,放到了读集合中,那么对应这个集合中发生的事件,select 的操作都是读。
来到 poll 中也是一样,相当于将事件分类了,将文件描述符发生的事件归类与读事件,那么poll 只对它进行读。这个有点像学校的食堂,食堂将窗口都分类了,有的窗口卖奶茶,有的卖面,有的卖米饭。你去到了奶茶的窗口,人家只会卖给你奶茶,不会卖米饭和面给你。
5.revents
(1)在select 函数中,每次调用它,会改变 socket 集合的内存,所以要把 socket 集合保存下来或者说备份,传一个临时的 socket 给 select 操作。
(2)在poll 中不再对 文件描述符集合进行备份,直接对这个集合进行操作。会发现这里数组的下标直接用 socket 的值。
因为 poll 修改的是结构体中的 revents ,返回的也是revents 。其他的两个不变,所以不备份了,这里效率就提高了一点点。
struct pollfd
{
int fd;
short events;
short revents;
};
6.调用 poll 的错误返回值
这个和 select 差不多:给了错误的文件描述符,信号中断,系统内存满了。
7.poll 的超时机制
select 采用的是时间结构体:
poll 用的是整数,单位是毫秒
8.使用流程
8.1
遍历整个数组,如果文件描述符的值为 -1 就表示没有事件发生,判断下一个,
8.2
如果文件描述符的值不为-1,表示有事件发生;接着就判断发生的时间是不是读事件。如果不是读事件,下一个
8.3
有读事件发生,那么就先把 revents 清空(初始化),因为接下来要修改它。就像是使用一个数组前,先清空。
8.4
如果发生事件的 监听的socket ,那么就表示有新的客户端连接上来,也要把新的客户端的socket连接,加入数组中。因为客户端是发数据过来的,暂时把它的事件都分为读事件。
将新来的socket加入数组中,也将事件置为可读。同时要修改监视的数量。
8.5
发生了错误
忽略了一个socket,就要重新计算监视的数量。
8.6
读取数据
三、文件描述符数组补充
1.监视的数量变化问题
当文件描述符的数量有变化时,设置的监视的文件描述符的数量可以变也可以不变。比如说当数组里面只有4个文件描述符的时候,你可以设置监视的范围为4或者大于4都可以。
为什么可以大于4呢,因为其他的文件描述符的值我可以置为 -1 ,-1的会被poll忽略。这样和填4没有什么区别。反而会更加容易编程。这个怎么应用呢,当客户端关闭了socket之后,理应来说文件描述符数组中要将这个socket去掉,接着重排数组,然后重新计算要监视的数量。但是现在我将这个socket文件描述符置为-1,poll 会忽略掉它。
那么问题又来了,这样操作会不会导致数组里面有很多-1呢?其实不会的,系统分配socket 的值时候,不是随便分的,而是从小到大分配空闲的数(比如说从1-10分配给socket)分配。分配socket的值的时候,系统会看哪个数是空闲的,没有socket在使用。前面的0,1,2都被使用了,如果3被置为-1,那么代表是空闲的,就将3分配给一个socket,这样就不会出现很多-1了。