1. 程式人生 > >I/O多路複用詳解(三)

I/O多路複用詳解(三)

    在linux的網路程式設計中,很長的一段時間都在使用select來做事件觸發。然而select逐漸暴露出了一些缺陷,使得linux不得不在新的核心中尋找出替代方案,那就是epoll。其實,epoll與select原理類似,只不過,epoll作出了一些重大改進,即:
    a、當它們所監聽的集合中有狀態發生改變時,select需要迴圈檢查整個集合,才能確定那個檔案描述符狀態發生改變,進而進行操作;而epoll在新增檔案描述符到集合時,已經綁定了該檔案描述符的對應函式,因此,當該檔案描述符狀態改變時,不需要迴圈查詢整個集合,因而將複雜度由0(n)將為o(1),效能得到幾何量級的提高,尤其是在大量連線的情況下。
    b、再有,select所監聽的描述字最大數目是有一定限制的,由FD_SETSIZE設定,通常是1024。對於那些需要支援的上萬連線數目的web伺服器來說顯然是太少了,儘管可以通過修改標頭檔案再重編譯核心來擴大這個數目,不過資料也同時指出這樣會帶來網路效率的下降。(詳細請參考:epoll 相對於poll的優點)
epoll的介面非常簡單,一共就三個函式:
(1)、int epoll_create(int size);
     建立一個epoll的控制代碼,size用來告訴核心這個監聽的數目一共有多大。需要注意的是,當建立好epoll控制代碼後,epoll本身就佔用一個fd值,所以用完後必須呼叫close()關閉,以防止fd被耗盡。
(2)、int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
     epoll的事件註冊函式,第一個引數是epoll_create()的返回值,第二個引數表示動作,用三個巨集來表示:
            EPOLL_CTL_ADD:註冊新的fd到epfd中;
            EPOLL_CTL_MOD:修改已經註冊的fd的監聽事件;
            EPOLL_CTL_DEL:從epfd中刪除一個fd;
第三個引數是需要監聽的fd,第四個引數是告訴核心需要監聽什麼事件,struct epoll_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表示已超時。
     令人高興的是,2.6核心的epoll比其2.5開發版本的/dev/epoll簡潔了許多,所以,大部分情況下,強大的東西往往是簡單的。唯一有點麻煩是epoll有2種工作方式:LT和ET。
     LT(level triggered)是預設的工作方式,並且同時支援block和no-block socket.在這種做法中,核心告訴你一個檔案描述符是否就緒了,然後你可以對這個就緒的fd進行IO操作。如果你不作任何操作,核心還是會繼續通知你的,所以,這種模式程式設計出錯誤可能性要小一點。傳統的select/poll都是這種模型的代表.
     ET (edge-triggered)是高速工作方式,只支援no-block socket。在這種模式下,當描述符從未就緒變為就緒時,核心通過epoll告訴你。然後它會假設你知道檔案描述符已經就緒,並且不會再為那個檔案描述符傳送更多的就緒通知,直到你做了某些操作導致那個檔案描述符不再為就緒狀態了(比如,你在傳送,接收或者接收請求,或者傳送接收的資料少於一定量時導致了一個EWOULDBLOCK 錯誤)。但是請注意,如果一直不對這個fd作IO操作(從而導致它再次變成未就緒),核心不會發送更多的通知(only once),不過在TCP協議中,ET模式的加速效用仍需要更多的benchmark確認。
注:以上參考epoll精髓一文。
一個epoll的例子:
 
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <fcntl.h>
#include <sys/epoll.h>
#include <sys/time.h>
#include <sys/resource.h>
#define MAXBUF 1024
#define MAXEPOLLSIZE 10000
/*
setnonblocking - 設定控制代碼為非阻塞方式
*/
int setnonblocking(int sockfd)
{
    if (fcntl(sockfd, F_SETFL, fcntl(sockfd, F_GETFD, 0)|O_NONBLOCK) == -1) {
        return -1;
    }
    return 0;
}


/*
handle_message - 處理每個 socket 上的訊息收發
*/
int handle_message(int new_fd)
{
    char buf[MAXBUF + 1];
    int len;
    /* 開始處理每個新連線上的資料收發 */
    bzero(buf, MAXBUF + 1);
    /* 接收客戶端的訊息 */
    len = recv(new_fd, buf, MAXBUF, 0);
    if (len > 0)
        printf
            ("%d接收訊息成功:'%s',共%d個位元組的資料\n",
             new_fd, buf, len);
    else {
        if (len < 0)
            printf
                ("訊息接收失敗!錯誤程式碼是%d,錯誤資訊是'%s'\n",
                 errno, strerror(errno));
        close(new_fd);
        return -1;
    }
    /* 處理每個新連線上的資料收發結束 */
    return len;
}
/************關於本文件********************************************
*filename: epoll-server.c
*purpose: 演示epoll處理海量socket連線的方法
*wrote by: zhoulifa(
[email protected]
) 周立發(http://zhoulifa.bokee.com)
Linux愛好者 Linux知識傳播者 SOHO族 開發者 最擅長C語言
*date time:2007-01-31 21:00
*Note: 任何人可以任意複製程式碼並運用這些文件,當然包括你的商業用途
* 但請遵循GPL
*Thanks to:Google
*Hope:希望越來越多的人貢獻自己的力量,為科學技術發展出力
* 科技站在巨人的肩膀上進步更快!感謝有開源前輩的貢獻!
*********************************************************************/
int main(int argc, char **argv)
{
    int listener, new_fd, kdpfd, nfds, n, ret, curfds;
    socklen_t len;
    struct sockaddr_in my_addr, their_addr;
    unsigned int myport, lisnum;
    struct epoll_event ev;
    struct epoll_event events[MAXEPOLLSIZE];
    struct rlimit rt;


    if (argv[1])
        myport = atoi(argv[1]);
    else
        myport = 7838;


    if (argv[2])
        lisnum = atoi(argv[2]);
    else
        lisnum = 2;


    /* 設定每個程序允許開啟的最大檔案數 */
    rt.rlim_max = rt.rlim_cur = MAXEPOLLSIZE;
    if (setrlimit(RLIMIT_NOFILE, &rt) == -1) {
        perror("setrlimit");
        exit(1);
    }
    else printf("設定系統資源引數成功!\n");


    /* 開啟 socket 監聽 */
    if ((listener = socket(PF_INET, SOCK_STREAM, 0)) == -1) {
        perror("socket");
        exit(1);
    } else
        printf("socket 建立成功!\n");


    setnonblocking(listener);


    bzero(&my_addr, sizeof(my_addr));
    my_addr.sin_family = PF_INET;
    my_addr.sin_port = htons(myport);
    if (argv[3])
        my_addr.sin_addr.s_addr = inet_addr(argv[3]);
    else
        my_addr.sin_addr.s_addr = INADDR_ANY;


    if (bind
        (listener, (struct sockaddr *) &my_addr, sizeof(struct sockaddr))
        == -1) {
        perror("bind");
        exit(1);
    } else
        printf("IP 地址和埠繫結成功\n");


    if (listen(listener, lisnum) == -1) {
        perror("listen");
        exit(1);
    } else
        printf("開啟服務成功!\n");


    /* 建立 epoll 控制代碼,把監聽 socket 加入到 epoll 集合裡 */
    kdpfd = epoll_create(MAXEPOLLSIZE);
    len = sizeof(struct sockaddr_in);
    ev.events = EPOLLIN | EPOLLET;
    ev.data.fd = listener;
    if (epoll_ctl(kdpfd, EPOLL_CTL_ADD, listener, &ev) < 0) {
        fprintf(stderr, "epoll set insertion error: fd=%d\n", listener);
        return -1;
    } else
        printf("監聽 socket 加入 epoll 成功!\n");
    curfds = 1;
    while (1) {
        /* 等待有事件發生 */
        nfds = epoll_wait(kdpfd, events, curfds, -1);
        if (nfds == -1) {
            perror("epoll_wait");
            break;
        }
        /* 處理所有事件 */
        for (n = 0; n < nfds; ++n) {
            if (events[n].data.fd == listener) {
                new_fd = accept(listener, (struct sockaddr *) &their_addr,
                                &len);
                if (new_fd < 0) {
                    perror("accept");
                    continue;
                } else
                    printf("有連線來自於: %s:%d, 分配的 socket 為:%d\n",inet_ntoa(their_addr.sin_addr), ntohs(their_addr.sin_port), new_fd);


                setnonblocking(new_fd);
                ev.events = EPOLLIN | EPOLLET;
                ev.data.fd = new_fd;
                if (epoll_ctl(kdpfd, EPOLL_CTL_ADD, new_fd, &ev) < 0) {
                    fprintf(stderr, "把 socket '%d' 加入 epoll 失敗!%s\n",
                            new_fd, strerror(errno));
                    return -1;
                }
                curfds++;
            } else {
                ret = handle_message(events[n].data.fd);
                if (ret < 1 && errno != 11) {
                    epoll_ctl(kdpfd, EPOLL_CTL_DEL, events[n].data.fd,
                              &ev);
                    curfds--;
                }
            }
        }
    }
    close(listener);
    return 0;
}


編譯此程式用命令:
gcc -Wall epoll-server.c -o server
執行此程式需要具有管理員許可權!
sudo ./server 7838 1
通過測試這一個伺服器可能同時處理10000 -3 = 9997 個連線!
如果這是一個線上服務系統,那麼它可以支援9997人同時線上,比如遊戲、聊天等。

相關推薦

I/O

    在linux的網路程式設計中,很長的一段時間都在使用select來做事件觸發。然而select逐漸暴露出了一些缺陷,使得linux不得不在新的核心中尋找出替代方案,那就是epoll。其實,epoll與select原理類似,只不過,epoll作出了一些重大改進,即:     a、當它們所監聽的集合中有狀

I/O

轉載地址: http://blog.csdn.net/segments/article/details/6894189 要想完全理解I/O多路複用,需先要了解I/O模型: 一、五種I/O模型 1、阻塞I/O模型      最流行的I/O模型是阻塞I/O模型,預設情

Java網路程式設計與NIO2:JAVA NIO 一步步構建I/O的請求模型

微信公眾號【黃小斜】作者是螞蟻金服 JAVA 工程師,專注於 JAVA 後端技術棧:SpringBoot、SSM全家桶、MySQL、分散式、中介軟體、微服務,同時也懂點投資理財,堅持學習和寫作,相信終身學習的力量!關注公眾號後回覆”架構師“即可領取 Java基礎、進階、專案和架構師等免費學習資料,更有資料

Go語言I/Onetpoller模型

> 轉載請宣告出處哦~,本篇文章釋出於luozhiyun的部落格:https://www.luozhiyun.com > > 本文使用的go的原始碼15.7 可以從 Go 原始碼目錄結構和對應程式碼檔案瞭解 Go 在不同平臺下的網路 I/O 模式的實現。比如,在 Linux 系統下基於 epoll,free

Linux網路程式設計---I/O之select

1.I/O多路複用(IO multiplexing) 我們之前講了I/O多路複用和其他I/O的區別,在這裡,我們再具體討論下I/O多路複用是怎麼工作? I/O 多路複用技術就是為了解決程序或執行緒阻塞到某個 I/O 系統呼叫而出現的技術,使程序不阻塞於某個特定的 I/O 系統呼叫。

I/O

I/O型別:     接下來我們將介紹幾種常見的I/O模型及其區別         阻塞I/O:blocking I/O(如果沒有資訊,則阻塞)       

【Linux】I/O

五種IO模型     阻塞IO(等待魚上鉤)         在核心將資料準備好之前,系統呼叫會一直等待,所有的套接字,預設是阻塞模式。         等待,拷貝資料到buf中,(等待的時間長)     非阻塞IO(定期檢視是否有魚上鉤)         如果核心還未將資料

I/O技術multiplexing

作者:知乎使用者 連結:https://www.zhihu.com/question/28594409/answer/52835876 來源:知乎 著作權歸作者所有。商業轉載請聯絡作者獲得授權,非商業轉載請註明出處。 下面舉一個例子,模擬一個tcp伺服器處理30個客戶soc

嵌入式Linux網路程式設計,I/O,epoll()示例,epoll()客戶端,epoll()伺服器,單鏈表

文章目錄 1,I/O多路複用 epoll()示例 1.1,epoll()---net.h 1.2,epoll()---client.c 1.3,epoll()---sever.c 1.4,epoll()---linklist.h

嵌入式Linux網路程式設計,I/O,poll()示例,poll()客戶端,poll()伺服器,單鏈表

文章目錄 1,IO複用poll()示例 1.1,poll()---net.h 1.2,poll()---client.c 1.3,poll()---sever.c 1.4,poll()---linklist.h 1.5,p

嵌入式Linux網路程式設計,I/O,select()示例,select()客戶端,select()伺服器,單鏈表

文章目錄 1,IO複用select()示例 1.1 select()---net.h 1.2 select()---client.c 1.3 select()---sever.c 1.4 select()---linklist.h

嵌入式Linux網路程式設計,I/O,阻塞I/O模式,非阻塞I/O模式fcntl()/ioctl(),I/O select()/pselect()/poll(),訊號驅動I/O

文章目錄 1,I/O模型 2,阻塞I/O 模式 2.1,讀阻塞(以read函式為例) 2.2,寫阻塞 3,非阻塞模式I/O 3.1,非阻塞模式的實現(fcntl()函式、ioctl() 函式)

I/O之select、poll、epoll

很早之前有寫過篇IO多路複用的文章:https://www.cnblogs.com/klcf0220/archive/2013/05/14/3077003.html 參考連結:https://segmentfault.com/a/1190000003063859 select,poll,epoll都是IO多路

UNIX網路程式設計-I/O

目錄 Unix下可用的5種I/O模型 阻塞式I/O模型 非阻塞式I/O模型 I/O複用模型 訊號驅動式I/O模型 非同步I/O模型 各種I/O模型的比較 參考   Unix下可用的5種I/O模型 阻塞式I/O 非阻塞式I/O

IO基礎入門之I/O技術

在I/O程式設計過程中,當需要同時處理多個客戶端接入請求時,可以利用多執行緒或者I/O多路複用技術進行處理。I/O多路複用技術通過把多個I/O的阻塞複用到同一個select的阻塞上,從而使得系統在單執行緒的情況下可以同時處理多個客戶端請求。與傳統的多執行緒/多程序模型比,I

I/O伺服器程式設計

一、實驗目的 理解I/O多路複用技術的原理。 學會編寫基本的單執行緒併發伺服器程式和客戶程式。 二、實驗平臺 ubuntu-8.04作業系統 三、實驗內容 採用I/O多路複用技術實現單執行緒併發伺服器,完成使用一個執行緒處理併發客戶請求的功能。 四、實驗原理 除了可以採用多

淺談網路I/O模型 select & poll & epoll

我們首先需要知道select,poll,epoll都是IO多路複用的機制。I/O多路複用就通過一種機制,可以監視多個描述符,一旦某個描述符就緒(一般是讀就緒或者寫就緒),能夠通知程式進行相應的讀寫操作。但select,poll,epoll本質上都是同步I/O,因為他們都需要在讀寫事件就緒後自己負責進行讀寫,

I/O之 epoll 系統呼叫

I/O多路複用除了之前我們提到的select和poll外,epoll 也可以檢查多個檔案描述符的就緒狀態,以達到I/O多路複用的目的。 epoll 系統呼叫是 Linux 系統專有的,在 Linux 核心 2.6 版本新增,epoll 的主要優點有: 當檢

Socket網路程式設計_之I/O

1. IO多路複用: 每一次網路通訊都是一個Socket的I/O流,對於伺服器而言,有兩種方法 1.傳統的多程序併發模型(每進來一個新的I/O流會分配一個新的程序管理。) 2.方法二就是I/O的多路複用

Linux 下I/O總結

 select,poll,epoll都是IO多路複用的機制。I/O多路複用就通過一種機制,可以監視多個描述符,一旦某個描述符就緒(一般是讀就緒或者寫就緒),能夠通知程式進行相應的讀寫操作。但select,poll,epoll本質上都是同步I/O,因為他們都需要在讀寫事件