epoll内部使用红黑树数据结构,epoll_create函数创建一个根节点并返回,epoll_ctl函数进行添加删除修改子节点。
epoll操作过程需要三个接口,分别如下:
#include <sys/epoll.h>
int epoll_create(int size);
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
(1) int epoll_create(int size);
创建一个epoll的句柄,size用来告诉内核这个监听的数目一共有多大。这个参数不同于select()中的第一个参数,给出最大监听的fd+1的值。需要注意的是,当创建好epoll句柄后,它就是会占用一个fd值,在linux下如果查看/proc/进程id/fd/,是能够看到这个fd的,所以在使用完epoll后,必须调用close()关闭,否则可能导致fd被耗尽。
(2)int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
epoll的事件注册函数,它不同与select()是在监听事件时告诉内核要监听什么类型的事件epoll的事件注册函数,它不同与select()是在监听事件时告诉内核要监听什么类型的事件,而是在这里先注册要监听的事件类型。第一个参数是epoll_create()的返回值,第二个参数表示动作,用三个宏来表示:
EPOLL_CTL_ADD:注册新的fd到epfd中;
EPOLL_CTL_MOD:修改已经注册的fd的监听事件;
EPOLL_CTL_DEL:从epfd中删除一个fd;
第三个参数是需要监听的fd,第四个参数是告诉内核需要监听什么事,struct epoll_event结构如下:
struct epoll_event {
__uint32_t events; /* Epoll events */
epoll_data_t data; /* User data variable */
};
events可以是以下几个宏的集合:
EPOLLIN :表示对应的文件描述符可以读(包括对端SOCKET正常关闭);
EPOLLOUT:表示对应的文件描述符可以写;
EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);
EPOLLERR:表示对应的文件描述符发生错误;
EPOLLHUP:表示对应的文件描述符被挂断;
EPOLLET: 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的。
EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里
(3) int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
等待事件的产生,类似于select()调用。参数events用来从内核得到事件的集合,maxevents告之内核这个events有多大,这个maxevents的值不能大于创建epoll_create()时的size,参数timeout是超时时间(毫秒,0会立即返回,-1将不确定,也有说法说是永久阻塞)。该函数返回需要处理的事件数目,如返回0表示已超时。
示例
#include "stdlib.h"
#include "sys/epoll.h"
#include "netinet/ip.h"
#include "netinet/in.h"
#include "wrap.h"
#include "strings.h"
#include "arpa/inet.h"
#include "unistd.h"
#include "fcntl.h"
#include "ctype.h"
#include "string.h"
#define PORT 8888
//#define IP "127.0.0.1"
int socket_bind( in_addr_t ip,int port)
{
int listenfd;
struct sockaddr_in servaddr;
bzero(&servaddr,sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr .s_addr= htonl(ip);
//inet_pton(AF_INET,ip,&servaddr.sin_addr);
servaddr.sin_port = htons(port);
listenfd = Socket(AF_INET,SOCK_STREAM,0);
int opt = 1;
setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,(void*)&opt,sizeof(opt));
Bind(listenfd,(struct sockaddr*)&servaddr,sizeof(servaddr));
return listenfd;
}
void handle_accept(int epfd,int listfd)
{
int cfd;
struct sockaddr_in clitaddr;
socklen_t clit_len;
char clit_ip[1024];
bzero(clit_ip,1024);
bzero(&clitaddr,sizeof(clitaddr));
clit_len = sizeof(clit_len);
cfd = Accept(listfd,(struct sockaddr*)&clitaddr,&clit_len);
printf("client ip:%s,port:%d\n",
inet_ntop(AF_INET,&clitaddr.sin_addr.s_addr,clit_ip,1024),
ntohs(clitaddr.sin_port));
struct epoll_event ev;
ev.events = EPOLLIN|EPOLLET;
ev.data.fd = cfd;
epoll_ctl(epfd,EPOLL_CTL_ADD,cfd,&ev);
int flag = fcntl(cfd,F_GETFL);
flag |= O_NONBLOCK;
fcntl(cfd,F_SETFL,flag);
}
char buf[BUFSIZ];
void do_read(int epfd,int cfd)
{
bzero(&buf,BUFSIZ);
int n = read(cfd,buf,BUFSIZ);
if(n == -1)
{
sys_err("read error");
Close(cfd);
epoll_ctl(epfd,EPOLL_CTL_DEL,cfd,NULL);
}else if(n == 0)
{
printf("client exit.code:%d...\n",cfd);
Close(cfd);
epoll_ctl(epfd,EPOLL_CTL_DEL,cfd,NULL);
}else
{
write(STDOUT_FILENO,buf,n);
struct epoll_event ev;
ev.events = EPOLLOUT|EPOLLET;
ev.data.fd = cfd;
epoll_ctl(epfd,EPOLL_CTL_MOD,cfd,&ev);
}
}
void do_write(int epfd,int cfd)
{
int n = strlen(buf);
for (int i = 0; i < n; i++)
{
buf[i] = toupper(buf[i]);
}
write(cfd,buf,n);
struct epoll_event ev;
ev.events = EPOLLIN|EPOLLET;
ev.data.fd = cfd;
epoll_ctl(epfd,EPOLL_CTL_MOD,cfd,&ev);
}
void server_epoll(int listenfd)
{
int epollfd;
struct epoll_event events[1000];
int ret;
epollfd = epoll_create(1000);//创建epoll头结点
struct epoll_event ev;
ev.events = EPOLLIN|EPOLLET;//ET 模式
ev.data.fd = listenfd;
epoll_ctl(epollfd,EPOLL_CTL_ADD,listenfd,&ev);//添加监听套接字
int flag = fcntl(listenfd,F_GETFL);
flag |= O_NONBLOCK;
fcntl(listenfd,F_SETFL,flag);//设置非阻塞
while (1)
{
ret = epoll_wait(epollfd,events,1000,-1);
if(ret == -1)
{
sys_err("epoll wait error");
break;
}
int fd;
//进行遍历
for (int i = 0;i < ret;i++)
{
fd = events[i].data.fd;
//根据描述符的类型和事件类型进行处理
if ((fd == listenfd) &&(events[i].events & EPOLLIN))
handle_accept(epollfd,listenfd);
else if (events[i].events & EPOLLIN)
do_read(epollfd,fd);
else if (events[i].events & EPOLLOUT)
do_write(epollfd,fd);
}
}
Close(listenfd);
Close(epollfd);
}
int main(int args,char *argc[])
{
int listenfd;
listenfd = socket_bind((INADDR_ANY),PORT);
Listen(listenfd,128);
server_epoll(listenfd);
return 0;
}