poll:
int poll(struct pollfd * fds, int nfds, int timeout);
struct pollfd
{
int fd;//指定的是用户关注的文件描述符的值
short events;//用户关注的事件
short revents;//由内核修改,表示关注的文件描述符发生了哪些事件
}
nfds:数组的长度,用户关注的文件描述符的格式
timeout:超时时间。-1代表永久阻塞,直到文件就绪才能返回。
select 里面关注的是文件描述符上的事件,poll里面的第一个参数是一个pollfd的类型的,第一个参数是一个数组,每个数组就是一个pollfd的类型
返回值:-1 出错 0 超时 >0 就绪文件描述符的个数
3.1 用户关注的文件描述符的值可以更大,一个文件描述符使用一个int值来表示,
3.2 用户关注的文件描述符的个数由用户数组决定,第一个参数传递的是数组首地址,这个数组是由用户定义的,
4、 poll返回的就是文件描述符个数,poll检测就绪文件描述符的时间复杂度依旧是O(n),poll返回后,用户程序依旧需要循环检测哪些文件描述符就绪。
5、poll的事件类型:
POLLPRI:这里需要说明的就是TCP的带外数据,就是TCP报头里面的那个紧急指针。
#define _GNU_SOURCE
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#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 inster_fds(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 caddr,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));
assert(res != -1);
listen(sockfd ,5);
struct pollfd fds[SIZE];//定义数组
init_fds(fds);//初始化数组
inster_fds(fds,sockfd,POLLIN);//先将链接文件描述符插入数组里,只有读事件,所以选择POLLIN事件
while(1)
{
int n = poll(fds,SIZE,-1);//
if(n <= 0)
{
printf("poll error\n");
continue;
}
//n>0,说明有文件描述符就绪,进行处理
//将整个数组遍历一遍,检查哪个文件描述符就绪
int i = 0;
for(; i < SIZE ;++i)
{
if(fds[i].fd != -1)//fd != -1 才能进行判断l
{
int fd = fds[i].fd;
if(fds[i].revents & POLLRDHUP)//如果有关闭TCP链接的事件发生,进行断开链接处理
{
close(fd);
delete_fd(fds,fd);
}
else if(fds[i].revents & POLLIN)//检查是否有读事件就绪
{
if(fd == sockfd)//代表有客户端链接事件发生
{
int len = sizeof(caddr);
int c = accept(fd,(struct sockaddr *) &caddr ,&len);
if(c < 0)//出错处理
{
continue;
}
inster_fds(fds,c,POLLIN|POLLRDHUP);//将用户的链接套接字添加进数组里
}
else//代表客户端发送事件发生,进行接收数据处理
{
char buff [128] = {0};
recv(fd,buff,127,0);
printf("%d : %s",fd,buff);
send(fd,"ok",2,0);
}
}
}
}
}
}
epoll:Linux独有
int epoll_create(int size);à 创建内核事件表,内核时间表底层是由红黑树实现的
int epoll_ctl(int epollfd,int cmd,int fd,struct epoll_event * event); à 设置内核事件表,设置(添加,修改,删除)事件包括
cmd:EPOLL_CTL_ADD/EPOLL_CTL_MOD/EPOLL_CTL_DELETE
fd:要操作的文件描述符
int epoll_wait(int epollfd,struct epoll_event * revents,int maxevents,int timeout);
epoll_wait返回的是就绪文件描述符的个数
revents 只返回就绪的文件描述符
maxevents 是数组的大小,一次能返回多少个就绪的文件描述符
epoll使用的优点:
用户关注的事件由内核维护,每次调用epoll_wait时,不需要将用户空间的数据拷贝到内核空间。
每次epoll只会返回就绪的文件描述符,每次用户程序检测就绪文件描述符的效率O(1)。 epoll的内核实现比select和poll高效
下面是epoll_event结构体的成员:
select和poll,内核监听的时候采用的是轮询的方式,epoll采用的是回调的方式,
epoll支持高效的ET模式
epoll实现代码:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define SIZE 100
int main()
{
int sockfd = socket(AF_INET,SOCK_STREAM,0);
assert(sockfd != -1);
struct sockaddr_in caddr,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));
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 eorror!\n");
continue;
}
int i = 0;
for(;i< n;++i)
{
int fd = events[i].data.fd;
if(fd == sockfd)
{
int len = sizeof(caddr);
int c = accept(fd,(struct sockaddr*)&caddr,&len);
if(c<0)
{
continue;
}
event.events = EPOLLIN | EPOLLRDHUP;
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 is 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);
}
}
}
}
总结:select 、 poll、epoll 比较:
系统调用 |
select |
poll |
epoll |
事件集合 |
用户通过三个参数分别传入感兴趣的可读可写及异常等事件,内核通过对这些参数的在线修改,来反馈其中的就绪事件,这使得用户在每次调用select都要重置这三个参数 |
统一处理所有的事件类型,因此只需要一个事件集参数。用户通过pollfd.events传入感兴趣的事件,内核通过修改pollfd.revents反馈其中的的就绪事件。 |
内核通过一个事件表直接管理用户感兴趣的所有事件,因此每次调用epoll_wait时,无需反复传入用户感兴趣的事件。epoll_wait系统调用的参数events仅用来反馈就绪的事件。 |
应用程序索引就绪文件描述符的时间复杂度 |
O(n) |
O(n) |
O(1) |
最大支持的文件描述符的个数 |
一般有最大限制值 |
65535 |
65535 |
工作模式 |
LT |
LT |
ET和LT |
内核实现和工作效率 |
采用轮询方式来检测就绪的事件 |
采用轮询的方式来检测就绪事件 |
采用回调方式来检测就绪事件。 |