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:
6、运行结果
服务器端:
客户端:
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、运行结果
服务器端:
客户端:
相关文章推荐
- C/S通信---服务器IO多路复用模型之epoll的使用
- Linux下套接字详解(十)---epoll模式下的IO多路复用服务器
- 服务器基础:IO多路复用之select、poll、epoll详解
- 用C写一个web服务器(二) I/O多路复用之epoll
- IO多路复用之select、poll、epoll详解
- 搭建高并发服务器--高效I/O复用(epoll)
- Linux网络通信编程(套接字模型TCP\UDP与IO多路复用模型select\poll\epoll)
- IO多路复用之epoll(二)
- python网络编程--IO多路复用之epoll
- epoll服务器---I/O多路转接之epoll
- 聊聊IO多路复用之select、poll、epoll详解
- 多路IO复用模型 select epoll 等
- 并发服务器:多路复用I/O
- 简单的,tcp多路复用,服务器的编写
- 高性能网络服务器--I/O复用 select poll epoll_wait之间的区别
- IO多路复用总结:select pool epoll
- C/S通信---服务器IO多路复用模型之select的使用
- I/O多路复用之select,poll,epoll简介
- 服务器IO多路复用模型
- I/O多路复用之select、epoll的实现和区别 ,ET与LT模式