为了解决select的监听描述符最大值(1024个)的限制问题,和事件和描述符分开的问题,引入了poll。poll将描述符和要注册的事件和实际发生的事件进行封装pollfd
函数原型:
#include <poll.h>
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
pollfd就是封装体。
struct pollfd {
int fd; /* file descriptor */
short events; /* requested events */ short16bit per bit represents a events
short revents; /* returned events */
};
fd就是包含的要打开的文件描述符
events成员是一个用户定义的参数,是一个应用感兴趣的比特标识位。
revents是内核填充的参数:即真正发生的事件。
include any of those specified in events, or one of the values POLLERR, POLLHUP, or POLLNVAL. (These three bits are meaningless in the events field, and will be set in the revents field whenever the corresponding condition is true.)。If none of the events requested (and no error) has occurred for any of the file descriptors, then poll() blocks until one of the events occurs.
在POLLERR,POLLHUPP,POLLNVAL的特定事件中(这三个位对于events是无意义的,因为这三个参数实际会被放到revents中)如果没有注册的事件发生或者没有出现错误在任何被观察的描述符上,那么poll将会阻塞,直到有事件发生。
poll事件部分
#include<poll.h>:
POLLIN :有数据可读
POLLPRI:有紧急数据要读(比如 tcp套接字中的带外数据;分组模式中的伪终端主机已经发现状态改变)
POLLOUT:立即写,后续不阻塞。(普通数据可写)
POLLRDHUP (since Linux 2.6.17) 套接字断开连接(recv返回为0),或者关闭写了一半的连接,使用该宏必须要在文件头定义_GNU_SOURCE (Stream socket peer closed connection, or shut down writing half of connection. The _GNU_SOURCE feature test macro must be defined in order to obtain this definition.)
POLLERR 发生错误 (output only)
POLLHUP 发生挂起 (output only).
POLLNVAL 描述符不是一打开的个文件( Invalid request: fd not open (output only).)
大家应该多看看man手册,man手册对于函数的描述还是很细致的。下面是unix网络编程上的总结
它把POLL事件分为四类。看图
![](https://i-blog.csdnimg.cn/blog_migrate/72d8aeb9d75df9e33aefa877679ecc79.png)
第三部分我们提醒过:不能作为events的参数,因为第三类错误处理只可能给revents
要提一点 poll只识别三种类别的数据:
普通数据,优先级带数据和高优先级数据,这些术语源自于流式的设计
如POLLIN=POLLRDNORM | POLLRDBAND
考虑到TCP和UDP套接字,下面的条件引起poll返回特定的revent
(1)所有正规的TCP数据和UDP数据都被认为是普通数据
(2)当TCP连接的读这一半关闭时(比如接收到一个FIN),也认为是普通数据,且后续的读操作将返回0
(3)TCP连接存在错误即可认为是普通数据,也可以认为是错误(POLLERR).无论哪种情况,后续的读操作将返回-1,并设置errno为合适的值,这就处理了诸如接收到了RST(复位报文)或者超时等条件
(4)在监听套接字上新连接的可用性即可认为是普通数据,也可以认为是优先级数据,大多数实现都将其作为普通数据考虑。
第二个参数是我们自定义的要监听的描述符的个数(即结构数组中元素的个数)由于是int类型的,所以支持的最大描述符个数是65536个
第三个参数与select一样,也是timeout结构,timeout指定函数返回前要等待多少时间。
timeout可能的取值
INFTIM 永远等待
0 立即返回,不阻塞
>0 等待指定数目的毫秒数
INFTIM定义为一个负数。如果系统不能提供毫秒级别的定时器,该值将向上取得最接近的支持值
poll函数的返回值:
-1 发生错误
0 定时器时间到,但是没有描述符准备好。
>0 返回准备好的描述符的个数,且返回时将成员的revents的值置为0
现在我们粗略对比一下poll和select
我们在使用select时,使用了描述符集合fd_set,并就每个描述符集合中的最大数目研究了一番。现在使用poll将以前的fd_set和事件都封装成了pollfd结构体,并定义结构数组,并告诉内核结构数组的大小让内核去管理。减少了select的一系事件分类造成的麻烦
但是二者返回之后还是要进行轮询查询数组中的描述符的状态,类型和可用情况
poll的部分使用参考代码
47 int i=0;
48 for(;i<Maxfd;i++)
49 {
50 int fd=fds[i].fd;
51 if(fd==-1)
52 {
53 continue;
54 }
55
56 if(fds[i].revents&POLLRDHUP) //有挂断事件
57 {
58 printf("client hub,close\n");
59 close(fds[i].fd);
60 fds_del(fds,fds[i].fd);
61 }
62 if(fds[i].revents&POLLIN) //有读事件
63 {//监听套接字 -》创建连接
64 if(fd==sockfd)
65 {
66 struct sockaddr_in caddr;
67 int len=sizeof(caddr);
68 int c=accept(sockfd,(struct sockaddr*)&caddr,&len);
69 if(c<0)
70 {
71 continue;
72 }
73 printf("accept %d\n",c);
74 fds_add(fds,c);
75 }
else
77 { //当前描述符是连接描述符
78 char buff[128]={0};
79 if((recv(fd,buff,127,0))<=0) //如果没有数据可读或者是断开连接
80 {
81 close(fds[i].fd); //先关闭描述符
82 fds_del(fds,fd); //将描述符原先所在的fds数组中对应的元素置为-1
83 printf("client over\n");
84 break; //直接可以结束了
85 }
86 else
87 {//recv收到数据
88 printf("c=%d buff=%s\n",fd,buff);
89 send(fd,"ok",127,0);
90 }
91 }
92 }
93 }
94 }
如果我们不关心某个特定描述符,那么可将pollfd结构数组的fd初始化为负数,如-1,表示当前描述符不可用
128 void fds_init(struct pollfd fds[])
129 {
130 int i=0;
131 for(;i<Maxfd;i++)
132 {
133 fds[i].fd=-1;
134 fds[i].events=0;
135 fds[i].revents=0;
136 }
137 }
138
139 void fds_add(struct pollfd fds[],int fd)
140 {
141 int i=0;
142 for(;i<Maxfd;i++)
143 {
144 if(fds[i].fd==-1)
145 {
146 fds[i].fd=fd; //将就绪的描述符添加到pollfd结构数组中
147 fds[i].events=POLLIN | POLLRDHUP; //注册事件
148 break; //直接退出,防止将后续的fds元素也置为fd
149 }
150 }
151 }
当然poll也有增强版本ppoll,man手册描述The Linux ppoll() system call modifies its timeout argument. However, the glibc wrapper function hides this behavior by using a local vari-able for the timeout argument that is passed to the system call. Thus, the glibc ppoll() function does not modify its timeout argument.
Linux ppoll调用可以修改timeout时间(poll内的timeout可以修改吗?什么情况会修改?),然而Glibc包装函数隐藏了通过系统调用使用局部变量timeout参数的实现。因此glibc的ppoll函数不会修改它自身的timeout参数
如有错误,希望及时指出