上文说到的实现IO复用的函数中的select,本文接着介绍第二种poll。
poll
Poll相对于select来说突破了监听的文件描述符上限1024,最大文件描述符是系统所能允许的最大值,可以通过查看proc/sys/fs/file-max文件查看,这个值也可以改(通过limits.conf)。另一方面是实现了监听和就绪事件的分离。其他和select类似,也是在指定时间内轮询一定数量的文件描述符,检测其中是否有就绪的。还是先从API说起。
poll API
函数原形:
# include <poll.h>
int poll ( struct pollfd * fds, unsigned int nfds, int timeout);
参数
poll函数的第一个参数fds是struct pollfd结构体类型的事件集,一般传入一个该类型的数组。
pollfd结构体定义如下:
struct pollfd {
int fd; /* 文件描述符 */
short events; /* 等待的事件 */
short revents; /* 实际发生了的事件,由内核填充 */
} ;
该结构体有三个成员:
第一个fds是要监听的文件描述符,
第二个events是要监听的事件(POLLIN、POLLOUT、POLLERR),
第三个revents是监控事件中满足条件返回的事件,内核通过修改这个参数来反馈监听的就绪事件。
事件主要有以下几个:
POLLIN 有数据可读。
POLLRDNORM 有普通数据可读。
POLLRDBAND 有优先数据可读。
POLLPRI 有紧迫数据可读。
POLLOUT 写数据不会导致阻塞。
POLLWRNORM 写普通数据不会导致阻塞。
POLLWRBAND 写优先数据不会
第二个参数nfds是监听的文件描述符的个数,
第三个参数timeout是设置超时时间,用法和select一样。是一个struct timeval结构体指针,该结构体定义如下:
struct timeval{
long tv_sec; //second
long tv_usec; //minisecond
}
超时时间可以设置到毫秒级别,有三种设置情况:
NULL:阻塞等待,直到某个文件描述符上发生了事件。
0:仅检测描述符集合的状态,然后立即返回。
> 0: 指定超时时间,如果在该时间段里没有事件发生,select将超时返回。
返回值
成功: poll()返回结构体中revents域不为0的文件描述符个数;
失败: poll()返回-1,并设置errno;
超时: poll()返回0;
不足
虽然epoll突破了最大监听1024个文件描述符的限制,但它还是存在缺点就是当大量连接上只有少量活跃的连接时,它也是采用轮询的机制每次还要遍历整个数组,效率太低。在下文中,将介绍一种更为高效的实现IO复用的函数epoll。
程序示例
最后还是以一个poll实现的服务器的示例程序结束本文:
#include<stdio.h>
#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
#include<sys/types.h>
#include<netinet/in.h>
#include<sys/socket.h>
#include<sys/wait.h>
#include<arpa/inet.h>
#include<unistd.h>
#include<ctype.h>
#include<poll.h>
#define MYPORT 8888
#define BACKLOG 10
#define MAXDATASIZE 1024
#define FILEMAX 3000
int main()
{
int i,j,maxi;
int listenfd,connfd,sockfd; //定义套接字描述符
int nready; //接受pool返回值
int numbytes; //接受recv返回值
char buf[MAXDATASIZE]; //发送缓冲区
struct pollfd client[FILEMAX]; //struct pollfd* fds
//定义IPV4套接口地址结构
struct sockaddr_in seraddr; //service 地址
struct sockaddr_in cliaddr; //client 地址
int sin_size;
//初始化IPV4套接口地址结构
seraddr.sin_family =AF_INET; //指定该地址家族
seraddr.sin_port =htons(MYPORT); //端口
seraddr.sin_addr.s_addr = INADDR_ANY; //IPV4的地址
bzero(&(seraddr.sin_zero),8);
//socket()函数
if((listenfd = socket(AF_INET,SOCK_STREAM,0))==-1)
{
perror("socket");
exit(1);
}
//地址重复利用
int on = 1;
if(setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on)) < 0)
{
perror("setsockopt");
exit(1);
}
//bind()函数
if(bind(listenfd,(struct sockaddr *)&seraddr,sizeof(struct sockaddr))==-1)
{
perror("bind");
exit(1);
}
//listen()函数
if(listen(listenfd,BACKLOG)==-1)
{
perror("listen");
exit(1);
}
client[0].fd = listenfd; //将listenfd加入监听序列
client[0].events = POLLIN; //监听读事件
//初始化client[]中剩下的元素
for(i = 1;i < FILEMAX;i++)
{
client[i].fd = -1; //不能用0,其也是文件描述符
}
maxi = 0; //client[]中最大元素下标
while(1)
{
nready = poll(client,maxi+1,-1);//阻塞监听
if(nready < 0)
{
perror("poll error!\n");
exit(1);
}
if(client[0].revents & POLLIN)//位与操作;listenfd的读事件就绪
{
sin_size = sizeof(cliaddr);
if((connfd=accept(listenfd,(struct sockaddr *)&cliaddr,&sin_size))==-1)
{
perror("accept");
exit(1);
}
printf("client IP: %s\t PORT : %d\n",inet_ntoa(cliaddr.sin_addr),ntohs(cliaddr.sin_port));
//将sockfd加入监听序列
for(i = 1;i < FILEMAX;i++)
{
if(client[i].fd < 0)
{
client[i].fd = connfd;
break;
}
}
if(i == FILEMAX)
{
perror("too many clients!\n");
exit(1);
}
client[i].events = POLLIN;//监听connfd的读事件
if(i > maxi)
{
maxi = i;
}
//判断是否已经处理完事件
if(--nready == 0)
{
continue;
}
}
//检测客户端是否发来消息
for(i = 1;i <= maxi;i++)
{
if((sockfd = client[i].fd) < 0)
{
continue;
}
if(client[i].revents & POLLIN)
{
memset(buf,0,sizeof(buf));
numbytes = recv(sockfd,buf,MAXDATASIZE,0);
if(numbytes < 0)
{
if(errno == ECONNRESET) //RET标志
{
printf("client[%d] aborted connection!\n",i);
close(sockfd);
client[i].fd = -1;
}
else
{
perror("recv error!\n");
exit(1);
}
}
else if(numbytes == 0)
{
printf("client[%d],close!\n",i);
close(sockfd);
client[i].fd = -1;
}
else
{
send(sockfd,buf,numbytes,0);
}
if(--nready == 0)
{
break;
}
}
}
}
return 0;
}