文章目录
【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].events=POLLIN;
fds[i].revents=0;
break;
}
}
}
//在fds数组中删除指定文件描述符fd及其相关事件信息
void fds_del(struct pollfd fds[],int fd)
{
int i=0;
for(;i<MAXFD;++i)
{
if(fds[i].fd==fd)
{
fds[i].fd=-1;
fds[i].events=0;
fds[i].revents=0;
break;
}
}
}
//初始化fds数组,其中文件描述符fd置为-1,其余都置为0
void fds_init(struct pollfd fds[])
{
int i=0;
for(;i<MAXFD;++i)
{
fds[i].fd=-1;
fds[i].events=0;
fds[i].revents=0;
}
}
int main()
{
int sockfd=create_sockfd();
assert(sockfd!=-1);
//初始化pollfd类型的结构体数组fds
struct pollfd fds[MAXFD];
//初始化fds数组
fds_init(fds);
//将sockfd添加到fds数组中
fds_add(fds,sockfd);
while(1)
{
//使用poll系统调用轮询,测试其中是否有就绪者
int n=poll(fds,MAXFD,5000);
if(n==-1)
{
perror("poll error");
}
else if(n==0) //返回0表示超时,没有就绪者
{
printf("time out\n");
}
else //fds数组存在就绪的文件描述符
{
int i=0;
for(;i<MAXFD;++i)
{
if(fds[i].fd==-1)
{
continue;
}
//可获得是哪个文件描述符上就绪, fds[i]中的成员revents由系统内核修改
if(fds[i].revents &POLLIN)
{
//这时候分两种情况
//若fds[i].fd == sockfd说明监听队列中有连接待处理
//则使用accept接收连接
//否则,没有新连接产生,是已有客户端发来了数据
//我们直接使用recv接收客端数据,并打印收到的数据
if(fds[i].fd==sockfd)
{
struct sockaddr_in caddr;
int len=sizeof(caddr);
//接收一个套接字已建立的连接,得到连接套接字c
int c=accept(sockfd,(struct sockaddr*)&caddr,&len);
if(c<0)
{
continue;
}
printf("accept c=%d\n",c);
//将新的连接套接字c,添加到pollfd结构类型的数组fds
fds_add(fds,c);//join
}
else
{
char buff[128]={0};
//使用recv来接收客端数据
int res=recv(fds[i].fd,buff,127,0);
//接收服务器端的数据是零,即n返回0,说明客户端已经关闭
if(res<=0)
{
//关闭文件描述符fds[i].fd
close(fds[i].fd);
//从fds数组中删除此文件描述符
fds_del(fds,fds[i].fd);
printf("one client over\n");
}
else
{
//n不为0,即接收到了数据,那么打印数据,并向客端回复一条信息
printf("recv(%d)=%s\n",fds[i].fd,buff);
send(fds[i].fd,"ok",2,0);
}
}
}
}
}
}
}
int create_sockfd()
{
int sockfd=socket(AF_INET,SOCK_STREAM,0);
if(sockfd==-1)
{
return -1;
}
struct sockaddr_in saddr;
memset(&saddr,0,sizeof(saddr));
saddr.sin_family=AF_INET;
saddr.sin_port=htons(6000);
saddr.sin_addr.s_addr=inet_addr("127.0.0.1");
int res=bind(sockfd,(struct sockaddr *)&saddr,sizeof(saddr));
if(res==-1)
{
return -1;
}
listen(sockfd,5);
return sockfd;
}
client.c
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<assert.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
int main()
{
int sockfd = socket(AF_INET,SOCK_STREAM,0);
assert(sockfd != -1 );
struct sockaddr_in saddr;
memset(&saddr,0,sizeof(saddr));
saddr.sin_family = AF_INET;
saddr.sin_port = htons(6000);
saddr.sin_addr.s_addr = inet_addr("127.0.0.1");
int res = connect(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));//向服务器发起链接
assert(res != -1);
while(1)
{
char buff[128] = {0};
printf("Please Input:");
fgets(buff,128,stdin);
if(strncmp(buff,"end",3) ==0 )
{
break;
}
send(sockfd,buff,strlen(buff),0);
memset(buff,0,128);
recv(sockfd,buff,127,0);
printf("RecvBuff:%s\n",buff);
printf("\n");
}
close(sockfd);
}
运行结果如下:
当程序没有输入时,每过5秒超时时间就会打印time out。
【4】poll的优缺点
poll的优点:
- poll() 不要求开发者计算最大文件描述符加一的大小。
- poll() 在应付大数目的文件描述符的时候相比于select速度更快
- 它没有最大连接数的限制,原因是它是基于链表来存储的。
poll的缺点:
-
与select一样,poll返回后,需要轮询pollfd来获取就绪的描述符这样会使性能下降 。
-
虽然poll突破了最大监听1024个文件描述符的限制,但它还是存在缺点就是当大量连接上只有少量活跃的连接时,它也是采用轮询的机制每次还要遍历整个数组