文章链接:https://codemouse.online/archives/2020-02-24153745
epoll结构体的定义:
struct epoll_event {
__uint32_t events; /* epoll event */
epoll_data_t data; /* User data variable */
};
typedef union epoll_data {
void *ptr;
int fd;
__uint32_t u32;
__uint64_t u64;
} epoll_data_t;//保存触发事件的某个文件描述符相关的数据
工作原理
epoll是在唯一个红黑树和一个就绪链表.
内核不仅会将事件存放到红黑树中,还会将所有的epoll对象与设备驱动程序(中断处理程序)建立回调关系(注册回调函数),当一个事件的中断到达,就把事件存放到链表中。所以当一个事件就绪时,内核把网卡上的数据拷贝到内核中后就把事件存放到就绪链表中,这样,一棵红黑树,一个链表就解决了大量并发的描述符问题。
epoll的创建
-
创建一个epoll的fd, size用来告诉内核这个监听的数目一共有多大
int epoll_create(int size);
-
绑定fd的事件的类型
struct epoll_event ep_ev;
ep_ev.events = EPOLLIN;//数据的读取
ep_ev.data.fd = listen_sock; -
events的宏的集合:
EPOLLIN :表示对应的文件描述符可以读(包括对端SOCKET正常关闭);
EPOLLOUT:表示对应的文件描述符可以写;
EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);
EPOLLERR:表示对应的文件描述符发生错误;
EPOLLHUP:表示对应的文件描述符被挂断;
EPOLLET: 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的。
EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里 -
添加epoll的事件,并设置事件的类型
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
epoll_ctl(epoll_fd,EPOLL_CTL_ADD,listen_sock,&ep_ev);
EPOLL_CTL_ADD:注册新的fd到epfd中;
EPOLL_CTL_MOD:修改已经注册的fd的监听事件;
EPOLL_CTL_DEL:从epfd中删除一个fd -
轮循事件
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
返回值是发生事件的个数
struct epoll_event ready_ev[128];//申请空间来放就绪的事件。
int maxnum = 128;
int timeout = 1000;
epoll_wait(epoll_fd,ready_ev,maxnum,timeout); -
判断是哪种事件
ready_ev[i].events & EPOLLIN
-
获取fd
int fd = ready_ev[i].data.fd;
-
使用结束记得要close掉fd,防止资源的浪费
close(fd);
注意
添加事件的时候,尽量使用EPOLLET边缘触发,防止在多线程的时候,多次出发,重复读
epoll demo
void epoll_use(int listen_sock){
int epoll_fd = epoll_create(256);
if(epoll_fd < 0){
perror("epoll creat");
exit(6);
}
struct epoll_event ep_ev;
ep_ev.events = EPOLLIN;//数据的读取
ep_ev.data.fd = listen_sock;
//添加关心的事件
if(epoll_ctl(epoll_fd,EPOLL_CTL_ADD,listen_sock,&ep_ev) < 0){
perror("epoll_ctl");
exit(7);
}
struct epoll_event ready_ev[128];//申请空间来放就绪的事件。
int maxnum = 128;
int timeout = 1000;//设置超时时间,若为-1,则永久阻塞等待。
int ret = 0;
int done = 0;
while(!done){
ret = epoll_wait(epoll_fd,ready_ev,maxnum,timeout);
if(ret == -1)
{
perror("epoll_wait");
continue;
}
else if(ret == 0)
{
printf("time out...\n");
continue;
}
//遍历events数组
int i = 0;
for(;i < ret;++i){
//判断是否为监听套接字,是的话accept
int fd = ready_ev[i].data.fd;
if((fd == listen_sock) && (ready_ev[i].events & EPOLLIN)){
struct sockaddr_in remote;
socklen_t len = sizeof(remote);
int accept_sock = accept(listen_sock,(struct sockaddr*)&remote,&len);
if(accept_sock < 0){
perror("accept");
continue;
}
printf("accept a client..[ip]: %s,[port]: %d\n",inet_ntoa(remote.sin_addr),ntohs(remote.sin_port));
//将新的事件添加到epoll集合中
ep_ev.events = EPOLLIN | EPOLLET;
ep_ev.data.fd = accept_sock;
set_noblock(accept_sock);
if(epoll_ctl(epoll_fd,EPOLL_CTL_ADD,accept_sock,&ep_ev) < 0){
perror("epoll_ctl");
close(accept_sock);
}
}
else{//普通IO
if(ready_ev[i].events & EPOLLIN){
//申请空间同时存文件描述符和缓冲区地址
char buf[102400];
memset(buf,'\0',sizeof(buf));
ssize_t _s = recv(fd,buf,sizeof(buf)-1,0);
if(_s < 0){
perror("recv");
continue;
}else if(_s == 0){
printf("remote close..\n");
//远端关闭了,进行善后
epoll_ctl(epoll_fd,EPOLL_CTL_DEL,fd,NULL);
close(fd);
}else{
//读取成功,输出数据
printf("client# %s",buf);
fflush(stdout);
//将事件改写为关心事件,进行回写
ep_ev.data.fd = fd;
ep_ev.events = EPOLLOUT | EPOLLET;
//在epoll实例中更改同一个事件
epoll_ctl(epoll_fd,EPOLL_CTL_MOD,fd,&ep_ev);
}
}else if(ready_ev[i].events & EPOLLOUT){
const char*msg = "hello client\n";
send(fd,msg,strlen(msg),0);
epoll_ctl(epoll_fd,EPOLL_CTL_DEL,fd,NULL);
close(fd);
}
}
}
}
close(listen_sock);
}