您的位置:首页 > 其它

[置顶] epoll服务器的介绍和实现

2017-07-03 13:05 204 查看
一、epoll解释:

epoll是Linux下多路复用IO接口select/poll的增强版本,Linux内核为处理大批量句柄而作了改进的poll。要使用epoll只需要这三个系统调用:epoll_create、epoll_ctl、epoll_wait。它是在2.5.44内核中被引进的,在2.6内核中得到广泛应用。

  它能显著减少程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率,因为它不会复用文件描述符集合来传递结果而迫使开发者每次等待事件之前都必须重新准备要被侦听的文件描述符集合。

  获取事件的时候,它无须遍历整个被侦听的文件描述符集,只要遍历那些被内核IO事件异步唤醒而加入Ready队列的描述符集合就行了。

  epoll除了提供select/poll 那种IO事件的电平触发外,还提供了边沿触发,这就使得用户空间程序有可能缓存IO状态,减少epoll_wait的调用,提高应用程序效率。

1.epoll底层实现

epoll在底层实现了自己的高速缓存区,并且建立了一个红黑树用于存放socket,另外维护了一个链表用来存放准备就绪的事件。

2.epoll工作原理:

  执行epoll_ create时,创建了红黑树和就绪链表,执行epoll_ ctl时,如果增加socket句柄,则检查在红黑树中是否存在,存在立即返回,不存在则添加到树干上,然后向内核注册回调函数,用于当中断事件来临时向准备就绪链表中插入数据。执行epoll_wait时立刻返回准备就绪链表里的数据即可。

3.epoll服务器代码

#include<stdio.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<fcntl.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/epoll.h>
#include<string.h>

typedef struct ep_buff
{
int fd;
char buff[1024];
}ep_buff_t,*ep_buff_p;

void *alloc_ep_buff(int fd)
{
ep_buff_p n=(ep_buff_p)malloc(sizeof(ep_buff_t));
if(!n)
{
perror("malloc");
exit(6);
}

n->fd=fd;
return n;
}

int set_fd_nonblack()
{}

int myread()
{
while(1)
{}
}

int mywrite()
{
while(1)
{}
}

int myaccept(int epfd,int listen_sock)
{}

static void usage(const char*proc)
{
printf("Usage: %s [local_ip] [local_port]\n",proc);
}

int startup(const char*_ip,int _port)
{
int sock=socket( AF_INET ,SOCK_STREAM, 0);
if(sock<0)
{
perror("socket");
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=inet_addr(_ip);

if(bind(sock,(struct sockaddr*)&local,sizeof(local))<0)
{
perror("bind");
exit(3);
}

if(listen(sock,10))
{
perror("listen");
exit(4);
}
return sock;
}

int main(int argc,char* argv[])
{
if(argc!=3)
{
usage(argv[0]);
return 1;
}

//create epoll model
int listen_sock=startup(argv[1],atoi(argv[2]));

int epfd=epoll_create(256);
if(epfd<0)
{
perror("epfd");
return 5;
}

struct epoll_event ev;
ev.events=EPOLLIN | EPOLLET;
ev.data.ptr=alloc_ep_buff(listen_sock);
epoll_ctl(epfd,EPOLL_CTL_ADD,listen_sock,&ev);

int nums=-1;
struct epoll_event revs[64];
int timeout =-1;

while(1)
{
switch((nums=epoll_wait(epfd,revs,64,timeout)))
{
case 0:
printf("timeout...\n");
break;
case -1:
perror("epoll_wait");
break;
default:
{
int i=0;
for(;i<nums;i++)
{
int sock=((ep_buff_p)(revs[i].data.ptr))->fd;
if(sock==listen_sock && (revs[i].events&EPOLLIN))
{
struct sockaddr_in client;
socklen_t len=sizeof(client);
int new_sock=accept(listen_sock,(struct sockaddr*)&client,&len);

if(new_sock<0)
{
perror("accept");
continue;
}

printf("get client:[%s:%d]",inet_ntoa(client.sin_addr),ntohs(client.sin_port));
ev.events=EPOLLIN;
ev.data.ptr=alloc_ep_buff(new_sock);
epoll_ctl(epfd,EPOLL_CTL_ADD,new_sock,&ev);
}//listen_sock

else if(sock!=listen_sock)
{
if(revs[i].events&EPOLLIN)//normol fd read evevts readlly
{
char buf[10240];
ssize_t s=read(sock,buf,sizeof(buf)-1);

if(s>0)
{
printf("client:%s\n",buf);
ev.events=EPOLLOUT;
// ((ep_buff_p)(ev.data.ptr))->fd=sock;

epoll_ctl(epfd,EPOLL_CTL_MOD,sock,&ev);
}

else if(s==0)
{
printf("client quit!!\n");
close(sock);

epoll_ctl(epfd,EPOLL_CTL_DEL,sock,NULL);
free(revs[i].data.ptr);
}

else
{
perror("read");
close(sock);
epoll_ctl(epfd,EPOLL_CTL_DEL,sock,NULL);
free(revs[i].data.ptr);
}
}//litetn_sock

else if(revs[i].events&EPOLLOUT)//write events readlly
{
const char* msg="HTTP/1.0 ok 200\r\n\r\n<html><h1>hello epoll!</h1></html>";
write(sock,msg,strlen(msg));
close(sock);
epoll_ctl(epfd,EPOLL_CTL_DEL,sock,NULL);
free(revs[i].data.ptr);
}

else
{}
}
}
}
break;
}
}
return 0;
}


4.epoll的优缺点:

优点:

a. 支持一个进程打开大数目的socket描述符(FD)

b.IO效率不随FD数目增加而线性下降

c.使用mmap加速内核与用户空间的消息传递。

5.总结

(1)select,poll实现需要自己不断轮询所有fd集合直到设备就绪,期间可能要睡眠和唤醒多次交替。epoll也需要调用 epoll_ wait不断轮询就绪链表,期间也可能多次睡眠和唤醒交替,但它会在设备就绪时,调用回调函数,把就绪fd放入就绪链表中,并唤醒在 epoll_wait中进入睡眠的进程。 

  虽然都要睡眠和交替,但是select和poll在“醒着”的时候要遍历整个fd集合,而epoll在“醒着”的 时候只要判断一下就绪链表是否为空就行了,这节省了大量的CPU时间,这就是回调机制带来的性能提升。

(2)select,poll每次调用都要把fd集合从用户态往内核态拷贝一次,并且要把current往设备等待队列中挂一次,而epoll只要一次拷贝,而且把current往等待队列上挂也只挂一次(在epoll_wait的开始,注意这里的等待队列并不是设备等待队列,只是一个epoll内 部定义的等待队列),这也能节省不少的开销。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: