IO多路轉接之epoll

目錄

一. epoll的實現原理

二.? epoll的相關接口

2.1 epoll_create --?創建epoll模型

2.2 epoll_ctl --?對epoll模型進行控制

2.3?epoll_wait --?等待epoll所關注的事件就緒

2.4?epoll相關接口的使用方法

三. Epoll服務器的模擬實現

3.1 EpollServer類的聲明

3.2?EpollServer類的實現

四.?總結


一. epoll的實現原理

在通過select和poll實現IO多路轉接時,都需要程序員來維護一個數組,用來隨時控制要被關心的事件(fd)。同時,使用select和poll實現IO多路轉接,都需要對用戶維護的數組進行遍歷,遍歷操作的時間復雜度為O(N),這會很大程度上消耗計算資源,降低效率。

為了解決poll和select的這些缺陷,epoll被提了出來,相比于select和poll,epoll在有事件就緒時,不需要逐個遍歷檢查每個被關注的文件描述符是否就緒,希望被關注的事件不需要程序員自己維護數組來控制,這提高了效率,降低了程序員的代碼編寫成本。

在使用epoll實現IO多路轉接之前,必須要先在OS內核中建立epoll模型。如圖1.1所示,epoll模型主要有兩部分組成:

  • 一顆紅黑樹:用來維護用戶所關注的事件(fd),一個紅黑樹節點對應一個被關注的事件,節點中要記錄包括文件描述符,所關注事件的類型(讀/寫/異常)等。
  • 就緒隊列:當有事件就緒的時候,會將已經就緒的事件添加到就緒隊列中去,從隊頭拿走就緒事件及其相關的屬性信息交給用戶層,就能對已經就緒的事件進行響應。

相比于直接遍歷數組查找某個節點O(N)的時間復雜度,使用紅黑樹查找的時間復雜度為O(logN),這樣就提高了OS內核管理事件的效率。同時,通過就緒隊列維護已經就緒的事件,避免了在wait成功之后在遍歷數組的過程中確定具體是哪個事件就緒,這進一步降低了資源的消耗,提高效率。

圖1.1?epoll模型

二.? epoll的相關接口

2.1 epoll_create --?創建epoll模型

函數原型:int epoll_create(size_t size)

頭文件:#include <sys/epoll.h>

函數參數:在Linux 2.6.8版本之后參數size就已經被棄用,這里是為了向前兼容,在調用接口時只需要傳一個大于0的參數即可。

返回值:如果創建成功,返回新創建的epoll模型的文件描述符epfd,如果創建失敗返回-1。

epoll_create函數所執行的工作,就是在操作系統內核中,創建一個如圖1.1所示的epoll模型,即:一顆紅黑樹和一個就緒隊列。epoll_create接口返回值表示被創建的epoll模型的對應fd值,可見OS是將epoll模型當做文件來處理的,這符合Linux下一切接文件的觀點。

2.2 epoll_ctl --?對epoll模型進行控制

函數原型:int epoll_create(int epfd, int op, int fd, struct epoll_event *event)

頭文件:#include <sys/epoll.h>

函數參數:

  • epfd --?所要進行操作的epoll模型對應的文件描述符。
  • op --?用于指定所要進行的操作。
  • fd --?用于對epoll進行操作的文件描述符,如指明要添加關注的事件。
  • event --?輸入型參數,告知OS要關注的事件的屬性信息。

返回值:如果函數執行成功返回0,失敗返回-1。

在該接口函數的參數中,epfd為通過epoll_create創建epoll模型獲取的文件描述符,op用于指定所進行的操作的類型,表2.1為op的可選值及其對應的意義。

表2.1 op的可選值及其意義
op意義
EPOLL_CTL_ADD將指定事件添加到epoll模型中進行關注
EPOLL_CTL_DEL刪除epoll模型中對某個事件的關注
EPOLL_CTL_MOD改變epoll模型中某個事件被關注的狀態(event)

fd用于指定對epoll進行操作的文件描述符,可以為listen文件描述符,也可為普通文件描述符,假設op傳EPOLL_CTL_ADD,那么所epoll_ctl所進行的操作就是將fd加入到epoll模型中進行關注。

struct epoll_event?類型數據定義如下,該類型包含兩個成員,其中一個為uint32_t類型成員events,用于控制該事件的屬性是可讀、可寫還是異常等。events可以為表示4.2中宏的集合,還有一個為聯合自定義類型數據,其中該聯合類型可以傳四種不同的數據表達不同的意義,但一般使用fd來指定文件描述符。

struct epoll_event類型的定義:?

typedef union epoll_data 
{void      *ptr;int        fd;uint32_t   u32;uint64_t   u64;
} epoll_data_t;struct epoll_event 
{uint32_t   events;   /* Epoll events */epoll_data_t data;   /* User data variable */
};
表4.2 events的可選宏及對應含義
events可選宏含義
EPOLLIN對應文件描述符可讀
EPOLLOUT對應文件描述符可寫
EPOLLPRI對應文件描述符帶有緊急數據可讀(TCP緊急指針置1的報文)
EPOLLERR對應文件描述符異常
EPOLLHUP對應文件描述符被掛起
EPOLLSHOT對應文件描述符只被監視一次,監視完一次后會移出epoll模型

2.3?epoll_wait --?等待epoll所關注的事件就緒

函數原型:int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout)

頭文件:#include <sys/epoll.h>

函數參數:

  • epfd --?進行等待的epoll模型文件描述符。
  • events --?輸出型參數,用于存放已經就緒了的事件相關的屬性信息,如文件描述符fd以及就緒事件的類型(可讀/可寫/...)等。
  • maxevents --?一次wait所能獲取到的最大的就緒事件數量。
  • timeout --?最長阻塞等待時間,傳-1表示一直阻塞等待,傳0表示完全非阻塞。

返回值:如果等待成功,返回已經就緒的事件的數量,返回0表示在設定的阻塞時間內沒有事件就緒,返回-1表示等待失敗。

相對于select和poll在等待成功后需要遍歷整個數組來確定具體哪一個文件描述符就緒,epoll_wait等待到的就緒事件相關屬性信息會被放置在events所指向的空間的前n個位置,其中n為就緒事件數量,即epoll_wait的返回值。因此,只需要遍歷events[0] ~ events[n-1]即可,events[0] ~ events[n-1]中記錄的事件一定是已經就緒了的

如果已經就緒了的事件多于maxevents會發生什么情況呢?這時會先拿取maxevents個已經就緒的事件,剩余的等到下一輪epoll_wait再進行提取處理,并不會造成任何錯誤。

2.4?epoll相關接口的使用方法

通過epoll實現IO多路轉接,需要按照以下三步操作執行:

  1. 通過epoll_create創建epoll模型。
  2. 通過epoll_ctl添加對特定事件的關注。
  3. 通過epoll_wait等待所關注的事件的一個或多個就緒。

為了方便調用epoll相關的接口函數,代碼2.1將epoll的3個相關接口函數進行了封裝。在代碼2.1中包含了log.hpp頭文件,里面是日志打印函數的聲明和實現,詳見代碼2.2。

代碼2.1:對epoll接口的封裝(Epoll.hpp頭文件)

#pragma once#include "log.hpp"
#include <cstring>
#include <cerrno>
#include <sys/epoll.h>namespace Epoll
{static const int default_size = 1024;// 創建Epoll模型函數static int EpollCreate(int size = default_size){int epfd = epoll_create(size);if(epfd < 0)logMessage(FATAL, "Epoll create fail, %d:%s\n", errno, strerror(errno));elselogMessage(NORMAL, "Epoll create success, epfd:%d\n", epfd);return epfd;}// 進行Epoll控制函數static int EpollCtl(int epfd, int op, int fd, uint32_t events){struct epoll_event event;event.events = EPOLLIN;event.data.fd = fd;int ret = epoll_ctl(epfd, op, fd, &event);if(ret < 0) logMessage(ERROR, "Epoll control fail, %d:%s\n", errno, strerror(errno));else logMessage(NORMAL, "Epoll control success, ret:%d\n", ret);return ret;}// Epoll等待函數static int EpollWait(int epfd, struct epoll_event* events, int maxevents, int timeout){return epoll_wait(epfd, events, maxevents, timeout);}
}

代碼2.2:日志打印函數的聲明和實現(log.hpp頭文件)

#pragma once
#include <iostream>
#include <cstdio>
#include <cstdarg>
#include <ctime>#define DEBUG  0
#define NORMAL 1
#define WARING 2
#define ERROR  3
#define FATAL  4static const char* g_levelMap[5] = 
{"DEBUG","NORMAL","WARING","ERROR","FATAL"
};static void logMessage(int level, const char *format, ...)
{// 1. 輸出常規部分time_t timeStamp = time(nullptr);struct tm *localTime = localtime(&timeStamp);printf("[%s]  %d-%d-%d, %02d:%02d:%02d\n", g_levelMap[level], localTime->tm_year, localTime->tm_mon, \localTime->tm_mday, localTime->tm_hour, localTime->tm_min, localTime->tm_sec);// 2. 輸出用戶自定義部分va_list args;va_start(args, format);vprintf(format, args);va_end(args);
}

三. Epoll服務器的模擬實現

3.1 EpollServer類的聲明

在EpollServer中,要包含以下成員變量:epoll模型文件描述符、listen套接字、端口號、ip地址以及指向存放就緒事件相關屬性信息的空間的指針。

要有以下成員函數:構造函數和析構函數、服務器運行函數Start、就緒事件處理函數Handler、接收客戶端連接請求函數Accepter、對端數據讀取函數Reciever。

#pragma once#include "Sock.hpp"
#include "Epoll.hpp"
#include <unistd.h>static const int maxevents = 64;class EpollServer
{
public:EpollServer(uint16_t port = 8080, const std::string& ip = "");   // 構造函數void Start();    // 啟動運行函數~EpollServer();  // 析構函數private:void Handler(int n);            // 就緒事件處理函數void Accepter(int listenSock);  // 連接接收函數void Reciever(int fd);          // 信息讀取函數int _listenSock;   // 監聽套接字int _epfd;         // epoll套接字uint16_t _port;    // 服務進程端口號std::string _ip;   // 服務器ip struct epoll_event* _ptr_events;   // 指向就緒隊列的指針
};

3.2?EpollServer類的實現

  • 構造函數:首先要進行關于tcp通信的常規準備工作,獲取listen套接字、綁定端口號、設置監聽狀態,之后要創建epoll模型、開辟一塊內存空間用于存放wait到的就緒事件并將listen套接字添加到epoll模型中去。
  • 析構函數:判斷listen套接字和epoll模型文件描述符是否>=0,如果是,就close掉它們。再判斷_ptr_events是否為nullptr,如果否,要delete[]釋放動態申請的內存空間。
  • 服務器運行函數Start:常駐進程,執行while死循環,每層while循環都調用epoll_wait檢測事件就緒的情況,如果epoll_wait返回值大于0,那么調用Handler函數處理就緒事件。
  • 就緒事件處理函數Handler:接收一個參數n表示已就緒事件的數量,遍歷_ptr_events[0] ~?ptr_events[n-1],根據就緒的是listen文件描述符還是普通文件描述符,分類進行后續處理,listen文件描述符調用Accepter函數接收對端連接,普通文件描述符調用Reciever函數接收數據。
  • 接收對端連接函數Accepter:獲取對端連接,分配文件描述符fd,并將fd添加到epoll模型中去。
  • 數據讀取函數Reciever:調用read函數讀取客戶端發送的數據,如果read返回值>0,那么就執行對應操作處理讀取到的數據,如果read返回值為0,那么表示客戶端關閉,要將對應的文件描述符fd從epoll模型中刪除。

代碼3.2:EpollServer的實現(EpollServer.cc源文件)

#include "EpollServer.hpp"EpollServer::EpollServer(uint16_t port, const std::string& ip): _listenSock(-1), _epfd(-1), _port(port), _ip(ip), _ptr_events(nullptr)
{// 1.獲取監聽套接字_listenSock = Sock::Socket();if(_listenSock < 0) exit(1);// 2.綁定端口號if(Sock::Bind(_listenSock, _ip, _port) < 0) exit(2);// 3.設置監聽狀態if(Sock::Listen(_listenSock) < 0) exit(3);// 4.創建epoll模型_epfd = Epoll::EpollCreate();if(_epfd < 0) exit(4);// 5.為_events開辟內存空間并進行初始化_ptr_events = new epoll_event[maxevents];// 6.將listenSock添加到epoll模型中if(Epoll::EpollCtl(_epfd, EPOLL_CTL_ADD, _listenSock, EPOLLIN) < 0) exit(5);logMessage(NORMAL, "EpollServer init success!\n");
}// Epoll服務器啟動運行函數
void EpollServer::Start()
{while(1){// 對epoll進行等待int n = Epoll::EpollWait(_epfd, _ptr_events, maxevents, -1);switch(n){case 0:logMessage(DEBUG, "epoll wait time out!\n");break;case -1:logMessage(ERROR, "epoll wait error, %d:%s\n", errno, strerror(errno));break;default:Handler(n);break;}}
}// 析構函數
EpollServer::~EpollServer()
{if(_listenSock >= 0) close(_listenSock);if(_epfd >= 0) close(_epfd);if(_ptr_events) delete[] _ptr_events;
}// 就緒事件處理函數
void EpollServer::Handler(int n)
{// 遍歷_events,查找已經就緒的事件for(int i = 0; i < n; ++i){// 分listen套接字和普通套接字兩種情況討論if(_ptr_events[i].data.fd == _listenSock) Accepter(_listenSock);else Reciever(_ptr_events[i].data.fd);}
}// 連接接收函數
void EpollServer::Accepter(int listenSock)
{std::string cli_ip;   // 發起連接的客戶端ipuint16_t cli_port;    // 客戶端端口號int fd = Sock::Accept(listenSock, cli_ip, cli_port);if(fd < 0) return;// 將新增的fd添加到epoll模型中去Epoll::EpollCtl(_epfd, EPOLL_CTL_ADD, fd, EPOLLIN);logMessage(NORMAL, "Add a new fd to epoll success, fd:%d\n", fd);
}// 信息讀取函數
void EpollServer::Reciever(int fd)
{char buffer[1024];ssize_t n = read(fd, buffer, 1023);// 如果讀取成功if(n > 0){buffer[n - 1] = '\0';printf("Client# %s\n", buffer);}else if(n == 0){// 對端關閉,將fd從epoll模型中拿走Epoll::EpollCtl(_epfd, EPOLL_CTL_DEL, fd, EPOLLIN);if(n == 0){// logMessage(NORMAL, "Remove fd from epoll success, fd:%d\n", fd);printf("Remove fd from epoll success, fd:%d\n", fd);close(fd);}}else  // 讀取數據失敗{logMessage(ERROR, "Read message fail, %d:%s\n", errno, strerror(errno));}
}

四.?總結

  • 相比于通過select和poll實現IO多路轉接,epoll不需要程序員維護數組來控制關注的事件,在有事件就緒后也不需要遍歷數組查找具體哪個事件就緒,效率較高。
  • epoll底層維護一顆紅黑樹存儲要關心的事件,維護一個就緒隊列表示已經就緒的事件。
  • epoll實現IO多路轉接,要進行的操作為:epoll_create創建epoll模型 ->?epoll_ctl向epoll模型中添加受到關注的文件描述符 -> epoll_wait等待事件就緒。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/167486.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/167486.shtml
英文地址,請注明出處:http://en.pswp.cn/news/167486.shtml

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

相關文章

網工內推 | 美的、得力集團,包吃包住,IE認證優先,14薪

01 美的 招聘崗位&#xff1a;網絡工程師 職責描述&#xff1a; 1.負責IT網絡設備、IDC機房的日常維護巡檢、監控和管理&#xff1b; 2.負責路由、交換、防火墻、無線控制器、AP等網絡設備的開通、調整、優化升級&#xff1b; 3.負責公司OT、IT網絡規劃&#xff0c;項目實施以…

路由VRRP配置例子

拓樸如下&#xff1a; 主要配置如下&#xff1a; [R1] interface GigabitEthernet0/0/0ip address 10.1.1.1 255.255.255.0 vrrp vrid 1 virtual-ip 10.1.1.254vrrp vrid 1 priority 200vrrp vrid 1 preempt-mode timer delay 20 # interface GigabitEthernet0/0/1ip address …

【10套模擬】【10】

關鍵字&#xff1a; 線性探測次數、冒泡交換性質、排序次數最值、bst查找關鍵字最多比較次數、m叉樹空指針域 鏈表合并、二叉排序樹查找x、堆排序

flink的集成測試

背景 日常測試中我們使用flink的TestHarness只能測試單個算子&#xff0c;很多情況下我們需要集成測試來測試真正的問題&#xff0c;所以在flink中進行集成測試還是非常有必要的&#xff0c;本文就來記錄下如何在flink中進行集成測試 flink中進行集成測試 flink中進行集成測…

css給盒子寫四個角

如圖&#xff1a;之前一直用定位 現在發現可以用css寫 background: linear-gradient(to top, #306eef, #306eef) left top no-repeat, /*上左*/ linear-gradient(to right, #306eef, #386eef) left top no-repeat, /*左上*/ linear-gradient(to left, #386eef, #306eef) righ…

查找學習筆記

1、靜態查找表 以下查找的索引均從1開始 &#xff08;1&#xff09;順序查找&#xff08;帶哨兵&#xff09; #include<iostream> #include<vector>using namespace std;int search(vector<int> arr, int key) {arr[0] key;int i;for (i arr.size() - 1…

代碼隨想錄 860. 檸檬水找零

題目 在檸檬水攤上&#xff0c;每一杯檸檬水的售價為 5 美元。顧客排隊購買你的產品&#xff0c;&#xff08;按賬單 bills 支付的順序&#xff09;一次購買一杯。 每位顧客只買一杯檸檬水&#xff0c;然后向你付 5 美元、10 美元或 20 美元。你必須給每個顧客正確找零&#xf…

python opencv -模板匹配

python opencv -模板匹配 模板匹配就是&#xff0c;我們現有一個模板和一個圖片&#xff0c;然后&#xff0c;在這個圖片中尋找和模板近似的部分。 在opencv 中主要通過cv2.matchTemplate這個函數去實現。 下面我們先看一下&#xff0c;模板圖片和需要匹配的圖片&#xff1a…

(Matalb時序預測)GA-BP遺傳算法優化BP神經網絡的多維時序回歸預測

目錄 一、程序及算法內容介紹&#xff1a; 基本內容&#xff1a; 亮點與優勢&#xff1a; 二、實際運行效果&#xff1a; 三、部分代碼 四、本文代碼數據說明手冊分享&#xff1a; 一、程序及算法內容介紹&#xff1a; 基本內容&#xff1a; 本代碼基于Matalb平臺編譯&am…

Spring IOC 和 AOP

Spring IOC 什么是 IoC ? IoC &#xff08;Inversion of Control 控制反轉&#xff09;是一種設計思想&#xff0c;而不是一個具體的技術實現。IoC 的思想就是將原本在程序中手動創建對象的控制權&#xff0c;交由給 Spring 框架來管理。 為什么叫控制反轉&#xff1f; 控制…

unsigned詳講(干貨滿滿)

前言&#xff1a;過年偷懶了(●ˇ?ˇ●)&#xff0c;但是年后開學了一定要恢復學習狀態&#xff0c;在復習加繼續學習的途中&#xff0c;我發現對于unsigned關鍵字的掌握并不是很熟練&#xff0c;于是翻閱了各個大佬的博客以及書籍&#xff0c;總結了對于unsigned的一些知識點…

數據結構與算法編程題18

循環隊列相關代碼。 #include <iostream> using namespace std;#define Maxsize 100 #define ERROR 0 #define OK 1 typedef int Elemtype; typedef struct Queue {Elemtype data[Maxsize];int front;int rear; }Queue;void Init_Queue(Queue &Q) {Q.front Q.rear …

P9 C++類

目錄 01 類是什么 02 如何創建類 03 方法 后話 本期我們要講的是 C 中的類。 我們終于講到了面向對象編程&#xff0c;這是一種非常流行的編程方式&#xff0c;面向對象編程實際上只是一種你可以采用的編寫代碼的方式&#xff0c;其他語言例如 C#、Java 這些主要是面向對象…

白嫖CTG4.0

大家好&#xff0c;到點了我來給各位大佬獻策CTG&#xff0c;不是花錢買不起&#xff0c;而是免費更有性價比&#xff0c;哈哈哈不調侃了我們自此開始正文&#xff0c;咱們主打的就是一個分享是一種態度 當然我更希望大家支持國產對國產有自己的信心&#xff08;文心一言&…

Git常用命令詳細總結,更適合中國寶寶體質

文章目錄 代碼倉庫創建倉庫1.進入需要創建代碼庫的文件夾2.創建/切始化倉庫3.關聯遠程倉庫拉取遠程倉庫到本地 添加文件到倉庫1.查看工作區狀態2.添加文件到暫存區3.提交到本地倉庫4.對比工作區文件變化 倉庫配置1.配置全局用戶名和郵箱2.配當前倉庫用戶名和郵箱3.查看Git全局配…

Selenium中常用的JS操作總結

? 目錄 前言&#xff1a; JS相關操作 JS Xpath定位 獲取單個元素 獲取元素集合 文本輸入 獲取坐標 獲取瀏覽器窗口的內部高度 獲取瀏覽器窗口的內部寬度&#xff1b; 坐標計算 設置樣式 設置窗口大小 類數組對象arguments JQuery選擇器 jQuery 選擇器 jQuery …

多模態——使用stable-video-diffusion將圖片生成視頻

多模態——使用stable-video-diffusion將圖片生成視頻 0. 內容簡介1. 運行環境2. 模型下載3. 代碼梳理3.1 修改yaml文件中的svd路徑3.2 修改DeepFloyDataFiltering的vit路徑3.3 修改open_clip的clip路徑3.4 代碼總體結構 4. 資源消耗5. 效果預覽 0. 內容簡介 近期&#xff0c;…

Linux上安裝Redis

案例中Linux版本為CentOS7.9&#xff0c;安裝目錄為 /root/software/ 1、使用 wget 命令從官網下載安裝包 wget https://github.com/redis/redis/archive/7.2.3.tar.gz2、解壓縮 tar -xzf 7.2.3.tar.gz3、進入解壓后的目錄 cd redis-7.2.34、 編譯和安裝Redis make make i…

npm中,你不了解的.npmrc文件

原文鏈接&#xff1a;npm中&#xff0c;你不了解的.npmrc文件 寫在前面 對于寫JS的程序員來說&#xff0c;可能沒有人不知道npm&#xff0c;但是有些同學對他的配置文件(即.npmrc文件)并不了解。結合我的學習心得&#xff0c;寫一篇博客跟大家分享一些該配置文件的知識。 .np…

理解CLIP模型

1.簡介 學習深度學習必看CLIP&#xff01;論文鏈接arxiv.org/pdf/2103.00020v1.pdf。 簡單來說就是傳統的分類任務被用來預測指定的類別&#xff0c;有監督訓練限制了模型的通用性和可用性&#xff0c;并且需要帶有標簽的數據來訓練&#xff0c;該篇論文就想直接從原始文本中…