相关API
epoll_create
epoll_create(int size);//创建epoll模型
注:在linux2.68之后,size被忽略
使用epoll_create
创建模型的时候,内核会创建红黑树和就绪队列以及回调机制用来维护所关心的文件描述符。
- 红黑树的节点用来保存用户所关心的文件描述符
- 就绪队列用来保存已经响应的文件描述符
epoll_ctl
epoll_ctl(int epfd,int op,int fd,struct epoll_event* event);
epfd:使用epoll_create创建的句柄
- op:需要使用的操作
- EPOLL_CTL_ADD:添加文件描述符
- EPOLL_CTL_MOD:修改已经注册的fd
- EPOLL_CTL_DEL:删除已经注册的fd
event结构如下:
typedef union epoll_data {
void *ptr;
int fd;
uint32_t u32;
uint64_t u64;
} epoll_data_t;struct epoll_event { uint32_t events; /* Epoll events */ epoll_data_t data; /* User data variable */ };
- events可以是一下集合:
- EPOLLIN:对应描述符可读
- EPOLLOUT:对应描述符可写
- EPOLLPRI:对应的描述符有紧急的数据可读
- EPOLLERR:对应的描述符发生错误
- EPOLLHUP:对应的描述符被挂断
- EPOLLET:将epoll设置成ET模式
- EPOLLONESHOT:只监听一次事件,如果还想继续监听需要将文件描述符添加到监听队列里面
epoll_wait
int epoll_wait(int epfd,struct epoll_event* events,int maxevents,int timeouts);
events:用户传入的就绪队列缓冲区,用于存储已经就绪的文件描述符
maxevents:传入缓冲区的大小,不能超过epoll_create(size)中size的大小
epoll工作原理
- 每一个epoll对象都有一个eventpoll的结构体,用来存储用来存放epoll添加进来的事件
- 将添加进来的事件加入到红黑树里面。
- 添加进来的事件和设备驱动程序建立关系,当事件发生响应的时候调用回调方法。
- 回调方法将发生的事件添加到rdlist链表里面
- 在epoll中每一个事件都会创建epitem结构体
- 当调用epoll_wait的时候,只需要检查eventpoll中的rdlist中是否有epitem元素即可
- 如果rdlist不为空,将rdlist里面的数据复制到用户区
epoll的优点
- 文件描述符没有上线,通过红黑树的结构管理文件描述符
- 基于事件的同时方法,一旦被监听的文件描述符就绪,系统调用回调机制激活文件描述符,随着文件描述符增多,并不会影响性能
- 对于就绪的文件描述符系统维护的是就绪队列,主需要在就绪队列里面取元素即可,操作时间复杂度O(1)
实例:
#include<stdio.h>
#include<fcntl.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<sys/epoll.h>
#include<arpa/inet.h>
#include<string.h>
#define EPOLLMAX 128
int startup(int port){
int sock = socket(AF_INET,SOCK_STREAM,0);
if(sock < 0){
perror("socket");
exit(2);
}
struct sockaddr_in local;
local.sin_family = AF_INET;
local.sin_port = htons(port);
local.sin_addr.s_addr = htonl(INADDR_ANY);
if(bind(sock,(struct sockaddr*)&local,sizeof(local)) < 0){
perror("bind");
exit(3);
}
if(listen(sock,5) < 0){
perror("listen");
exit(4);
}
return sock;
}
int main(int argc,char* argv[]){
if(argc != 2){
printf("Usage:%s[port]",argv[1]);
exit(1);
}
int listenfd = startup(atoi(argv[1]));
int epfd = epoll_create(EPOLLMAX);
if(epfd < 0){
perror("epoll_create");
exit(5);
}
struct epoll_event ev;
ev.events = EPOLLIN;
ev.data.fd = listenfd;
if(epoll_ctl(epfd,EPOLL_CTL_ADD,listenfd,&ev) < 0){
perror("epoll_ctl");
exit(9);
}
for(;;){
struct epoll_event rcvfds[EPOLLMAX];
int num = epoll_wait(epfd,rcvfds,EPOLLMAX,-1);
if(num < 0){
perror("epoll_wait");
exit(6);
}
if(num == 0){
printf("time out...\n");
exit(7);
}
int i =0;
struct epoll_event nev;
for(;i<num;i++){
if(rcvfds[i].data.fd == listenfd){
struct sockaddr_in client;
socklen_t len = sizeof(client);
int new_fd = accept(listenfd,(struct sockaddr*)&client,&len);
if(new_fd < 0){
perror("accept");
continue;
}
printf("Get a connect:[%s]\n",inet_ntoa(client.sin_addr));
nev.events = EPOLLIN;
nev.data.fd = new_fd;
if(epoll_ctl(epfd,EPOLL_CTL_ADD,new_fd,&nev) < 0){
perror("epoll_ctl");
continue;
}
}
else{
if(rcvfds[i].events == EPOLLIN){
char buf[1024];
ssize_t r = read(rcvfds[i].data.fd,buf,sizeof(buf)-1);
if(r < 0){
perror("read");
continue;
}
if(r == 0){
printf("client is close...\n");
close(rcvfds[i].data.fd);
if(epoll_ctl(epfd,EPOLL_CTL_DEL,rcvfds[i].data.fd,NULL) < 0){
//perror("epoll_ctl");
printf("100line\n");
continue;
}
}
else{
buf[r] = 0;
printf("client:%s\n",buf);
}
nev.events = EPOLLOUT;
nev.data.fd = rcvfds[i].data.fd;
if(epoll_ctl(epfd,EPOLL_CTL_MOD,rcvfds[i].data.fd,&nev) < 0){
perror("epoll_ctl");
continue;
}
}
if(rcvfds[i].events == EPOLLOUT){
const char* buf = "http/1.0 200 ok \r\n\r\n<html><h1>hello word...\n</h1></html>";
write(rcvfds[i].data.fd,buf,strlen(buf));
nev.events = EPOLLIN;
nev.data.fd = rcvfds[i].data.fd;
if(epoll_ctl(epfd,EPOLL_CTL_MOD,rcvfds[i].data.fd,&nev) < 0){
perror("epoll_ctl");
continue;
}
}
}
}
}
return 0;
}