Linux epoll模型

http://www.cnblogs.com/venow/archive/2012/11/30/2790031.html

定义:

  epoll是Linux内核为处理大批句柄而作改进的poll,是Linux下多路复用IO接口select/poll的增强版本,它能显著的减少程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率。因为它会复用文件描述符集合来传递结果而不是迫使开发者每次等待事件之前都必须重新准备要被侦听的文件描述符集合,另一个原因就是获取事件的时候,它无须遍历整个被侦听的描述符集,只要遍历那些被内核IO事件异步唤醒而加入Ready队列的描述符集合就行了。epoll除了提供select\poll那种IO事件的电平触发(Level Triggered)外,还提供了边沿触发(Edge Triggered),这就使得用户空间程序有可能缓存IO状态,减少epoll_wait/epoll_pwait的调用,提供应用程序的效率。

工作方式:

  LT(level triggered):水平触发,缺省方式,同时支持block和no-block socket,在这种做法中,内核告诉我们一个文件描述符是否被就绪了,如果就绪了,你就可以对这个就绪的fd进行IO操作。如果你不作任何操作,内核还是会继续通知你的,所以,这种模式编程出错的可能性较小。传统的select\poll都是这种模型的代表。

  ET(edge-triggered):边沿触发,高速工作方式,只支持no-block socket。在这种模式下,当描述符从未就绪变为就绪状态时,内核通过epoll告诉你。然后它会假设你知道文件描述符已经就绪,并且不会再为那个描述符发送更多的就绪通知,直到你做了某些操作导致那个文件描述符不再为就绪状态了(比如:你在发送、接受或者接受请求,或者发送接受的数据少于一定量时导致了一个EWOULDBLOCK错误)。但是请注意,如果一直不对这个fs做IO操作(从而导致它再次变成未就绪状态),内核不会发送更多的通知。

  区别:LT事件不会丢弃,而是只要读buffer里面有数据可以让用户读取,则不断的通知你。而ET则只在事件发生之时通知。

使用方式:

  1、int epoll_create(int size)

创建一个epoll句柄,参数size用来告诉内核监听的数目。

  2、int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)

epoll事件注册函数,

  参数epfd为epoll的句柄;

  参数op表示动作,用3个宏来表示:EPOLL_CTL_ADD(注册新的fd到epfd),EPOLL_CTL_MOD(修改已经注册的fd的监听事件),EPOLL_CTL_DEL(从epfd删除一个fd);

  参数fd为需要监听的标示符;

  参数event告诉内核需要监听的事件,event的结构如下:

struct epoll_event {__uint32_t events;  /* Epoll events */epoll_data_t data;  /* User data variable */
};

  其中events可以用以下几个宏的集合:

  EPOLLIN :表示对应的文件描述符可以读(包括对端SOCKET正常关闭)

  EPOLLOUT:表示对应的文件描述符可以写

  EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来)

  EPOLLERR:表示对应的文件描述符发生错误

  EPOLLHUP:表示对应的文件描述符被挂断;

  EPOLLET: 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的

  EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里

3、 int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout)

  等待事件的产生,类似于select()调用。参数events用来从内核得到事件的集合,maxevents告之内核这个events有多大,这个maxevents的值不能大于创建epoll_create()时的size,参数timeout是超时时间(毫秒,0会立即返回,-1将不确定,也有说法说是永久阻塞)。该函数返回需要处理的事件数目,如返回0表示已超时。

应用举例:

  下面,我引用google code中别人写的一个简单程序来进行说明。svn路径:http://sechat.googlecode.com/svn/trunk/

  该程序一个简单的聊天室程序,用Linux C++写的,服务器主要是用epoll模型实现,支持高并发,我测试在有10000个客户端连接服务器的时候,server处理时间不到1秒,当然客户端只是与服务器连接之后,接受服务器的欢迎消息而已,并没有做其他的通信。虽然程序比较简单,但是在我们考虑服务器高并发时也提供了一个思路。在这个程序中,我已经把所有的调试信息和一些与epoll无关的信息干掉,并添加必要的注释,应该很容易理解。

  程序共包含2个头文件和3个cpp文件。其中3个cpp文件中,每一个cpp文件都是一个应用程序,server.cpp:服务器程序,client.cpp:单个客户端程序,tester.cpp:模拟高并发,开启10000个客户端去连服务器。

  utils.h头文件,就包含一个设置socket为不阻塞函数,如下:

int setnonblocking(int sockfd)
{CHK(fcntl(sockfd, F_SETFL, fcntl(sockfd, F_GETFD, 0)|O_NONBLOCK));return 0;
}

  local.h头文件,一些常量的定义和函数的声明,如下:

复制代码
#define BUF_SIZE 1024                 //默认缓冲区
#define SERVER_PORT 44444             //监听端口
#define SERVER_HOST "192.168.34.15"   //服务器IP地址
#define EPOLL_RUN_TIMEOUT -1          //epoll的超时时间
#define EPOLL_SIZE 10000              //epoll监听的客户端的最大数目#define STR_WELCOME "Welcome to seChat! You ID is: Client #%d"
#define STR_MESSAGE "Client #%d>> %s"
#define STR_NOONE_CONNECTED "Noone connected to server except you!"
#define CMD_EXIT "EXIT"//两个有用的宏定义:检查和赋值并且检测
#define CHK(eval) if(eval < 0){perror("eval"); exit(-1);}
#define CHK2(res, eval) if((res = eval) < 0){perror("eval"); exit(-1);}//================================================================================================
//函数名:                  setnonblocking
//函数描述:                设置socket为不阻塞
//输入:                    [in] sockfd socket标示符
//输出:                    无
//返回:                    0
//================================================================================================
int setnonblocking(int sockfd);

//================================================================================================ //函数名: handle_message //函数描述: 处理每个客户端socket //输入: [in] new_fd socket标示符 //输出: 无 //返回: 返回从客户端接受的数据的长度 //================================================================================================ int handle_message(int new_fd);
复制代码

  server.cpp文件,epoll模型就在这里实现,如下:

复制代码
#include "local.h"
#include "utils.h"using namespace std;// 存放客户端socket描述符的list
list<int> clients_list;int main(int argc, char *argv[])
{int listener;   //监听socketstruct sockaddr_in addr, their_addr;  addr.sin_family = PF_INET;addr.sin_port = htons(SERVER_PORT);addr.sin_addr.s_addr = inet_addr(SERVER_HOST);socklen_t socklen;socklen = sizeof(struct sockaddr_in);static struct epoll_event ev, events[EPOLL_SIZE];ev.events = EPOLLIN | EPOLLET;     //对读感兴趣,边沿触发char message[BUF_SIZE];int epfd;  //epoll描述符clock_t tStart;  //计算程序运行时间int client, res, epoll_events_count;CHK2(listener, socket(PF_INET, SOCK_STREAM, 0));             //初始化监听socketsetnonblocking(listener);                                    //设置监听socket为不阻塞CHK(bind(listener, (struct sockaddr *)&addr, sizeof(addr))); //绑定监听socketCHK(listen(listener, 1));                                    //设置监听
CHK2(epfd,epoll_create(EPOLL_SIZE));                         //创建一个epoll描述符,并将监听socket加入epollev.data.fd = listener;CHK(epoll_ctl(epfd, EPOLL_CTL_ADD, listener, &ev));while(1){CHK2(epoll_events_count,epoll_wait(epfd, events, EPOLL_SIZE, EPOLL_RUN_TIMEOUT));tStart = clock();for(int i = 0; i < epoll_events_count ; i++){if(events[i].data.fd == listener)                    //新的连接到来,将连接添加到epoll中,并发送欢迎消息
            {CHK2(client,accept(listener, (struct sockaddr *) &their_addr, &socklen));setnonblocking(client);ev.data.fd = client;CHK(epoll_ctl(epfd, EPOLL_CTL_ADD, client, &ev));clients_list.push_back(client);                  // 添加新的客户端到list
                bzero(message, BUF_SIZE);res = sprintf(message, STR_WELCOME, client);CHK2(res, send(client, message, BUF_SIZE, 0));}else {CHK2(res,handle_message(events[i].data.fd)); //注意:这里并没有调用epoll_ctl重新设置socket的事件类型,但还是可以继续收到客户端发送过来的信息
            }}printf("Statistics: %d events handled at: %.2f second(s)\n", epoll_events_count, (double)(clock() - tStart)/CLOCKS_PER_SEC);}close(listener);close(epfd);return 0;
}int handle_message(int client)  
{char buf[BUF_SIZE], message[BUF_SIZE];bzero(buf, BUF_SIZE);bzero(message, BUF_SIZE);int len;CHK2(len,recv(client, buf, BUF_SIZE, 0));  //接受客户端信息if(len == 0)   //客户端关闭或出错,关闭socket,并从list移除socket
    {CHK(close(client));clients_list.remove(client);}else          //向客户端发送信息
    { if(clients_list.size() == 1) { CHK(send(client, STR_NOONE_CONNECTED, strlen(STR_NOONE_CONNECTED), 0));return len;}sprintf(message, STR_MESSAGE, client, buf);list<int>::iterator it;for(it = clients_list.begin(); it != clients_list.end(); it++){if(*it != client){ CHK(send(*it, message, BUF_SIZE, 0));}}}return len;
}
复制代码

  tester.cpp文件,模拟服务器的高并发,开启10000个客户端去连接服务器,如下:

复制代码
#include "local.h"
#include "utils.h"using namespace std;char message[BUF_SIZE];     //接受服务器信息
list<int> list_of_clients;  //存放所有客户端
int res;
clock_t tStart;int main(int argc, char *argv[])
{int sock; struct sockaddr_in addr;addr.sin_family = PF_INET;addr.sin_port = htons(SERVER_PORT);addr.sin_addr.s_addr = inet_addr(SERVER_HOST);tStart = clock();for(int i=0 ; i<EPOLL_SIZE; i++)  //生成EPOLL_SIZE个客户端,这里是10000个,模拟高并发
    {CHK2(sock,socket(PF_INET, SOCK_STREAM, 0));CHK(connect(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0);list_of_clients.push_back(sock);bzero(&message, BUF_SIZE);CHK2(res,recv(sock, message, BUF_SIZE, 0));printf("%s\n", message);}list<int>::iterator it;          //移除所有客户端for(it = list_of_clients.begin(); it != list_of_clients.end() ; it++)close(*it);printf("Test passed at: %.2f second(s)\n", (double)(clock() - tStart)/CLOCKS_PER_SEC); printf("Total server connections was: %d\n", EPOLL_SIZE);return 0;
}
复制代码

  我就不给出程序的执行结果的截图了,不过下面这张截图是代码作者自己测试的,可以看出,并发10000无压力呀 

  单个客户端去连接服务器,client.cpp文件,如下:

复制代码
#include "local.h"
#include "utils.h"using namespace std;char message[BUF_SIZE];/*流程:调用fork产生两个进程,两个进程通过管道进行通信子进程:等待客户输入,并将客户输入的信息通过管道写给父进程父进程:接受服务器的信息并显示,将从子进程接受到的信息发送给服务器
*/
int main(int argc, char *argv[])
{int sock, pid, pipe_fd[2], epfd;struct sockaddr_in addr;addr.sin_family = PF_INET;addr.sin_port = htons(SERVER_PORT);addr.sin_addr.s_addr = inet_addr(SERVER_HOST);static struct epoll_event ev, events[2]; ev.events = EPOLLIN | EPOLLET;//退出标志int continue_to_work = 1;CHK2(sock,socket(PF_INET, SOCK_STREAM, 0));CHK(connect(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0);CHK(pipe(pipe_fd));CHK2(epfd,epoll_create(EPOLL_SIZE));ev.data.fd = sock;CHK(epoll_ctl(epfd, EPOLL_CTL_ADD, sock, &ev));ev.data.fd = pipe_fd[0];CHK(epoll_ctl(epfd, EPOLL_CTL_ADD, pipe_fd[0], &ev));// 调用fork产生两个进程
    CHK2(pid,fork());switch(pid){case 0:                   // 子进程close(pipe_fd[0]);    // 关闭读端printf("Enter 'exit' to exit\n");while(continue_to_work){bzero(&message, BUF_SIZE);fgets(message, BUF_SIZE, stdin);// 当收到exit命令时,退出if(strncasecmp(message, CMD_EXIT, strlen(CMD_EXIT)) == 0){continue_to_work = 0;}else{            CHK(write(pipe_fd[1], message, strlen(message) - 1));}}break;default:                 // 父进程close(pipe_fd[1]);   // 关闭写端int epoll_events_count, res;while(continue_to_work) {CHK2(epoll_events_count,epoll_wait(epfd, events, 2, EPOLL_RUN_TIMEOUT));for(int i = 0; i < epoll_events_count ; i++){bzero(&message, BUF_SIZE);if(events[i].data.fd == sock)   //从服务器接受信息
                    {CHK2(res,recv(sock, message, BUF_SIZE, 0));if(res == 0)               //服务器已关闭
                        {CHK(close(sock));continue_to_work = 0;}else {printf("%s\n", message);}}else        //从子进程接受信息
                    {CHK2(res, read(events[i].data.fd, message, BUF_SIZE));if(res == 0){continue_to_work = 0; }else{CHK(send(sock, message, BUF_SIZE, 0));}}}}}if(pid){close(pipe_fd[0]);close(sock);}else{close(pipe_fd[1]);}return 0;
}
复制代码

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/383931.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Linux IO模式及 select、poll、epoll详解

https://segmentfault.com/a/1190000003063859 同步IO和异步IO&#xff0c;阻塞IO和非阻塞IO分别是什么&#xff0c;到底有什么区别&#xff1f;不同的人在不同的上下文下给出的答案是不同的。所以先限定一下本文的上下文。 本文讨论的背景是Linux环境下的network IO。一 概念…

mysql思维导图

后期会不断进行更新

C++第一节课

C数据类型 几个概念 命名空间是C标准库引入的,其中命名空间可以解决变量冲突问题,当出现局部变量和全局变量同名的时候, 局部变量优先被访问.同时命名空间的格式如同一下代码 namespace name1 { int a 0; }namespace name2 { int a 2; } 注意C中的所有组件都是在一个叫做s…

【C/C++】关键字static

http://blog.csdn.net/woxiaohahaa/article/details/51014224 参考自&#xff1a;http://www.cnblogs.com/biyeymyhjob/archive/2012/07/19/2598815.html &#xff08;华山大师兄&#xff09; 这里我们只讨论了C语言的static 首先我们回顾一下各种变量在内存中的位置&#xff1…

IO多路复用之epoll总结

http://www.cnblogs.com/Anker/p/3263780.html 1、基本知识 epoll是在2.6内核中提出的&#xff0c;是之前的select和poll的增强版本。相对于select和poll来说&#xff0c;epoll更加灵活&#xff0c;没有描述符限制。epoll使用一个文件描述符管理多个描述符&#xff0c;将用户关…

3_V1-类和对象 -- 默认成员函数

定义一个日期类 #include <iostream> #include <assert.h> using namespace std;class Date { public:void Display(); private:int _year;int _month;int _day; }; 注意: 在定义一个类的时候往往会将其成员变量定义为私有,成员函数定义为公有.这是为了达到软件…

HDU - 2973威尔逊定理

核心问题就是那个等式 我们观察到等式可以写成(n-1)!-1/n-[(n-1)!/n]的形式&#xff0c;这样就应该联想到威尔逊定理了。 回顾一下威尔逊定理的内容&#xff1a;当且仅当n为素数的时候n|(n-1)!-1&#xff0c;n为合数且大于4的时候n|(n-1)!【参见威尔逊定理的证明】 对于这个…

linux网络编程之posix 线程(四):posix 条件变量与互斥锁 示例生产者--消费者问题

http://blog.csdn.net/jnu_simba/article/details/9129939 一、posix 条件变量 一种线程间同步的情形&#xff1a;线程A需要等某个条件成立才能继续往下执行&#xff0c;现在这个条件不成立&#xff0c;线程A就阻塞等待&#xff0c;而线程B在执行过程中使这个条件成立了&#x…

3-V2-类和对象 -- const内联 静态成员 友元

const修饰成员函数 在成员函数后面加一个const, const修饰this指针指向的对象, 保证调用这个const成员函数的对象在函数内不会被改变 注意:成员函数如果不修改成员变量,则加上const,成员函数如果要修改成员变量,此时就不能给其加上const修饰了 1.const对象不能调用非const…

C语言 二级指针内存模型混合实战

http://www.cnblogs.com/zhanggaofeng/p/5485833.html //二级指针内存模型混合实战 #define _CRT_SECURE_NO_WARNINGS #include <stdio.h> #include <stdlib.h> #include <string.h>//将内存模型①和内存模型②的数据拷贝到内存模型③ char ** threemodel(ch…

Linux 网络编程八(epoll应用--大并发处理)

http://www.cnblogs.com/zhanggaofeng/p/5901316.html //头文件 pub.h #ifndef _vsucess#define _vsucess#ifdef __cplusplus extern "C" {#endif //服务器创建socket int server_socket(int port);//设置非阻塞 int setnonblock(int st);//接收客户端socket int ser…

【数据结构与算法】内部排序之三:堆排序(含完整源码)

转载请注明出处&#xff1a;http://blog.csdn.net/ns_code/article/details/20227303 前言 堆排序、快速排序、归并排序&#xff08;下篇会写这两种排序算法&#xff09;的平均时间复杂度都为O&#xff08;n*logn&#xff09;。要弄清楚堆排序&#xff0c;就要先了解下二叉堆这…

模线性方程(中国剩余定理+扩展中国剩余定理)

已知一系列除数和模数,求最小的满足条件的数 我们先考虑一般的情况&#xff0c;即模数不互质。&#xff08;扩展中国剩余定理&#xff09; 我们考虑两个方程的情况 x%MR xk1∗MRxk1 * MRxk1∗MR x%mr xk2∗mrxk2 * mrxk2∗mr 所以k1∗MRk2∗mrk1 * MRk2 * mrk1∗MRk2∗mr 即…

【数据结构】(面试题)使用两个栈实现一个队列(详细介绍)

http://blog.csdn.net/hanjing_1995/article/details/51539578 使用两个栈实现一个队列 思路一&#xff1a; 我们设定s1是入栈的&#xff0c;s2是出栈的。 入队列&#xff0c;直接压到s1即可 出队列&#xff0c;先把s1中的元素倒入到s2中&#xff0c;弹出s2中的栈顶元素&#x…

C++::探索对象模型

前面我们已经知道, 在没有虚函数的时候, 对象的大小就是对应的成员变量的大小, 而成员函数不会占用对象的空间, 今天我们来讨论一下, 当类中定义了虚函数的时候, 此时对象的大小以及对象模型 非继承下的对象模型 class Base { public:virtual void func1(){cout << &qu…

软件测试相关概念

什么叫软件测试 软件测试就是测试产品没有错误,同时又证明软件是可以正确运行的 测试和调试的区别 调试一般都在开发期间 ,测试是伴随着整个软件的生命周期, 调试是发现程序中问题并且解决问题, 测试是发现程序中的缺陷 软件测试的目的和原则 目的:验证软件有没有问题 原…

C++静态成员函数访问非静态成员的几种方法

https://www.cnblogs.com/rickyk/p/4238380.html 大家都知道C中类的成员函数默认都提供了this指针&#xff0c;在非静态成员函数中当你调用函数的时候&#xff0c;编译器都会“自动”帮你把这个this指针加到函数形参里去。当然在C灵活性下面&#xff0c;类还具备了静态成员和静…

HDU2683——欧拉完全数

题目要求符合等式的数&#xff0c;我们首先要做的就是分析这个数&#xff1a; 对于这个等式&#xff0c;我们可能什么都看不出来&#xff0c;左边很难化简的样子&#xff0c;所以我们就要想到通过变化怎么样把右边化成和左边形式差不多的样子。结合组合数我们想到二项式定理&am…

获取网络接口信息——ioctl()函数与结构体struct ifreq、 struct ifconf

http://blog.csdn.net/windeal3203/article/details/39320605 Linux 下 可以使用ioctl()函数 以及 结构体 struct ifreq 结构体struct ifconf来获取网络接口的各种信息。 ioctl 首先看ioctl()用法ioctl()原型如下&#xff1a;#include <sys/ioctl.h>int ioctl(int fd, i…

java中引用传递

基本概念 栈内存 所谓的栈内存就是存储进程在运行过程中变量的内存空间 堆内存 所谓的堆内存就是存储系统中数据的内存空间 数组相关的引用传递 先来看一段代码 public class ArrayDemo {public static void main(String[] args) {int[] x null;x new int[3];System.o…