linux I/O复用--------epoll
epoll是linux特有的I/O复用函数。它在实现和使用上与select,poll有很大的差异。首先,epoll使用一组函数来完成任务,而不只是单个函数,其次,epoll把用户关心的文件描述符上的事件都放在内核里的一个时间表中,而无需像select和epoll那样每次调用都要重复传入文件描述符集或事件集,但epoll需要使用一个额外的文件描述符来唯一标识内核中的这个时间表,这个文件描述符使用如下epoll_create函数来创建。
int epoll_create(int size);用于创建一个文件描述符来唯一标识内核中的事件表。size不起作用,他只是告诉内核事件表需要多大。
对事件表的操作:
int epoll_ctl(int epfd,int op,int fd,struct epoll_event* event);
epfd:是int epoll_create();函数的返回值(标识内核事件表的文件描述符)。
Op:指定操作类型:
1:EPOLL_CTL_ADD:往内核事件表上注册fd的事件
2:EPOLL_CTL_MOD:修改内核时间表上的事件
3:EPOLL_CTL_DEL:删除内核事件表上的事件
Struct epoll_event* event:此处的event只是一个结构体变量
Struct epoll_event
{
_uint32_t event; //epoll事件
Epoll_data_t data: //用户数据
}
Typedef union epoll_data
{
Void* ptr;
Int fd; //关注的事件类型
}epoll_data_t;
epoll_wait():在一段时间内等待一组文件描述符上的事件
Int epoll_wait(int epfd,struct epoll_event* events,int maxevents,int timeout); 成功时返回就绪文件描述符的个数 失败返回-1
Epfd:是int epoll_create();函数的返回值(标识内核事件表的文件描述符)。
struct epoll_event* events:events是struct epoll_event类型的一个数组(存在于用户空间,用于输出epoll_wait()检测到的就绪事件)
maxevents:数组的大小(指定监听多少个事件)
timeout:超时时间,以毫秒记
epoll_wait函数如果检测到事件,就将所有有序的事件从内核事件表中复制到它的第二个参数events指向的数组中,这个数组只用于输出epoll_wait检测到的就绪事件,而不像select和poll的数组参数那样即用于传入用户注册的事件,又用于输出内核检测到的就绪事件,这要就极大地提高了索引就绪文件描述符的效率。
一幅图可能能让大家更好的理解epoll带给我们的方便。
epoll的优点:
1:用户关注的文件描述符记录在内核事件表中,无需两次拷贝(实际有一次拷贝,就是epoll_wait()函数检测到事件,就将就绪事件从内核事件表中拷贝到用户空间events结构体数组中,但是这次拷贝很小几乎可以忽略不计)
2:关注的文件描述符的个数以及大小受系统限制(系统有规定一个进程所能打开的文件描述符的个数)
3:返回的就是就绪文件描述符,所以检索文件描述符的时间复杂度为O(1)
epoll的缺点:
它的缺点就是它关注的文件描述符很少,就绪的文件描述符很多时,虽然它不需要轮询检测但是它调用回调函数也需要消耗栈帧空间,
epoll的 客户端和服务器发送数据的代码:
/********************客户端************************/
#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<sys/epoll.h>
#define MAX 1024
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 epoll_event event;
int epfd=epoll_create(5);
event.data.fd=listenfd;
event.events=EPOLLIN;
epoll_ctl(epfd,EPOLL_CTL_ADD,listenfd,&event);
struct epoll_event events[MAX];
while(1)
{
int n=epoll_wait(epfd,events,MAX,-1);
printf("epoll_wait return\n");
if(n==-1)
{
printf("epoll error\n");
exit(0);
}
else
{
int i=0;
for(;i<n;++i)
{
int fd=events[i].data.fd;
if(fd==listenfd)
{
int len=sizeof(cli);
int c=accept(listenfd,(struct sockaddr*)&cli,&len);
if(c<0)
{
printf("one client link error\n");
continue;
}
printf("one client link\n");
event.data.fd=c;
event.events=EPOLLIN|EPOLLRDHUP;
epoll_ctl(epfd,EPOLL_CTL_ADD,c,&event);
}
else
{
if(events[i].events&EPOLLRDHUP)
{
close(fd);
epoll_ctl(epfd,EPOLL_CTL_DEL,fd,&event);
printf("events error\n");
continue;
}
if(events[i].events&EPOLLIN)
{
while(1)
{
char buff[128]={0};
int n=recv(fd,buff,127,0);
if(n<=0)
{
printf("not get\n");
close(fd);
epoll_ctl(epfd,EPOLL_CTL_DEL,fd,&event);
continue;
}
printf("%d %s\n",fd,buff);
send(fd,"ok",2,0);
}
}
}
}
}
}
}