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.pswp.cn/news/383931.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/383931.shtml
英文地址,請注明出處:http://en.pswp.cn/news/383931.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

UVa11572

書上把這種問題叫做滑動窗口問題. 我的想法是先進行離散化,然后用一個數組記錄元素出現的位置,如果判斷某個元素已經出現,就將左端點移到上次出現的位置的后面.每次出現重復元素的時候判斷一下答案.我覺得這樣的復雜度是最低的. #include<cstdio> #include<cstring&…

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

https://segmentfault.com/a/1190000003063859 同步IO和異步IO&#xff0c;阻塞IO和非阻塞IO分別是什么&#xff0c;到底有什么區別&#xff1f;不同的人在不同的上下文下給出的答案是不同的。所以先限定一下本文的上下文。 本文討論的背景是Linux環境下的network IO。一 概念…

mysql思維導圖

后期會不斷進行更新

CF Gym 101630 B Box

題目的意思大概就是給一個長方體的長寬高,問他能不能用一個w*h的紙剪出來,就是說展開圖的長寬能不能比給定的小. 題目給了11中展開圖的拓撲結構,我覺得這個很關鍵,要是題目沒有給這個我可能想不到那么全面,不過題目已經給了我就分析那11個圖形,發現展開圖的長寬大概分為三類 …

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…

HDU5391威爾遜定理

威爾遜定理 當且僅當p為素數,p | (p-1)!1 若p為合數,則pa*b;如果a!b,那么p|(p-1)!, 如果ab,如果p為4,那么p|(p-1)!2,如果p大于4,那么sqrt和sqrt(2q)肯定屬于(p-1)!中,可以整除 #include<cstdio> #include<cstring> #include<algorithm> #include<climit…

C++的基本認識

簡單介紹C 語言特點 支持數據封裝和數據隱藏 在C中&#xff0c;類是支持數據封裝的工具&#xff0c;對象則是數據封裝的實現。C通過建立用戶定義類支持數據封裝和數據隱藏。 在面向對象的程序設計中&#xff0c;將數據和對該數據進行合法操作的函數封裝在一起作為一個類的定…

OD 投籃大賽

/*** 題目描述* 你現在是一場采用特殊賽制投籃大賽的記錄員。這場比賽由若干回合組成&#xff0c;過去幾回合的得分可能會影響以后幾回合的得分。* 比賽開始時&#xff0c;記錄時空白的。你會得到一個記錄操作的字符串列表aops&#xff0c;其中ops[i]是你需要記錄的第i項操作&a…

IO多路復用之epoll總結

http://www.cnblogs.com/Anker/p/3263780.html 1、基本知識 epoll是在2.6內核中提出的&#xff0c;是之前的select和poll的增強版本。相對于select和poll來說&#xff0c;epoll更加靈活&#xff0c;沒有描述符限制。epoll使用一個文件描述符管理多個描述符&#xff0c;將用戶關…

2018南京區域賽 J-Prime Game

完全沒有頭緒 聽完隊友講的我還是楞了好半天菜慢慢理解.我好菜啊 首先要弄懂題目的意思,轉換一下題意就是求每個素因子出現區間的次數.區間長度最短為1.我們分析,第一個數的因子會影響1* n個區間(暫時不考慮重復),第二個數的因子會影響2 * (n-1)個區間,以此類推.因此我們只需要…

3_V1-類和對象 -- 默認成員函數

定義一個日期類 #include <iostream> #include <assert.h> using namespace std;class Date { public:void Display(); private:int _year;int _month;int _day; }; 注意: 在定義一個類的時候往往會將其成員變量定義為私有,成員函數定義為公有.這是為了達到軟件…

C++ 類模板二(類模版與友元函數)

http://www.cnblogs.com/zhanggaofeng/p/5661829.html //類模版與友元函數 #include<iostream> using namespace std;template<typename T> class Complex{ public:Complex(T a,T b);void Print() const//const修飾的是this指針{cout << this->Real <&…

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…

擴展歐幾里得算法

對于a?xb?yca*xb*yca?xb?yc,這樣一個二元一次方程組&#xff0c;我們想要得到他的一組解可以用擴展歐幾里得算法&#xff0c;參數列表的a,b,x,y就是方程中的a,b,x,y&#xff0c;d計算出來是gcd(a,b)。 算法求出來的是a?xb?ygcd(a,ba*xb*ygcd(a,ba?xb?ygcd(a,b的一組解…

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…

約瑟夫問題

n個人編號為0…n-1圍成一個圈,從0開始報數,每經過k個人那個人就退出這個圈不再報數,問最后留下來的人的編號. 樸素的做法當然是模擬,但是n,k的值一旦變得比較大的時候就難以解決問題. 我們考慮歸納的解決問題 當只有一個人的時候答案顯然為0, 假設我們已知n-1個人的時候答案為…