相比较于select服务器,poll使用一个结构体指针(struct pollfd*)来代替select中的三个文件描述符集,这样就可以通过一个结构体来对一个文件描述符上发生的事件进行描述。
pollfd结构包含了要监视的event和发生的event,不再使用select“参数-值”传递的方式。同时,pollfd并没有最大数量限制(但是数量过大后性能也是会下降)。 和select函数⼀一样,poll返回后,需要轮询pollfd来获取就绪的描述符。
从上面看,select和poll都需要在返回后,通过遍历文件描述符来获取已经就绪的socket。事实上,同时连接的大量客户端在一时刻可能只有很少的处于就绪状态,因此随着监视的描述符数量的增长,其效率也会线性下降。
poll函数:
参数:
- fds:一个结构数组
struct pollfd结构如下:
struct pollfd
{
int fd; //文件描述符
short events; //请求的事件
short revents; //返回的事件
};
events和revents是通过对代表各种事件的标志进行逻辑或运算构建而成的。events包括要监视的事件,poll用已经发生的事件填充revents。poll函数通过在revents中设置标志POLLHUP、POLLERR和POLLNVAL来反映相关条件的存在。不需要在events中对于这些标志符相关的比特位进行设置。如果fd小于0,则events字段被忽略,而revents被置为0.标准中没有说明如何处理文件结束。文件结束可以通过revents的标识符POLLHUN或返回0字节的常规读操作来传达。即使POLLIN或POLLRDNORM指出还有数据要读,POLLHUP也可能会被设置。因此,应该在错误检验之前处理正常的读操作。
poll函数的事件标志符值
POLLIN:普通或优先级带数据可读
POLLRDNORM:普通数据可读
POLLRDBAND:优先级带数据可读
POLLPRI:高优先级数据可读
POLLOUT:普通数据可写
POLLWRNORM:普通数据可写
POLLWRBAND:优先级带数据可写
POLLERR:发生错误
POLLHUP:发生挂起
POLLNVAL:描述字不是一个打开的文件
注意:后三个只能作为描述字的返回结果存储在revents中,而不能作为测试条件用于events中。
- nfds:要监视的描述符的数目。
- timeout:是一个用毫秒表示的时间,是指定poll在返回前没有接收事件时应该等待的时间。如果 它的值为-1,poll就永远都不会超时。值为0,则立即返回。值大于0,则会等待指定数目的毫秒后,若无就绪的文件描述符,则返回超时。
1) timeout 为 -1
这会造成 poll 永远等待。poll() 只有在一个描述符就绪时返回,或者在调用进程捕捉到信号时返回(在这里,poll 返回 -1),并且设置 errno 值为 EINTR 。-1 可以用宏定义常量 INFTIM 来代替(在 pth.h 中有定义) 。
2) timeout 等于0
在这种情况下,测试所有的描述符,并且 poll() 立刻返回。这允许在 poll 中没有阻塞的情况下找出多个文件描述符的状态。
3) time > 0
这将以毫秒为单位指定 timeout 的超时周期。poll() 只有在超时到期时返回,除非一个描述符变为就绪,在这种情况下,它立刻返回。如果超时周期到齐,poll() 返回 0。这里也可能会因为某个信号而中断该等待。
返回值:
成功时,poll()返回结构体中revents域不为0的文件描述符个数;如果在超时前没有任何事件发生,poll()返回0;失败时,poll()返回-1。
下面是一个基于poll的服务器代码:
#include<stdio.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<stdlib.h>
#include<netinet/in.h>
#include<unistd.h>
#include<arpa/inet.h>
#include<string.h>
#include<poll.h>
#define SIZE 100
static void usage(const char* proc)
{
printf("usage:%s [local_ip] [local_port]\n",proc);
}
int startup(char* ip, int port)
{
int sock = socket(AF_INET, SOCK_STREAM, 0);
if(sock<0)
{
perror("sock");
exit(2);
}
struct sockaddr_in local;
local.sin_family = AF_INET;
local.sin_port = ntohs(port);
local.sin_addr.s_addr = inet_addr(ip);
if(bind(sock, (struct sockaddr*)&local, sizeof(local)) <0)
{
perror("bind");
exit(3);
}
if(listen(sock, 10) <0)
{
perror("listen");
exit(4);
}
return sock;
}
int main(int argc, char* argv[])
{
if(argc != 3)
{
usage(argv[0]);
return(1);
}
int listen_sock = startup(argv[1], atoi(argv[2]));
printf("%d\n", listen_sock);
struct pollfd evs[SIZE];//创建一个结构数组存放要监视的文件描述符和其要监视的事件
int i = 0;
for(;i<SIZE;i++)//将数组中结构体对象的成员fd设置为-1;这样这个结构体的监视事件就是无效的
{
evs[i].fd = -1;
}
//将listen_sock设置进poll监视的结构数组
evs[0].fd = listen_sock;
evs[0].events = POLLIN;
evs[0].revents = 0;
int timeout = 1500;//设置超时时长为1500ms
while(1)
{
switch(poll((struct pollfd*)&evs, SIZE, timeout))
{
case 0:
printf("timeout\n");//函数超时
break;
case -1:
perror("poll");//函数出错
break;
//函数监视事件成功
default:
{
for(i=0; i<=SIZE; i++)
{
int fd = evs[i].fd;
if((fd == listen_sock) && (evs[i].revents & POLLIN))//listen ready
{
struct sockaddr_in client;
socklen_t len = sizeof(client);
int new_sock = accept(listen_sock, (struct sockaddr*)&client, &len);
if(new_sock < 0)
{
perror("accept");
break;
}
printf("get a client:%s %d\n", inet_ntoa(client.sin_addr),ntohs(client.sin_port));
int j = 0;
for(;j<SIZE;j++)//将获得的new_sock添加到poll监视的结构数组中
{
if(evs[j].fd == -1)
{
evs[j].fd = new_sock;
evs[j].events = POLLIN;
evs[j].revents = 0;
break;
}
}
}
else if(fd != listen_sock)
{
char buf[1024];
if(evs[i].revents & POLLIN)//read ready
{
ssize_t s = read(fd,buf,sizeof(buf)-1);
if(s > 0)//读取数据成功
{
buf[s] = 0;
printf("client# %s\n", buf);
//重新设定fd被监视的事件为写事件
evs[i].fd = fd;
evs[i].events = POLLOUT;
evs[i].revents = 0;
}
else if(s == 0)//客户端断开链接
{
printf("client is quit!\n");
//关闭fd,并从poll的监视数组中移除fd
close(fd);
evs[i].fd = -1;
break;
}
else//读取失败
{
perror("read");
//关闭fd,并从poll的监视数组中移除fd
close(fd);
evs[i].fd = -1;
break;
}
}
else if(evs[i].revents & POLLOUT)//write ready
{
write(fd,buf,strlen(buf));
//写事件结束,把fd的监听事件重设置为读时间
evs[i].fd = fd;
evs[i].events = POLLIN;
evs[i].revents = 0;
}
}
else
{}
}//for
}//default
}//switch
}//while
return 0;
}
理论上poll是没有文件描述符上限的,但是为了代码简单易懂,用define定义了一个常量SIZE,来标识文件描述符的总数量数量。
与select服务器相比较没有了select服务器的访问上限,poll的实现和select非常相似,只是描述fd集合的方式不同,poll使用pollfd结构而不是select的fd_set结构,其他的都差不多。
poll服务器的优缺点:
优点:
1、poll() 不要求开发者计算最大文件描述符加一的大小。
2、poll() 在应付大数目的文件描述符的时候相比于select速度更快
3、它没有最大连接数的限制,原因是它是基于链表来存储的。
缺点:
1、大量的fd的数组被整体复制于用户态和内核地址空间之间,而不管这样的复制是不是有意义。
2、与select一样,poll返回后,需要轮询pollfd来获取就绪的描述符。
3、poll随着用户的增多,I/O的效率呈线性下降。