欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

如何用C寫一個web服務器之I/O多路復用

 更新時間:2021年05月27日 09:36:18   作者:枕邊書  
本文主要介紹了如何用C寫一個web服務器之I/O多路復用,本次選擇了 I/O 模型的優(yōu)化,因為它是服務器的基礎,這個先完成的話,后面的優(yōu)化就可以選擇各個模塊來進行,不必進行全局化的改動了。

前言

I/O模型

接觸過 socket 編程的同學應該都知道一些 I/O 模型的概念,linux 中有阻塞 I/O、非阻塞 I/O、I/O 多路復用、信號驅(qū)動 I/O 和 異步 I/O 五種模型。

其他模型的具體概念這里不多介紹,只簡單地提一下自己理解的 I/O 多路復用:簡單的說就是由一個進程來管理多個 socket,即將多個 socket 放入一個表中,在其中有 socket 可操作時,通知進程來處理, I/O 多路復用的實現(xiàn)方式有 select、poll 和 epoll。

select/poll/epoll

在 linux下,通過文件描述符(file descriptor, 下 fd)來進行 socket 的操作,所以下文均是對 fd 操作。

首先說最開始實現(xiàn)的 select 的問題:

  • select 打開的 fd 最大數(shù)目有限制,一般為1024,在當前計算系統(tǒng)的并發(fā)量前顯然有點不適用了。
  • select 在收到有 fd 可操作的通知時,是無法得知具體是哪個 fd 的,需要線性掃描 fd 表,效率較低。
  • 當有 fd 可操作時,fd 會將 fd 表復制到內(nèi)核來遍歷,消耗也較大。

隨著網(wǎng)絡技術的發(fā)展,出現(xiàn)了 poll:poll 相對于 select,使用 pollfd 表(鏈表實現(xiàn)) 來代替 fd,它沒有上限,但受系統(tǒng)內(nèi)存的限制,它同樣使用 fd 遍歷的方式,在并發(fā)高時效率仍然是一個問題。

最終,epoll 在 Linux 2.6 的內(nèi)核面世,它使用事件機制,在每一個 fd 上添加事件,當fd 的事件被觸發(fā)時,會調(diào)用回調(diào)函數(shù)來處理對應的事件,epoll 的優(yōu)勢總之如下:

  • 只關心活躍的 fd,精確定位,改變了poll的時間效率 O(n) 到 O(1);
  • fd 數(shù)量限制是系統(tǒng)能打開的最大文件數(shù),會受系統(tǒng)內(nèi)存和每個 fd 消耗內(nèi)存的影響,以當前的系統(tǒng)硬件配置,并發(fā)數(shù)量絕對不是問題。
  • 內(nèi)核使用內(nèi)存映射,大量 fd 向內(nèi)核態(tài)的傳輸不再是問題。

為了一步到位,也是為了學習最先進的I/O多路復用模型,直接使用了 epoll 機制,接下來介紹一下 epoll 相關基礎和自己服務器的實現(xiàn)過程。

epoll介紹

epoll 需要引入<sys/epoll.h>文件,首先介紹一下 epoll 系列函數(shù):

epoll_create

int epoll_create(int size);

創(chuàng)建一個 epoll 實例,返回一個指向此 epoll 實例的文件描述符,當 epoll 實例不再使用時,需要使用close()方法來關閉它。

在最初的實現(xiàn)中, size 作為期望打開的最大 fd 數(shù)傳入,以便系統(tǒng)分配足夠大的空間。在最新版本的內(nèi)核中,系統(tǒng)內(nèi)核動態(tài)分配內(nèi)存,已不再需要此參數(shù)了,但為了避免程序運行在舊內(nèi)核中會有問題,還是要求此值必須大于0;

epoll_ctl

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

  • epfd 是通過 epoll_create 返回的文件描述符
  • op 則是文件描述符監(jiān)聽事件的操作方式,EPOLL_CTL_ADD/EPOLL_CTL_MOD/EPOLL_CTL_DEL 分別表示添加、修改和刪除一個監(jiān)聽事件。
  • fd 為要監(jiān)聽的文件描述符。
  • event 為要監(jiān)聽的事件,可選事件和行為會在下面描述

它的結構如下:

typedef union epoll_data {
   void        *ptr;
   int          fd;
   uint32_t     u32;
   uint64_t     u64;
} epoll_data_t;

struct epoll_event {
   uint32_t     events;      /* epoll事件 */
   epoll_data_t data;        /* 事件相關數(shù)據(jù) */
};

epoll_wait

int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout); 監(jiān)聽 epoll 事件:

  • events 是 epoll 事件數(shù)組,epoll 事件的結構上面已經(jīng)介紹過。
  • maxevents 是一次監(jiān)聽獲取到的最大事件數(shù)目。
  • timeout 是一次監(jiān)聽中獲取不到事件的最長等待時間,設置成 -1 會一直阻塞等待,0 則會立即返回。

epoll行為

在 epoll_ctl 的 event 參數(shù)中,事件 events 有如下可選項:

EPOLLIN(可讀)、EPOLLOUT(可寫)、EPOLLRDHUP(連接關閉)、EPOLLPRI(緊急數(shù)據(jù)可讀),此外 EPOLLERR(錯誤),EPOLLHUP(連接掛斷)事件會被 epoll 默認一直監(jiān)聽。

除了設置事件外,還可以對監(jiān)聽的行為設置:

  • level trigger:此行為被 epoll 默認支持,不必設置。在 epoll_wait 得到一個事件時,如果應用程序不處理此事件,在 level trigger 模式下,epoll_wait 會持續(xù)觸發(fā)此事件,直到事件被程序處理;
  • EPOLLET(edge trigger):在 edge trigger 模式下,事件只會被 epoll_wait 觸發(fā)一次,如果用戶不處理此事件,不會在下次 epoll_wait 再次觸發(fā)。在處理得當?shù)那闆r下,此模式無疑是高效的。需要注意的是此模式需求 socket 處理非阻塞模式,下面會實現(xiàn)此模式。
  • EPOLLONESHOT:在單次命中模式下,對同一個文件描述符來說,同類型的事件只會被觸發(fā)一次,若想重復觸發(fā),需要重新給文件描述符注冊事件。
  • EPOLLWAKEUP:3.5版本加入,如果設置了單次命中和ET模式,而且進程有休眠喚醒能力,當事件被掛起和處理時,此選項確保系統(tǒng)不進入暫?;蛐菝郀顟B(tài)。 事件被 epoll_wait 調(diào)起后,直到下次 epoll_wait 再次調(diào)起此事件、文件描述符被關閉,事件被注銷或修改,都會被認為是處于處理中狀態(tài)。
  • EPOLLEXCLUSIVE:4.5版本加入,為一個關聯(lián)到目標文件描述符的 epoll 句柄設置獨占喚醒模式。如果目標文件描述符被關聯(lián)到多個 epoll 句柄,當有喚醒事件發(fā)生時,默認所有 epoll 句柄都會被喚醒。而都設置此標識后,epoll 句柄之一被喚醒,以避免“驚群”現(xiàn)象。

當監(jiān)聽事件和行為需求同時設置時,使用運算符 |即可。

代碼實現(xiàn)

整體處理邏輯

使用 epoll 時的服務器受理客戶端請求邏輯如下:

1.創(chuàng)建服務器 socket,注冊服務器 socket 讀事件;

2.客戶端連接服務器,觸發(fā)服務器 socket 可讀,服務器創(chuàng)建客戶端 socket,注冊客戶端socket 讀事件;

3.客戶端發(fā)送數(shù)據(jù),觸發(fā)客戶端 socket 可讀,服務器讀取客戶端信息,將響應寫入 socket;

4.客戶端關閉連接,觸發(fā)客戶端 socket 可讀,服務器讀取客戶端信息為空,注銷客戶端 socket 讀事件;

erver_fd = server_start();
epoll_fd = epoll_create(FD_SIZE);
epoll_register(epoll_fd, server_fd, EPOLLIN|EPOLLET);// 這里注冊socketEPOLL事件為ET模式

while (1) {
    event_num = epoll_wait(epoll_fd, events, MAX_EVENTS, 0);
    for (i = 0; i < event_num; i++) {
        fd = events[i].data.fd;
        // 如果是服務器socket可讀,則處理連接請求
        if ((fd == server_fd) && (events[i].events == EPOLLIN)){
            accept_client(server_fd, epoll_fd);
        // 如果是客戶端socket可讀,則獲取請求信息,響應客戶端
        } else if (events[i].events == EPOLLIN){
            deal_client(fd, epoll_fd);
        } else if (events[i].events == EPOLLOUT)
            // todo 數(shù)據(jù)過大,緩沖區(qū)不足的情況待處理
            continue;
    }
}

需要注意的是,客戶端socket在可讀之后也是立刻可寫的,我這里直接讀取一次請求,然后將響應信息 write 進去,沒有考慮讀數(shù)據(jù)時緩沖區(qū)滿的問題。

這里提出的解決方案為:

1.設置一個客戶端 socket 和 buffer 的哈希表;

2.在讀入一次信息緩沖區(qū)滿時 recv 會返回 EAGIN 錯誤,這時將數(shù)據(jù)放入 buffer,暫時不響應。

3.后續(xù)讀事件中讀取到數(shù)據(jù)尾后,再注冊 socket 可寫事件。

4.在處理可寫事件時,讀取 buffer 內(nèi)的全部請求內(nèi)容,處理完畢后響應給客戶端。

5.最后注銷 socket 寫事件。

設置epoll ET(edge trigger)模式

上文說過,ET模式是 epoll 的高效模式,事件只會通知一次,但處理良好的情況下會更適用于高并發(fā)。它需要 socket 在非阻塞模式下才可用,這里我們實現(xiàn)它。

sock_fd = socket(AF_INET, SOCK_STREAM, 0);

// 獲取服務器socket的設置,并添加"不阻塞"選項
flags = fcntl(sock_fd, F_GETFL, 0);
fcntl(sock_fd, F_SETFL, flags|O_NONBLOCK);

.....
// 這里注冊服務器socket EPOLL事件為ET模式
epoll_register(epoll_fd, server_fd, EPOLLIN|EPOLLET);

我將處理事件注掉后使用一次客戶端連接請求進行了測試,很清晰地說明了 ET模式下,事件只觸發(fā)一次的現(xiàn)象,前后對比圖如下:

小結

Mac OS X 操作系統(tǒng)的某些部分是基于 FreeBSD 的,F(xiàn)reeBSD 不支持,MAC 也不支持(不過有相似的 kqueue),跑到開發(fā)機上開發(fā)的,作為一個最基礎的 C learner, 靠著printf()和fflush()兩個函數(shù)來調(diào)試的,不過搞了很久總算是完成了,有用 C 的前輩推薦一下調(diào)試方式就最好了

以上就是如何用C寫一個web服務器之I/O多路復用的詳細內(nèi)容,更多關于用C寫一個web服務器之I/O多路復用的資料請關注腳本之家其它相關文章!

相關文章

  • C語言異或校驗算法的項目實現(xiàn)

    C語言異或校驗算法的項目實現(xiàn)

    異或校驗算法(XOR校驗)是一種簡單的校驗算法,用于檢測數(shù)據(jù)在傳輸或存儲過程中是否發(fā)生了錯誤,本文主要介紹了C語言異或校驗算法的項目實現(xiàn),具有一定的參考價值,感興趣的可以了解一下
    2023-08-08
  • 函數(shù)指針的強制類型轉(zhuǎn)換實現(xiàn)代碼

    函數(shù)指針的強制類型轉(zhuǎn)換實現(xiàn)代碼

    函數(shù)指針的強制類型轉(zhuǎn)換實現(xiàn)代碼。需要的朋友可以過來參考下,希望對大家有所幫助
    2013-10-10
  • 教你如何使用qt quick-PathView實現(xiàn)好看的home界面

    教你如何使用qt quick-PathView實現(xiàn)好看的home界面

    pathView的使用類似與ListView,都需要模型(model)和代理(delegate),只不過pathView多了一個路徑(path)屬性,顧名思義路徑就是item滑動的路徑,下面給大家分享qt quick-PathView實現(xiàn)好看的home界面,一起看看吧
    2021-06-06
  • C++類的空指針調(diào)用成員函數(shù)的代碼

    C++類的空指針調(diào)用成員函數(shù)的代碼

    這篇文章主要介紹了C++類的空指針調(diào)用成員函數(shù),本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2020-07-07
  • C語言 選擇排序算法詳解及實現(xiàn)代碼

    C語言 選擇排序算法詳解及實現(xiàn)代碼

    本文主要介紹C語言 選擇排序算法,這里對排序算法做了詳細說明,并附代碼示例,有需要的小伙伴可以參考下
    2016-08-08
  • C++中判斷成員函數(shù)是否重寫

    C++中判斷成員函數(shù)是否重寫

    這篇文章主要介紹了C++中判斷成員函數(shù)是否重寫的相關資料,需要的朋友可以參考下
    2017-04-04
  • c語言枚舉類型enum的用法及應用實例

    c語言枚舉類型enum的用法及應用實例

    enum是C語言中的一個關鍵字,enum叫枚舉數(shù)據(jù)類型,枚舉數(shù)據(jù)類型描述的是一組整型值的集合,這篇文章主要給大家介紹了關于c語言枚舉類型enum用法及應用的相關資料,需要的朋友可以參考下
    2021-07-07
  • 一篇文章帶你了解C語言文件操作中的幾個函數(shù)

    一篇文章帶你了解C語言文件操作中的幾個函數(shù)

    這篇文章主要介紹了使用C語言操作文件的基本函數(shù)整理,包括創(chuàng)建和打開以及關閉文件的操作方法,需要的朋友可以參考下,希望能夠給你帶來幫助
    2021-09-09
  • C語言深入分析函數(shù)與宏的使用

    C語言深入分析函數(shù)與宏的使用

    C語言函數(shù)是一種函數(shù),用來編譯C語言,一般包括字符庫函數(shù),數(shù)學函數(shù),目錄函數(shù),進程函數(shù),診斷函數(shù),操作函數(shù)等,宏在C語言中是一段有名稱的代碼片段。無論何時使用到這個宏的時候,宏的內(nèi)容都會被這段代碼替換掉
    2022-04-04
  • C++實踐排序函數(shù)模板項目的參考方法

    C++實踐排序函數(shù)模板項目的參考方法

    今天小編就為大家分享一篇關于C++實踐排序函數(shù)模板項目的參考方法,小編覺得內(nèi)容挺不錯的,現(xiàn)在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧
    2019-02-02

最新評論