您的位置:首页 > 其它

I/O多路复用之epoll服务器

2017-07-15 16:32 323 查看
1、epoll服务器的函数

epoll是linux特有的I/O复用函数,它的实现和select和poll有很大的差异。epoll是使用一组函数来完成任务,而不是单个的函数。epoll把用户关心的文件描述符上的事件放在内核里的一个事件表中,而无需向select和poll那样每次调用都要重复传入文件描述符集或事件集,但是epoll却需要一个额外的文件描述符,来唯一标识内核中的这个事件表。

1、epoll_create函数:



这个函数是用来创建epoll事件的模型,它只有一个参数来表示创建的这个事件表是需要多大的。函数返回的文件描述符作为epoll其他函数的第一个参数,用来指定要访问的内核时间表。

2、epoll_ctl函数:



这个函数有四个参数,epfd是epoll_create这个函数的返回值,fd是要操作的文件描述符,op参数是指定的操作类型,而操作类型有以下三种:



EPOLL_CTL_ADD:往事件表上注册fd上的事件

EPOLL_CTL_MOD:修改fd上的注册事件

EPOLL_CTL_DEL:删除fd上的注册事件

event参数指定事件,它是epoll_event结构指针类型。epoll_event的定义如下:



events成员描述事件类型,而epoll_event中的data成员用于存储用户数据,而其中epoll_data_t定义如上图所示。

epoll_data_t是一个联合体,其中用的最多的是fd,它用来指定事件所从属的目标文件描述符。ptr成员可用来指定与fd相关的用户数据。但是epoll_data_t是一个联合体我们不能同时使用ptr成员和fd成员。

epoll_ctl成功的时候返回的是0,失败就会返回-1,并设置errno。

3、epoll_wait函数:



第一个参数epfd还是epoll_create函数的返回值,maxevents表示的是最多监听的 事件,它是必须大于0的。

timeout是要等待的时间,它的含义与poll函数中的timeout的含义是相同的。

epoll_wait这个函数如果检测到事件,就将所有继续的事件从内核事件表复制到它的第二个参数events的数组中去。

2、ET模式和LT模式

epoll对文件描述符的操作有两种模式:LT模式(水平触发)和ET模式(边沿触发),LT是默认的工作模式,在这种模式下epoll相当于一个效率较高的epoll,当往epoll内核事件表中注册一个文件描述符上的EPOLLET事件,epoll将以ET模式来操作该文件描述符,ET模式是epoll的高效工作方式。

ET模式在很大程度上降低了同一个epoll事件的被重复触发的次数,因此它的效率要比LT模式要高。

3、三种服务器的比较



4、epoll服务器的优点

1、底层采用回调机制来激活节点,将已经就绪的文件描述符加到就绪队列中去。

2、当有了新的事件就绪的时候这个事件就会被加到epoll事件的红黑树中去,红黑树的增删查改的时间复杂度(O (N*lgN))是要比数组高很多

3、epoll_wait获得就绪的文件描述符是从就绪队列中获取的,它的时间复杂度为O (1)这是epoll的时间复杂度也是epoll高效的原因。

4、epoll关心的文件描述符是没有上限的

5、就绪队列中的节点会映射到epoll_wait中的events结构体中,节省了一次内核态到用户态的数据拷贝,这是用的mmap技术(内存映射)。

6、就绪事件的陈列的方式是不同的,epoll访问的是就绪队列避免了访问没价值的数据,而select是一个数组保存的。

5、代码实现

epoll.c:

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

#define MAX_EVENTS 10
#define SIZE 64

typedef struct epbuff{
int fd;
char buf[2048];
int index;
}epbuff_t,*epbuff_p,**epbuff_pp;

static epbuff_p alloc_buff(int fd) //此处开空间之后后面就要是ptr指向的一段空间
{
epbuff_p ptr = (epbuff_p)malloc(sizeof(epbuff_t));
if(NULL == ptr)
{
perror("malloc");
exit(9);
}
ptr->fd = fd;
return ptr;
}

static void dealloc(epbuff_p ptr)//既然要malloc就得去free
{
if(NULL != ptr)
{
free(ptr);
ptr = NULL;
}
}

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

int setnonblocking(int sock) //将文件描述符设置为非阻塞的
{
int old_option = fcntl(sock,F_GETFL);
int new_option = old_option | O_NONBLOCK;
fcntl(sock,F_SETFL,new_option);
return old_option;
}

int startup(cons
4000
t 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) < 0)
{
perror("listen");
exit(4);
}
return sock;
}

//实现的是ET版本的epoll服务器  要实现非阻塞版本
int myread(int sock,char* buf)
{
int len = 0;
int total = 0;
while((len = read(sock,buf+total,1024) > 0) && (len = 1024 ))
{
total += len;
}
if(len > 0 && len< 1024)
{
total += len;
}
buf[total] = '\0';
return total;
}

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

int listen_sock = startup(argv[1],atoi(argv[2]));

int epfd = epoll_create(SIZE);//先创建一个红黑树和队列
if(epfd < 0)
{
perror("epoll_create");
exit(5);
}

struct epoll_event ev;
ev.events = EPOLLIN | EPOLLET;
ev.data.ptr = alloc_buff(listen_sock);

setnonblocking(listen_sock);//由于是ET工作模式下  将listen_sock设置为非阻塞的状态

if(epoll_ctl(epfd,EPOLL_CTL_ADD,listen_sock,&ev) < 0)//往事件表上注册fd事件(把listen_sock)加到红黑树上去
{
perror("epoll_ctl");
exit(6);
}

int timeout = 5000;
int nums = -1;

struct epoll_event revs[MAX_EVENTS];
while(1)
{
nums = epoll_wait(epfd,revs,MAX_EVENTS,timeout);
switch(nums)
{
case 0:
printf("time out!!!...\n");
break;
case -1:
perror("epoll_wait");
break;
default:
{
int i = 0;
for(;i < nums ;i++)
{
int sock = ((epbuff_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("myaccept");
continue;
}
printf("get a new client! ip: %s port:%d\n",inet_ntoa(client.sin_addr),ntohs(client.sin_port));
ev.events = EPOLLIN | EPOLLOUT;
setnonblocking(new_sock);
ev.data.ptr = alloc_buff(new_sock);
int ret = epoll_ctl(epfd,EPOLL_CTL_ADD,new_sock,&ev);
if(ret < 0)
{
perror("epoll_ctl");
exit(7);
}
}
else if(sock != listen_sock && (revs[i].events & EPOLLIN))
{
//char buf[1024];
char* buf = ((epbuff_p)(revs[i].data.ptr))->buf;
ssize_t s = myread(sock,buf);
if( s > 0)
{
buf[s] = '\0';
printf("client#:%s\n",buf);  //如果读成功之后就会直接关心其写操作
ev.events=EPOLLOUT;
epoll_ctl(epfd,EPOLL_CTL_MOD,sock,&ev);
}
else if(s == 0) // 直接关闭连接
{
printf("client is close!\n");
dealloc(revs[i].data.ptr);
epoll_ctl(epfd,EPOLL_CTL_DEL,sock,NULL);
close(sock);
}
else
{
perror("read");
dealloc(revs[i].data.ptr);
int ret = epoll_ctl(epfd,EPOLL_CTL_DEL,sock,NULL);
if(ret < 0)
{
perror("epoll_ctl");
exit(8);
}
close(sock);
}
}
else if(sock != listen_sock && (revs[i].events &EPOLLOUT))
{
const char* msg = "HTTP/1.0 200 OK\r\n\r\n Hello epoll!  ";
//const char* msg = "Hello epoll!\n";
ssize_t s = write(sock,msg,strlen(msg));
if(s < 0)
{
perror("write");
exit(10);
}
dealloc(revs[i].data.ptr);
epoll_ctl(epfd,EPOLL_CTL_DEL,sock,NULL);
close(sock);
}
}
break;
}
}
}

return 0;
}


6、运行结果

服务器端:



客户端:

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  io epoll 服务器