poll是I/O复用多路转接的另一种方法,其优化了select两个缺点:
- poll服务器支持的文件描述符数目没有上限;
- poll服务器函数接口的参数与select不同,其将输入与输出参数进行了分离(用结构体实现).
函数如下:
#include <poll.h>
int poll(struct pollfd* fds,nfds_t nfds,int timeout);
参数含义:
- fds:结构体数组,(可自定义数组的大小,以连接客户端的数目,所以以上可说其支持的文件描述符无上限,因为个数大小由用户可自定义大小),每一个结构体内容如下,并且可以看到其将输入输出型参数分离:
struct pollfd
{
int fd; //关心的文件描述符
short events; //其作输入型参数指定fd关心的事件
short revents;//其作输出型参数指定fd发生的哪一个事件就绪
};
- nfds:结构体数组的大小;
- timeout:与select含义相同,不过此处timeout不为结构体,只为一个整数值可直接设定,代表ms,含义如下:
NULL:poll则一直阻塞等待事件就绪;
0:则仅检测fd状态后不等待立即返回;
特定值:在指定值时间内没有相应事件就绪则超时返回。
poll服务器代码实现如下:
poll_server.c
#include <stdio.h>
#include <poll.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#define SIZE 64
static void usage(const char* porc)
{
printf("%s [local_ip] [local_porc]\n",porc);
}
int startup(char* ip,int port)
{
int sock=socket(AF_INET,SOCK_STREAM,0);
if(sock<0)
{
perror("sock");
close(sock);
exit(2);
}
int opt=1;
setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));
struct sockaddr_in local;
local.sin_family=AF_INET;
local.sin_port=htons(port);
local.sin_addr.s_addr=inet_addr(ip);
if(bind(sock,(struct sockaddr*)&local,sizeof(local))<0)
{
perror("bind");
close(sock);
exit(3);
}
if(listen(sock,10)<0)
{
perror("sock");
close(sock);
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])); //创建监听套接字
struct pollfd fds[SIZE]; //poll数组以此来保存每一个客户端信息
int i=0;
for( ;i<SIZE;++i) //初始化
{
fds[i].fd=-1;
fds[i].events=0;
fds[i].revents=0;
}
fds[0].fd=listen_sock;
fds[0].events=POLLIN;
fds[0].revents=0;
int timeout=500;
while(1)
{
switch(poll(fds,SIZE,timeout))
{
case -1:
perror("poll");
break;
case 0:
printf("timeout...\n");
break;
default: //成功
{
struct sockaddr_in client;
socklen_t len=sizeof(client);
char buf[1024];
int i=0;
for( ;i<SIZE;++i)
{
if(i==0 && fds[i].revents==POLLIN) //为0则为监听套接字建立连接
{
int new_sock=accept(listen_sock,(struct sockaddr*)& client,&len);
if(new_sock<0)
{
perror("accept");
close(new_sock);
continue;
}
printf("get a new client:[%s:%d]\n",inet_ntoa(client.sin_addr),ntohs(client.sin_port));
int j=1;
for( ;j<SIZE;++j)
{
if(fds[j].fd==-1)
break;
}
if(j==SIZE)
{
close(new_sock);
continue;
}
fds[j].fd=new_sock;
fds[j].events=POLLIN;
}
else if(i!=0 && fds[i].revents==POLLIN) //normal fd ready,不为0则直接读取
{
ssize_t s=read(fds[i].fd,buf,sizeof(buf)-1);
if(s>0)
{
buf[s]=0;
printf("client# %s\n",buf);
fds[i].events=POLLOUT; //读取成功则将此关心事件改为写
}
else if(s==0)
{
printf("client quit!!!\n");
close(fds[i].fd);
fds[i].fd=-1;
continue;
}
else
{
perror("read");
continue;
}
}
else if(i!=0 && fds[i].revents==POLLOUT)
{
ssize_t s=write(fds[i].fd,buf,strlen(buf));
if(s<0)
{
perror("write");
continue;
}
fds[i].events=POLLIN; //写成功则将此关心事件改为读
}
}
}
break;
}
}
return 0;
}
从上可知,select和poll都需要在返回后,通过遍历文件描述符来获取已经就绪的socket,事实上,同时连接的大量客户端在一时刻可能只有很少的处于就绪状态,因此随着监视的描述符数量的增长,其效率也会线性下降。