一.epoll概述linux
epoll是linux下的一個系統調用,用來監聽大量文件描述符並對其上的觸發事件進行處理。它是select/poll的加強版本,也是linux下多路複用io最經常使用的接口。要理解epoll是什麼,首先得清楚什麼是多路複用io。用戶進行io操做須要通過內核,而若是所請求的io目前不知足條件(如須要從標準輸入讀取數據,而用戶還未輸入),這個時候內核就會讓應用程序陷入等待,即阻塞狀態。我的理解,io複用技術就是經過特定接口,將這種阻塞進行了轉移,轉嫁到了如select/poll/epoll之類多系統調用上,而且支持多個文件描述符多監測,即多路複用。這樣epoll即可以替應用程序同時監聽多個文件描述符,一旦有觸發發生,內核就會通知應用程序進行處理。ios
二.epoll接口服務器
epoll的函數接口既能夠經過查看epoll.h文件來查看,也能夠用man命令查看。這裏引用epoll.h中的描述.(epoll.h在/usr/include/x86_64-linux-gnu/sys目錄下可找到)。網絡
epoll接口主要包括一個結構三個函數socket
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<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 1024 using 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) { addr_len = sizeof(sockaddr); 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; }
四.分析ui
優勢spa
1.可支持打開大量描述符server
在網絡應用程序中每每須要對大量文件描述符進行操做,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> #include<arpa/inet.h> #define SERV_PORT 5660 #define BUF_SIZE 1024 int 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); }