poll函数
poll的机制与select类似,与select在本质上没有多大差别,管理多个描述符也是进行轮询,根据描述符的状态进行处理,但是poll没有最大文件描述符数
主旨思想:
- 首先要构造一个关于pollfd数组,将文件描述符和要检测的时间加入数组中
- 调用一个poll函数,监听pollfd数组中的文件描述符,直到这些描述符中的一个或者多个进行I/O操作时,该函数才返回。
a.这个函数是阻塞
b.函数对pollfd数组的文件描述符的检测的操作是由内核完成的 - 在返回时,它会告诉进程有多少个描述符要进行I/O操作。
#include <poll.h>//头文件
struct pollfd {
int fd; 委托内核检测的文件描述符
short events; 委托内核检测文件描述符的什么事件
short revents; 文件描述符实际发生的事件
};
//实例
struct pollfd myfd;
myfd.fd = 5;
myfd.events = POLLIN | POLLOUT;
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
- 参数:
- fds : 是一个struct pollfd 结构体数组,这是一个需要检测的文件描述符的集合
- nfds : 这个是第一个参数数组中最后一个有效元素的下标 + 1
- timeout : 阻塞时长
0 : 不阻塞
-1 : 阻塞,当检测到需要检测的文件描述符有变化,解除阻塞
>0 : 阻塞的时长
- 返回值:
-1 : 失败
>0(n) : 成功,n表示检测到集合中有n个文件描述符发生变化
使用poll函数监听多个文件描述符的服务端程序
#include<stdio.h>
#include<stdlib.h>
#include<arpa/inet.h>
#include<string.h>
#include<unistd.h>
#include<sys/poll.h>
int main(char* argc,int argv){
//创建socket
int lfd=socket(PF_INET,SOCK_STREAM,0);
//绑定
struct sockaddr_in saddr;
saddr.sin_port=htons(9999);
saddr.sin_family=AF_INET;
inet_pton(AF_INET,"127.0.0.1",&saddr.sin_addr.s_addr);
bind(lfd,(struct sockaddr*)&saddr,sizeof(saddr));
//监听
listen(lfd,8);//连接与未连接最多8个
//创建pollfd数组
struct pollfd fds[128];
for(int i=0;i<128;++i){
fds[i].fd=-1;
fds[i].events=POLLIN;//检测是否有数据可读
}
fds[0].fd=lfd;
int nfds=0;//数组的最大下标
while(1){
//调用poll系统函数,让内核帮忙检测文件描述符是否有数据可读
int ret=poll(fds,nfds+1,-1);//阻塞
if(ret==-1){
perror("poll");
exit(0);
}else if(ret==0){
//超时时间到了 没有检测到 就为0
//程序中设置为永久阻塞 所以不会出现这种情况
continue;
}else{
// 说明检测到了有文件描述符的对应的缓冲区的数据发生了改变
if(fds[0].revents&POLLIN) {
// 表示有新的客户端连接进来了
struct sockaddr_in cliaddr;
int len = sizeof(cliaddr);
int cfd = accept(lfd, (struct sockaddr *)&cliaddr, &len);
//获取客户端信息
char clientIp[16];
inet_ntop(AF_INET,&cliaddr.sin_addr.s_addr,clientIp,sizeof(clientIp));
unsigned int clientPort=ntohs(cliaddr.sin_port);
printf("客户端ip:%s,端口:%d\n",clientIp,clientPort);
// 将新的文件描述符加入到集合中
for(int i=1;i<128;++i){
if(fds[i].fd==-1){
fds[i].fd=cfd;
fds[i].events=POLLIN;
nfds=nfds>i?nfds:i;//更新最大的pollfd数组下标
break;
}
}
}
//监听的文件描述符肯定在最前面
for(int i=1;i<=nfds;++i){
if(fds[i].revents&POLLIN){
//说明客户端发来的数据
char recvBuf[100]={0};
int len=read(fds[i].fd,recvBuf,sizeof(recvBuf));
if(len>0){
printf("read buf = %s\n", recvBuf);
write(fds[i].fd, recvBuf, strlen(recvBuf) + 1);
}else if(len==0){
printf("client close...\n");
close(fds[i].fd);
fds[i].fd=-1;
}else{
perror("read");
exit(-1);
}
}
}
}
}
close(lfd);
return 0;
}
poll函数的缺点:
1.每次调用poll,都需要把pollfd数组从用户态拷贝到内核态,这个开销在过很多时会很大
2.同时每次调用select都需要在内核遍历传递进来的所有pollfd,这个开销在很多时
也很大
3.只会返回有几个文件描述符可以读写 具体是哪几个 还需要在用户程序中遍历
poll与select的区别:
1.poll与select不同,通过一个pollfd数组向内核传递需要关注的事件,故没有描述符个数的限制
2.pollfd中的events字段和revents分别用于标示关注的事件和发生的事件,故pollfd数组只需要被初始化一次。
版权声明:本文为Science_Tec原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。