poll系统调用和select类似,也是在指定的时间内轮询一定数量的文件描述符,以测试其中是否有就绪者。
int poll(struct pollfd *fds,int nfds,int timeout);
- fds:是一个pollfd结构类型的数组,它指定所有用户感兴趣的文件描述符上发生的可读、可写和异常等事件。传入的是数组的首地址 。
pollfd结构体定义:
struct pollfd
{
int fd; //用户关注的文件描述符
short events; //用户关注的事件
short revents; //表示实际发生的事件,由内核修改
};
fd成员指定文件描述符;events成员告诉poll监听fd上的哪些事件,它是一系列事件的按位或;revents成员则是由内核修改,以通知应用程序fd上实际发生了那些事件。
- nfds:数组fds的长度,元素的个数,用户关注的文件描述符的个数
- timeout:指定poll的超时时间;当timeout为-1 时,poll调用将一直阻塞,直到某个事件发生;当timeout为0时,poll调用将立即返回。
与select的对比:
- 用户关注的事件类型更多
- 内核修改的用户关注的分开表示,每次调用不需要重新设置
- 文件描述符不再是按位来表示,直接用int类型
- 用户关注的文件描述符的值可以更大
- 用户关注的文件描述符的个数由用户数组决定,所以个数会更多
- poll返回时,也是将用户关注的所有文件描述符返回
- poll检测就绪文件描述符的时间复杂度O(n),poll返回后,用户程序依旧需要循环检测哪些文件描述符就绪
poll的优缺点
优点:
- 将用户关注的文件描述符的事件单独表示,可关注更多的事件类型
- 将用户传递和内核修改分开,每次调用poll之前,不需要重新设置
- poll函数没有最大文件描述符的限制
缺点:
- 每次调用都需要将用户空间数组拷贝到内核空间
- 每次返回都需要将所有的文件描述符拷贝到用户空间数组中,无论是否就绪
- 返回的是所有的文件描述符,搜索就绪文件描述符的时间复杂度为O(n)
poll事件类型
poll的实现
#define _GNU_SOURCE
#include<string.h>
#include<stdlib.h>
#include<assert.h>
#include<unistd.h>
#include<stdio.h>
#include<poll.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#define SIZE 100
void Init_Fds(struct pollfd *fds)
{
int i=0;
for(;i<SIZE;++i)
{
fds[i].fd=-1;
fds[i].events=0;
fds[i].revents=0;
}
}
void Insert_Fd(struct pollfd *fds,int fd,short event)
{
int i=0;
for(;i<SIZE;++i)
{
if(fds[i].fd==-1)
{
fds[i].fd=fd;
fds[i].events=event;
break;
}
}
}
void Delete_Fd(struct pollfd *fds,int fd)
{
int i=0;
for(;i<SIZE;++i)
{
if(fds[i].fd==fd)
{
fds[i].fd=-1;
fds[i].events=0;
break;
}
}
}
int main()
{
int sockfd=socket(AF_INET,SOCK_STREAM,0);
assert(sockfd!=-1);
struct sockaddr_in ser,cli;
memset(&ser,0,sizeof(ser));
ser.sin_family=AF_INET;
ser.sin_port=htons(6000);
ser.sin_addr.s_addr=inet_addr("127.0.0.1");
int res=bind(sockfd,(struct sockaddr*)&ser,sizeof(ser));
assert(res!=-1);
listen(sockfd,5);
struct pollfd fds[SIZE];
Init_Fds(fds);
Insert_Fd(fds,sockfd,POLLIN);
while(1)
{
int n=poll(fds,SIZE,-1);
if(n<=0)
{
printf("poll error\n");
continue;
}
int i=0;
for(;i<SIZE;++i)
{
if(fds[i].fd!=-1)
{
int fd=fds[i].fd;
if(fds[i].revents&POLLRDHUP)
{
printf("%d will close\n",fd);
close(fd);
Delete_Fd(fds,fd);
}
else if(fds[i].revents&POLLIN)//文件描述符就绪
{
if(fd==sockfd)
{
int len=sizeof(cli);
int c=accept(fd,(struct sockaddr*)&cli,&len);
if(c<0)
{
continue;
}
Insert_Fd(fds,c,POLLIN | POLLRDHUP);
}
else
{
char buff[128]={0};
recv(fd,buff,127,0);
printf("%d: %s\n",fd,buff);
send(fd,"OK",2,0);
}
}
}
}
}
}
epoll——Linux独有
epoll是Linux特有的I/O复用函数。它在实现和使用上与select、poll有很大的差异。首先,epoll使用一组函数来完成任务,而不是单个函数。其次,epoll吧用户关心的文件描述符上的事件放在内核里的一个事件表示中,从而不需要像select和poll那样每次调用都需要重复传入文件描述符集或事件集。但epoll需要使用一个额外的文件描述符,来标识唯一内核中的这个事件表。
int epoll_create(int size)://创建内核事件表,内核事件表底层是由红黑树来维护的
size:只是给内核一个提示,告诉它事件表需要多大
返回值:返回的文件描述符将用作其他所有epoll系统调用的第一个参数,以指定要访问的内核时间表。
int epoll_ctl(int epollfd,int cmd,int fd,struct epoll_event *event);//设置(添加、修改、删除)内核事件表中的文件描述符上的事件
epollfd:epoll_create创建的内核事件表的标记
cmd:EPOLL_CLT_ADD(添加) EPOLL_CLT_MOD(修改) EPOLL_CLT_DEL(删除)
fd:需要操作的是哪个文件描述符
返回值:成功返回0,失败返回-1,并设置error
int epoll_wait(int epollfd,struct epoll_event *revents,int maxevents,int timeout);
当活动链接比较多的时候,epoll_wait的效率未必比select和poll高,因为,此时回调函数被触发的过于频繁,所有epoll_wait适用于连接数量多但活动连接较少的情况。
revent是:只返回所有就绪的文件描述符(数组)
maxevents:指定最多监听多少个事件(数组的长度)
返回值:成功返回就绪的文件描述符个数,失败返回-1
struct epoll_event
{
__uint32_t events;//用户关注的事件
epoll_data_t data;//用户数据
}
typedef union epoll_data
{
void *ptr;
int fd; //用户关注的文件描述符
uint32_t u32;
unit64_t u64;
}epoll_data_t;
epoll的优点:
- 文件描述符的范围和文件描述符的个数都会扩大
- 事件类型会更多
- 用户关注的事件由内和维护,每次调用epoll_wait时,不需要将用户空间的数据拷贝到内核空间
- 每次epoll只会返回就绪的文件描述符
- 用户程序检测就绪文件描述符的效率O(1)
- epoll内核实现比select和poll高效
- selecthepoll,内核监听采用轮询方式,epoll采用回调方式
- epoll支持高效的ET模式
epoll的实现
#include<string.h>
#include<stdlib.h>
#include<assert.h>
#include<unistd.h>
#include<stdio.h>
#include<sys/epoll.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#define SIZE 100
int main()
{
int sockfd=socket(AF_INET,SOCK_STREAM,0);
assert(sockfd!=-1);
struct sockaddr_in ser,cli;
memset(&ser,0,sizeof(ser));
ser.sin_family=AF_INET;
ser.sin_port=htons(6000);
ser.sin_addr.s_addr=inet_addr("127.0.0.1");
int res=bind(sockfd,(struct sockaddr*)&ser,sizeof(ser));
assert(res!=-1);
listen(sockfd,5);
int epollfd=epoll_create(5);
assert(epollfd!=-1);
struct epoll_event event;
event.events=EPOLLIN;
event.data.fd=sockfd;
epoll_ctl(epollfd,EPOLL_CTL_ADD,sockfd,&event);
while(1)
{
struct epoll_event events[SIZE];
int n=epoll_wait(epollfd,events,SIZE,-1);
if(n<=0)
{
printf("epoll wait error\n");
continue;
}
int i=0;
for(;i<n;++i)
{
int fd=events[i].data.fd;
if(fd==sockfd)
{
int len=sizeof(cli);
int c=accept(fd,(struct sockaddr*)&cli,&len);
if(c<0)
{
continue;
}
event.events=EPOLLIN | EPOLLRDHUP;
event.data.fd=c;
epoll_ctl(epollfd,EPOLL_CTL_ADD,c,&event);
}
else if(events[i].events&EPOLLRDHUP)
{
epoll_ctl(epollfd,EPOLL_CTL_DEL,fd,NULL);
close(fd);
printf("%d was over\n",fd);
}
else if(events[i].events&EPOLLIN)
{
char buff[128]={0};
recv(fd,buff,127,0);
printf("%d :%s\n",fd,buff);
send(fd,"OK",2,0);
}
}
}
}