首页 > 代码库 > epoll模型实例

epoll模型实例

一.epoll概述

epoll是linux下的一个系统调用,用来监听大量文件描述符并对其上的触发事件进行处理。它是select/poll的增强版本,也是linux下多路复用io最常用的接口。要理解epoll是什么,首先得清楚什么是多路复用io。用户进行io操作需要经过内核,而如果所请求的io目前不满足条件(如需要从标准输入读取数据,而用户还未输入),这个时候内核就会让应用程序陷入等待,即阻塞状态。个人理解,io复用技术就是通过特定接口,将这种阻塞进行了转移,转嫁到了如select/poll/epoll之类多系统调用上,并且支持多个文件描述符多监测,即多路复用。这样epoll便可以替应用程序同时监听多个文件描述符,一旦有触发发生,内核就会通知应用程序进行处理。

二.epoll接口

epoll的函数接口既可以通过查看epoll.h文件来查看,也可以用man命令查看。这里引用epoll.h中的描述.(epoll.h在/usr/include/x86_64-linux-gnu/sys目录下可找到)。

epoll接口主要包括一个结构三个函数

struct epoll_event { uint32_t events;   /* Epoll事件 */ epoll_data_t data; /* 用户变量 */ } __EPOLL_PACKED; /* 创建一个epoll对象,返回其描述符.    "size" 参数用来指定和对象相关的描述符个数.    返回的文件描述符应该通过close()关闭   */  int epoll_create (int __size) __THROW; /* 操作epoll对象"epfd". 成功返回0,失败返回-1. "op" 参数为声明多宏    "fd"参数即操作多目标.    "event"参数为调用者感兴趣的描述符(通过用户变量data关联)  */  int epoll_ctl (int __epfd, int __op, int __fd,                       struct epoll_event *__event) __THROW; /* 监听epoll对象"epfd". 返回触发的文件描述符到"events". 或者出错返回-1.    "events"是用来存触发描述符的缓冲."maxevents"是返回的对大数."timeout"  表示等待多毫秒数(-1 == 无限).  */  int epoll_wait (int __epfd, struct epoll_event *__events,                        int __maxevents, int __timeout);

  

三.epoll模型实例

以下代码用于实现一个简单的服务器程序,接受客户端数据,并在服务端标准输出打印。

#include<unistd.h>#include<fcntl.h>#include<iostream>#include<sys/epoll.h>#include<netinet/in.h>#include<strings.h>#include<set>#include<stdlib.h>#define LISTENQ 1024#define MAX_EVENT 20#define BUF_SIZE 1024using namespace std;class Server{        public:                Server(unsigned int port = 5660);                ~Server();                void set_non_blocking(int);                void run();        private:                unsigned int server_port;                struct epoll_event ev, events[MAX_EVENT];                int listen_fd;                int epoll_fd;                std::set<int> connections;};Server::Server(unsigned int port):server_port(port){        listen_fd = socket(AF_INET, SOCK_STREAM, 0);        //initiate the socket address structure        struct sockaddr_in server_addr;        bzero(&server_addr, sizeof(server_addr));        server_addr.sin_family         = AF_INET;        server_addr.sin_addr.s_addr    = htonl(INADDR_ANY);        server_addr.sin_port           = htons(server_port);        //bind the local protocol type to a socket address        bind(listen_fd, (struct sockaddr *)&server_addr, sizeof(server_addr));        //initiate the epoll        epoll_fd = epoll_create(MAX_EVENT);        set_non_blocking(listen_fd);        ev.data.fd = listen_fd;        ev.events = EPOLLIN | EPOLLET;        epoll_ctl(epoll_fd, EPOLL_CTL_ADD, listen_fd, &ev);        //start listening        listen(listen_fd, LISTENQ);}Server::~Server(){}void Server::set_non_blocking(int fd){        int flag;        flag = fcntl(fd, F_GETFL);        flag  |= O_NONBLOCK;        fcntl(fd, F_SETFL, flag);}void Server::run(){        char buffer[BUF_SIZE + 1];        int trigger_num;        socklen_t addr_len;        struct sockaddr_in client_addr;        while(1)        {                trigger_num = epoll_wait(epoll_fd, events, MAX_EVENT, 500);                for(int i = 0; i < trigger_num; i++)                {                        //accept new connection                        if(events[i].data.fd  == listen_fd)                        {                                int connect_fd = accept(listen_fd,(sockaddr *)&client_addr, &addr_len);                                set_non_blocking(connect_fd);                                connections.insert(connect_fd);                                ev.data.fd = connect_fd;                                ev.events = EPOLLIN | EPOLLET;                                epoll_ctl(epoll_fd, EPOLL_CTL_ADD, connect_fd, &ev);                                cout<<"a new connection!"<<endl;                        }                        //read from a existed connection                        else                        {                                size_t read_num = recv(events[i].data.fd, buffer, BUF_SIZE, 0);                                if(read_num < 0)                                {                                        perror("read");                                        return;                                }                                                                if(0 == read_num)                                {                                        auto iterator = connections.find(events[i].data.fd);                                        if(iterator != connections.end())                                                connections.erase(iterator);                                        cout<<"a connections lost!"<<endl;                                }                                else                                        cout<< buffer;                        }                }        }}int main(){        Server my_server;        my_server.run();        return 0;}

四.分析

优点

1.可支持打开大量描述符

在网络应用程序中往往需要对大量文件描述符进行操作,epoll根据机器内存,支持数万到数十万多文件描述符

2.io效率不随描述符数量增加而线性下降

因为epoll只关心“活跃”的描述符,不需要对所有描述符进行线性扫描

缺点

如实例所示,epoll将对描述符的检测和处理放在同一个循环之中,当处理过程复杂之后,就会使得整个循环变得臃肿,效率会有所下降

 

 

附client.cpp用于测试连接

#include<stdio.h>#include<string.h>#include<stdlib.h>#include<unistd.h>#include<sys/socket.h>#include<netinet/in.h>#include<string.h>#define SERV_PORT 5660#define BUF_SIZE 1024int main(int argc, char ** argv){        char                buffer[BUF_SIZE + 1];        int                 sockfd;        struct sockaddr_in  servaddr;        int                 res;        if(argc != 2)        {                perror("IP address");                exit(EXIT_FAILURE);        }        sockfd = socket(AF_INET, SOCK_STREAM, 0);        bzero(&servaddr, sizeof(servaddr));        servaddr.sin_family = AF_INET;        servaddr.sin_port = htons(SERV_PORT);        inet_pton(AF_INET, argv[1], &servaddr.sin_addr);        connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));        while(1)        {                memset(buffer, ‘\0‘, BUF_SIZE);                fgets(buffer, BUF_SIZE, stdin);                if(strcmp(buffer, "end\n") == 0)                        break;                if(strlen(buffer) != 0)                {                        res = write(sockfd, buffer, BUF_SIZE);                        if(res == -1)                        {                                perror("Write");                                exit(EXIT_FAILURE);                        }                }        }        exit(EXIT_SUCCESS);}