【1】前言
poll本质上和select没有区别,它将用户传入的数组拷贝到内核空间,然后查询每个fd对应的设备状态,如果设备就绪则在设备等待队列中加入一项并继续遍历,如果遍历完所有fd后没有发现就绪设备,则挂起当前进程,直到设备就绪或者主动超时,被唤醒后它再次遍历fd。
【2】poll系统调用函数原型
# include <poll.h>
int poll ( struct pollfd * fds, unsigned int nfds, int timeout);
fds参数:一个pollfd结构类型的数组,指定所有我们希望监听的所有文件描述符上发生的可读、可写和异常等事件。
nfds参数:指定指定被监听事件集合fds的大小
timeout参数:指定poll的超时值,单位是毫秒。
timeout 为-1时: poll调用将永远阻塞,直到某个事件发生
timeout为0时:poll调用将立即返回
typedef unsigned long int nfds_t;
pollfd结构体定义如下:
struct pollfd
{
int fd; /* 文件描述符 */
short events; /* 注册的事件 */
short revents; /* 实际发生了的事件,由内核填充*/
};
其中fd成员指定文件描述符;
events成员告诉poll监听fd上那些事件,他是一系列事件的按位或;
revents成员由内核修改,用来通知应用程序fd上哪些事件真实发生了。
如果events上的事件发生了,revents就会将自己与events的对应位置为1
poll支持的时间类型如下:
事件 | 描述 |
---|---|
POLLIN | 有数据可读 |
POLLRDNORM | 有普通数据可读 |
POLLRDBAND | 有优先数据可读 |
POLLPRI | 有紧迫数据可读 |
POLLOUT | 写数据不会导致阻塞 |
POLLWRNORM | 写普通数据不会导致阻塞 |
POLLWRBAND | 写优先数据不会导致阻塞 |
POLLMSGSIGPOLL | 消息可用 |
… | … |
revents域中可能返回下列事件:
事件 | 描述 |
---|---|
POLLER | 指定的文件描述符发生错误 |
POLLHUP | 指定的文件描述符挂起事件 |
POLLNVAL | 指定的文件描述符非法 |
返回值
- 大于0:表示数组fds中有socket描述符的状态发生变化,或可以读取、或可以写入、或出错。并且返回的值表示这些状态有变化的socket描述符的总数量;此时可以对fds数组进行遍历,以寻找那些revents不空的socket描述符,然后判断这个里面有哪些事件以读取数据。
- 等于0:表示没有socket描述符有状态变化,并且调用超时。
- 小于0:此时表示有错误发生,此时全局变量errno保存错误码。
【3】程序示例
下面是一个基于TCP的多客户端服务器交互的程序:
【1】建立三次连接后若client客户端不发送数据,则服务器端会在超时时间5秒内轮询遍历fds数组等待client客户端发送数据,超时时间timeout设置为5秒,若5秒内没有数据发送到服务器则会打印出"time out"。
【2】直到client发送数据,内核会将该事件的fds[i].revevts修改,poll则会进行遍历fds数组找到fds[i].revents异或有数据可读POLLIN为真的事件进行处理。
【3】这时有两种情况,若fds[i].fd == sockfd说明监听队列中有连接待处理,则使用accept拿出一个连接。否则,没有新连接产生,是有客端发来了数据,我们直接使用recv接收客端数据,并打印收到的数据。
server.c
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<assert.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<poll.h>
#define MAXFD 10
int create_sockfd();
//向fds数组中添加文件描述符fd,指定关注的事件
void fds_add(struct pollfd fds[],int fd)
{
int i=0;
for(;i<MAXFD;++i)
{
if(fds[i].fd==-1)
{
fds[i].fd=fd;
fds[i