epoll
epoll 是 Linux 特有的 I/O 复用函数。它在实现和使用上与select、poll 有很大差异。首先,epoll 使用一组函数来完成任务,而不是单个函数。其次,epoll 把用户关心的文件描述符上的事件放在内核里的一个事件表中,从而无须像 select 和 poll 那样每次调用都要重复传入文件描述符集或事件集。但 epoll 需要使用一一个额外的文件描述符, 来唯一标识内核中的这个事件表。这个文件描述符使用如下 epoll_ create 函数来创建:
epoll的两种模式
LT
电平触发 如果 epoll_wait 将事件返回给应用程序,应用程序可以不立即处理事件,则下一次 epoll_wait 还会将该事件通知给应用程序。
ET
边沿触发 高效模式 内核事件表上的文件描述符必须关注 EPOLLET 事件,同一事件仅仅被触发一次
下一次调用 epoll_wait 不会再一次将该事件通知给应用程序,每次必须将就绪文件描述符上的事件处理完成。
1、文件描述符必须设置为非阻塞模式
2、内核事件表上的文件描述符必须关注 EPOLLET 事件
3、当事件发生时,必须以循环的方式处理事件,知道事件处理完成 recv 返回值<=0 error
epoll的使用
epoll:一組函数
创建内核事件表
int epoll_create(int size); //创建内核事件表//底层实现是红黑树
内核事件表:在系統内核中创建一个用于记录用户关注的文件描述符上的事件的一个表
size:用来告诉内核要监听的数目一共有多少个。
select: fd_set read; //用户空向
poll: struct pollfd fds[n]; //用户空向
返回值: 失敗返回 -1
成功返回内核事件表的标识符
事件注册
设置(添加、修改、删除)内核事件表中的文件描述符上的事件
#includ <sys/epoll.h>
int epoll_ctl (int epfd, int op, int fd, struct epoll_ event *event) ;
epfd:内核事件表 ID
op:指定操作类型
fd:要操作的文件描述符
event:指定事件
op 参数指定类型:
EPOLL_CTL_ADD 往事件表中注册 fd 上的事件
EPOLL_CTL_DEL 修改 fd 上的注册事件
EPOLL_CTL_MOD 删除 fd 上的注册事件
struct epoll_event
{
uint32_t events; //用户关注的事件 EPOLLIN
epoll_data_t data;
};
union epoll_data_t
{
int fd; //用户关注的文件描述符
void *ptr;
uint32_t 1n32;
uint64_t 164;
}:
epoll_wait 系统调用的主要接口,在一段超时时间内等待一组文件描述符上的事件
#includ <sys/epoll.h>
int epoll_wait(int epfd, struct epoll_event *events,int eventsLen, int timeout);
events:指向一个用户数组,此数組用于保存 epoll_wait 返回时,内核填充的就绪文件描述符信息,只返回所有就绪的文件描述符
eventsLen:指定数組元素的个数
返回值:=0 超时
=-1 失败
>0 就绪文件描述符的个数
epoll 比 select 和 poll 高效的地方
1、用户关注的事件直接保存到内核事件表中,每次 epoll_wait 不需要从用户空间向内核空间拷贝
2、epoll_wait 返回时,只需要将就绪的文件描述符拷贝到用户空间的数组上,select 和 poll 每次都将所有(就绪和未就绪)的文件描述符返问。
3、epoll 用户探测就绪文件描述符的时间复杂度位 O(1)
4、epoll 内核实现比 select 和 poll 高效。
5、epoll支持高效的 ET 模式
Sever
#define _GNU_SOURCE
#include <stdio.h>
#include <string.h>
#include <assert.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/epoll.h>
#define FDMAXNUM 100
void GetClientLink(int fd,int epfd)
{
struct sockaddr_in cli;
int len=sizeof(cli);
int c=accept(fd,(struct sockaddr*)&cli,&len);
if(c==-1)
{
return;
}
struct epoll_event event;
event.data.fd=c;
event.events=EPOLLIN|EPOLLRDHUP;
epoll_ctl(epfd,EPOLL_CTL_ADD,c,&event);
}
void CloseClient(int fd,int epfd)
{
close(fd);
epoll_ctl(epfd,EPOLL_CTL_DEL,fd,NULL);
}
void DealClientData(int fd,int epfd)
{
char buff[128]={0};
int n=recv(fd,buff,127,0);
if(n<=0)
{
printf("one client over;\n");
CloseClient(fd,epfd);
return;
}
else
{
printf("read:%s",buff);
send(fd,"OK",2,0);
}
}
void DealFinshEvent(int listenfd,int epfd,struct epoll_event *events,int n)
{
int i=0;
for(;i<n;i++)
{
int fd=events[i].data.fd;
if(fd==listenfd)
{
GetClientLink(fd,epfd);
}
else if(events[i].events & EPOLLRDHUP)
{
CloseClient(fd,epfd);
}
else if(events[i].events & EPOLLIN)
{
DealClientData(fd,epfd);
}
}
}
int main()
{
int listenfd = socket(AF_INET, SOCK_STREAM, 0);
assert(listenfd != -1);
struct sockaddr_in ser;
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(listenfd, (struct sockaddr*)&ser, sizeof(ser));
assert(res != -1);
listen(listenfd, 5);
int epfd=epoll_create(FDMAXNUM);
assert(epfd!=-1);
struct epoll_event event;
event.data.fd=listenfd;
event.events=EPOLLIN;
res=epoll_ctl(epfd,EPOLL_CTL_ADD,listenfd,&event);
assert(res!=-1);
while(1)
{
struct epoll_event events[FDMAXNUM];
int n=epoll_wait(epfd,events,FDMAXNUM,-1);
if(n<=0)
{
exit(0);
}
DealFinshEvent(listenfd,epfd,events,n);
}
}
Cli
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <unistd.h>
#include <string.h>
#include <netinet/in.h>//字节序列转换函数所用
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>//地址转换函数所用
int main()
{
int sockfd=socket(AF_INET,SOCK_STREAM,0);
assert(-1!=sockfd);
struct sockaddr_in ser;
memset(&ser,0,sizeof(ser));
ser.sin_family=AF_INET;
ser.sin_port=htons(6000);
inet_aton("127.0.0.1",(struct in_addr*)&ser.sin_addr);
int res=connect(sockfd,(struct sockaddr*)&ser,sizeof(ser));
assert(-1!=res);
while(1)
{
printf("please input: ");
char data[128]={0};
fgets(data,128,stdin);
if(strncmp(data,"bye",3)==0)
{
close(sockfd);
break;
}
send(sockfd,data,strlen(data)+1,0);
char buff[128]={0};
int n=recv(sockfd,buff,127,0);
if(n<=0)
{
printf("error\n");
close(sockfd);
break;
}
printf("n==%d: %s\n",n,buff);
}
}