为处理大量句柄而作改进的poll
创建epoll模型
int epoll_create(int size);
在内核里创建epoll模型:
- 创建红黑树
- 创建队列
- 建立底层回调机制
注册事件
int epoll_ctl(int epfd,int op,int fd,struct epoll_event *event);
epfd:epoll_create的返回值。
op:表示处理动作。
- EPOLL_CTL_ADD:注册新的fd到epfd中。
- EPOLL_CTL_MOD:修改已经注册的fd的监听事件。
- EPOLL_CTL_DEL:从epfd中删除一个fd。
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(可写);
#include <sys/epoll.h>
int epoll_wait(int epfd, struct epoll_event *events,int maxevents, int timeout);
编写epoll服务器
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<sys/epoll.h>
#define MAX 128
int startup(int port){
int sock=socket(AF_INET,SOCK_STREAM,0);
if(sock<0){
perror("socket\n");
exit(2);
}
int opt=1;
setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));
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\n");
exit(4);
}
if(listen(sock,5)<0){
perror("listen\n");
exit(2);
}
return sock;
}
void serviceIO(int epfd,struct epoll_event* revs,int num,int listen_sock){
int i=0;
struct epoll_event ev;
for(;i<num;i++){
int fd=revs[i].data.fd;
if(revs[i].events&EPOLLIN){//如果是可读事件
//read;
if(fd==listen_sock){
struct sockaddr_in client;
socklen_t len=sizeof(client);
int new_fd=accept(fd,(struct sockaddr*)&client,&len);//建立连接
if(new_fd<0){
perror("accept");
continue;
}
printf("get a new client[%s:%d]\n",inet_ntoa(client.sin_addr),ntohs(client.sin_port));
ev.events=EPOLLIN;
ev.data.fd=new_fd;
epoll_ctl(epfd,EPOLL_CTL_ADD,new_fd,&ev);
}else {
char buf[1024];
ssize_t s=read(fd,buf,sizeof(buf));
if(s>0){
buf[s]=0;
printf("%s\n",buf);
ev.events=EPOLLOUT;
ev.data.fd=fd;
epoll_ctl(epfd,EPOLL_CTL_MOD,fd,&ev);
}else if(s==0){
printf("client quit\n");
close(fd);
epoll_ctl(epfd,EPOLL_CTL_DEL,fd,NULL);
}else{
perror("read");
close(fd);
epoll_ctl(epfd,EPOLL_CTL_DEL,fd,NULL);
}
}
}
if (revs[i].events&EPOLLOUT){
//write
const char* msg="HTTP/1.0 200 ok\r\n\r\n<html><h1>EPOLL SUCESS</h1><html>\r\n";;
write(fd,msg,strlen(msg));
epoll_ctl(fd,EPOLL_CTL_DEL,fd,NULL);
}
close(epfd);
}
}
int main(int argc,char* argv[]){
if(argc!=2){
printf("usage:%s [port]\n",argv[0]);
return 1;
}
//创建监听套接字
int listen_sock=startup(atoi(argv[1]));
//创建epoll
int epfd=epoll_create(256);
if(epfd<0){
perror("epoll_create");
return 5;
}
//添加监听到的文件描述符以及处理动作
struct epoll_event ev;
ev.events=EPOLLIN;
ev.data.fd=listen_sock;
epoll_ctl(epfd,EPOLL_CTL_ADD,listen_sock,&ev);
struct epoll_event revs[MAX];
int size=0;
for(;;){
switch(size=epoll_wait(epfd,revs,MAX,-1)){//等待文件描述符对应的事件发生
case -1:
perror("epoll_wait");
break;
case 0:
printf("timeout\n");
break;
default:
serviceIO(epfd,revs,size,listen_sock);
break;
}
}
}
epoll优点:
文件描述符数目无上限。
一旦某个文件描述符就绪,内核会迅速采用回调机制迅速激活这个文件描述符。
当文件描述符就绪,会被放入就绪队列中。事件复杂度为O(1)
内存映射机制:内核直接将就绪队列通过mmap方式映射到用户态。