poll系统调用和select类似,也是在指定的事件内轮询一定数量的文件描述符,以测试其中是否有就绪的文件描述符,不过poll聪明的地方就是它把事件和文件描述符绑定了起来(后面大家就会知道这个操作的优点了!!!!!!)
《一》poll的函数原型如下:
#include<poll.h>
Int poll(struct pollfd* fds,nfds_t nfds,int timeout)
1>fds是一个pollfd结构体类型的数组,它指定所有我们感兴趣的文件描述符上发生的可读可写和异常等事件,
struct pollfd
{
Int fd; //文件描述符
Short events; //注册的事件
Short revents; //实际发生的事件,由内核填充
};
(fd events 由用户填充,revents由内核填充)
2>Nfds:被监听事件集合fds的大小,(fds结构体数组的大小)
3>Timeout:超时时间,为-1时,poll调用永远阻塞,直到某个事件发生
Poll成功返回就绪文件描述符的个数,失败返回-1,
《二》poll的原理图如下:
《三》poll的代码:
客户端:
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<string.h>
void main()
{
int sockfd=socket(AF_INET,SOCK_STREAM,0);
assert(sockfd!=-1);
struct sockaddr_in ser,cli;
ser.sin_family=AF_INET;
ser.sin_port=htons(6000);
ser.sin_addr.s_addr=inet_addr("127.0.0.1");
int n=connect(sockfd,(struct sockaddr*)&ser,sizeof(ser));
assert(n!=-1);
while(1)
{
char buff[128]={0};
printf("input:\n");
fgets(buff,128,stdin);
if(strncmp(buff,"end",3)==0)
{
close(n);
break;
}
send(sockfd,buff,strlen(buff)-1,0);
memset(buff,0,128);
recv(sockfd,buff,127,0);
printf("%s\n",buff);
}
close(sockfd);
}
服务器:
#define _GNU_SOURCE 1
#include<stdio.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<assert.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<fcntl.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<errno.h>
#include<sys/select.h>
#include<poll.h>
#define MAX 1024
void initfd(struct pollfd *fds)
{
int i=0;
for(;i<MAX;i++)
{
fds[i].fd=-1;
}
}
void insertfd(struct pollfd *fds,int fd,short events)
{
int i=0;
for(;i<MAX;i++)
{
if(fds[i].fd==-1)
{
fds[i].fd = fd;
fds[i].events=events;
break;
}
}
}
void main()
{
int listenfd=socket(AF_INET,SOCK_STREAM,0);
assert(listenfd!=-1);
struct sockaddr_in ser,cli;
ser.sin_family=AF_INET;
ser.sin_port=htons(6000);
ser.sin_addr.s_addr=inet_addr("127.0.0.1");
int res=bind(listenfd,(struct sockaddr*)&ser,sizeof(ser));
assert(res!=-1);
listen(listenfd,5);
struct pollfd fds[MAX];
initfd(fds);
fds[0].fd=listenfd;
fds[0].events=POLLIN;
while(1)
{
int n=poll(fds,MAX,5000);
if(n==-1)
{
printf("poll error\n");
exit(0);
}
else if(n==0)
{
printf("time out\n");
continue;
}
else
{
int i=0;
for(;i<MAX;++i)
{
if(fds[i].revents & POLLRDHUP)
{
printf("one client break\n");
close(fds[i].fd);
fds[i].fd=-1;
}
if(fds[i].revents & POLLIN)
{
if(fds[i].fd==listenfd)
{
int len=sizeof(cli);
int c=accept(listenfd,(struct sockaddr*)&cli,&len);
if(c<0)
{
printf("one client link error\n");
continue;
}
insertfd(fds,c,POLLIN|POLLRDHUP);
}
else
{
char buff[128]={0};
int n=recv(fds[i].fd,buff,127,0);
if(n<=0)
{
printf("not get\n");
close(fds[i].fd);
fds[i].fd=-1;
continue;
}
printf("%d %s\n",fds[i].fd,buff);
send(fds[i].fd,"ok",2,0);
}
}
}
}
}
}
《三》Poll与select的相同点:
- 返回的都是文件描述符的个数,并没有返回哪些文件描述符就绪,所以还需要轮询检测哪些文件描述符就绪,时间复杂度为O(n);
- 都会存在两次拷贝:
1》调用时将用户空间的数据拷贝到内核空间
2》返回时将内核空间数据拷贝到用户空间
3.两者都只能在工作在相对低效的LT模式下
《四》Poll相对select的优点:
- poll将用户关注的事件与内核反馈的事件分离,所以每次调用poll之前都不中重新设置,(内核只修改了revents,并么有修改events的值)
- 将事件用单独的变量表示,能关注的事件类型增多
- 文件描述符用单独的变量表示,关注的文件描述符值增大
- 用户关注的所有文件描述符都记录在用户数组中,同时关注的文件描述符增多(受进程所能打开的文件描述符的数量限制)