Linux網絡:多路轉接 epoll

Linux網絡:多路轉接 epoll

  • 一、epoll三個接口函數
    • 1、epoll_create
    • 2、epoll_ctl
    • 3、epoll_wait
  • 二、epoll的工作原理
  • 三、epoll的echo_server
    • 1、EpollServer類
    • 2、構造函數
    • 3、事件循環
    • 4、事件派發
    • 5、事件處理
    • 6、測試
  • 四、LT和ET模式
    • 1、LT
    • 2、ET
  • 五、項目代碼

一、epoll三個接口函數

多路轉接是非常高效的一種IO模型,它可以在同一時間等待多個套接字,從而提高效率。Linux提供了三種系統調用實現多路轉接:select、poll、epoll。本博客講解epoll。

epoll是經過改進的poll,在Linux 2.5.44版本引入內核,并認為是Linux2.6最好的多路轉接實現方案

1、epoll_create

epoll_create用于創建一個epoll模型,需要頭文件<sys/epoll.h>,函數原型如下:

int epoll_create(int size);

此處的參數size已經被廢棄,可以填入大于0的任何值。

返回值是一個文件描述符,通過這個文件描述符,可以操控Linux底層創建的epoll(主要是那兩個模型)

2、epoll_ctl

epoll_ctl用于控制epoll模型,需要頭文件<sys/epoll.h>,函數原型如下:

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

參數:

  1. epfd:通過epoll_create獲取到的文件描述符
  2. op:本次執行的操作,傳入宏
  3. fd:要監聽的文件的文件描述符
  4. event:對文件要執行的監聽類型

其中:op操作包括

  • EPOLL_CTL_ADD新增一個文件描述符到epoll中
  • EPOLL_CTL_MOD修改一個epoll中的文件描述符
  • EPOLL_CTL_DEL:從epoll中刪除一個文件描述符

其中event的類型是struct epoll_event*,該結構體定義如下:

struct epoll_event {uint32_t      events;  /* Epoll events */epoll_data_t  data;    /* User data variable */
};union epoll_data {void     *ptr;int       fd;uint32_t  u32;uint64_t  u64;
};

這個結構體中,包含events和data兩個字段:

  1. events:一個位圖,存儲要監聽的事件以及一些其它配置
  2. data:當epoll返回時,攜帶的數據

此處的data是一個聯合體,它可以存儲四種類型的數據:ptr指針,int文件描述符,uint32_t和uint64_t的整型。

其中events包括:

  • EPOLLIN:監聽讀事件
  • EPOLLOUT:監聽寫事件
  • EPOLLERR:監聽錯誤事件
  • EPOLLHUP:文件描述符被關閉
  • EPOLLONESHOT:只監聽一次事件,本次監聽完畢,文件描述符被從epoll中移除

當一個epoll返回已經就緒的文件時,用戶其實無法得知這個文件的描述符,那么就可以通過這個data.fd獲取到文件描述符,當然也可以通過其它的參數,傳遞更復雜的信息。

3、epoll_wait

epoll_wait用于等待epoll模型中的文件就緒,需要頭文件<sys/epoll.h>,函數原型如下:

int epoll_wait(int epfd, struct epoll_event *events,int maxevents, int timeout);

參數:

  • epfd:通過epoll_create獲取到的文件描述符
  • events:輸出型參數,指向一個epoll_event就緒數組,獲取之前通過epoll_ctl傳入的events
  • maxevents:用戶傳入的events數組的最大長度(就緒事件個數)
  • timeout:超時時間,以ms為單位

此處用戶要傳入一個epoll_event數組,這個數組用于存儲本次就緒的所有文件的epoll_event,為了防止越界,所以還要傳入maxevents。

就是說,epoll的使用方式是通過epoll_wait獲取就緒的文件,這些文件存儲到epoll_event數組中。

函數返回,用戶可以遍歷數組,獲取到所有就緒的文件的epoll_event結構體。

這個結構體是在epoll_ctl時傳入的,從它的events字段可以得知這個文件監聽的事件,從data字段可以獲取之前預設的其他信息,一般會預設data.fd獲取這個文件的描述符

返回值:

  • 0:超時,指定時間內沒有文件就緒
  • <0:出現錯誤
  • >0:就緒的文件的個數

通過此處,已經可以看出epoll相比于select的優勢了:

epoll返回時,把已經就緒的文件放到數組中,后續遍歷數組,每一個元素都是已經就緒的文件

在select中,就緒的事件通過一張位圖返回,用戶需要遍歷整個位圖所有元素,并判斷該元素是否就緒,那么就會浪費大量的時間在未就緒的文件上。

epoll返回時,不會把已經加入epoll的文件刪除,而是繼續監聽該文件

這是另一大優勢,在select中,每次返回都會重置用戶傳入的位圖,因此用戶在每次輪詢都要重新把文件描述符設置到select。

當然,用戶也可以在epoll_ctl的時候,設置EPOLLONESHOT,那么這個文件被epoll返回后,就會從epoll中刪除,也就是只監聽一次事件

二、epoll的工作原理

在這里插入圖片描述
在這里插入圖片描述
Epoll的工作原理:
在這里插入圖片描述
一種特殊的數據結構:
在這里插入圖片描述
雙層結構體:結構體嵌套結構體

在這里插入圖片描述

三、epoll的echo_server

接下來使用epoll系統調用,實現一個簡單的echo server

1、EpollServer類

// 多路轉接:事件循環、事件派發、事件處理!
class EpollServer
{
public:// 構造函數EpollServer(uint16_t port){}// 初始化函數void InitServer(){}// 轉換字符串std::string EventsToString(uint32_t events)// 事件處理:網絡套接字文件void Accepter(){}// 事件處理:普通文件void HandlerIO(int fd){}// 事件派發void HandlerEvent(int n){}// 事件循環void Loop(){}// 析構~EpollServer(){}
private:uint16_t _port;std::unique_ptr<Socket> _listensock;int _epfd;  // epoll_create的返回值!epoll句柄struct epoll_event revs[num]; // 將內核epoll模型里面的就緒事件,存入revs(revs是epoll_wait的參數)
};

2、構造函數

構造函數代碼如下:

// 構造函數EpollServer(uint16_t port): _port(port), _listensock(std::make_unique<TcpSocket>()){_listensock->BuildListenSocket(port); // 根據傳來的port來創建監聽套接字!!!// 1、epoll_create創建成功,說明底層內核已經創建好了:紅黑樹+就緒隊列_epfd = ::epoll_create(size);// 這里的參數size>0即可if (_epfd < 0){LOG(FATAL, "epoll create fail\n");exit(-1);}LOG(INFO, "epoll create sucess ,epfd:%d\n", _epfd);}

3、事件循環

事件循環代碼如下:

開啟循環后,進入一個while(true)死循環,每一輪循環通過epoll_wait獲取本輪循環就緒的文件:

// 事件循環void Loop(){int timeout = 1000; // 設置時限!1swhile (true){int n = ::epoll_wait(_epfd, revs, num, timeout); // epoll_wait的返回值就是就緒事件的個數switch (n){case 0:LOG(INFO, "epoll time out...\n");break;case -1:LOG(ERROR, "epoll wait fail\n");break;default:LOG(INFO, "have event happend! n:%d\n", n);HandlerEvent(n);// 事件派發break;}}}

4、事件派發

事件派發就是判斷文件描述符是_listenfd還是普通的sockfd,調用不同的函數進行處理。

    // 事件派發void HandlerEvent(int n){for (int i = 0; i < n; i++){int fd = revs[i].data.fd;uint32_t revents = revs[i].events; // 具體是哪一個fd里面的什么事件就緒了!?LOG(INFO, "%d 上面的有事件就緒了,具體事件是:%s\n", fd, EventsToString(revents).c_str());// 事件處理:封裝!// 1、listensock就緒if (fd = _listensock->Sockfd()){Accepter();}// 2、處理普通fdelse{HandlerIO(fd);}}}

5、事件處理

void Accepter(){InetAddr addr;int sockfd = _listensock->Accepter(&addr);if (sockfd < 0){LOG(ERROR, "獲取連接失敗\n");return ;}LOG(INFO, "得到一個新鏈接:%d,客戶端信息:%s:%d\n", sockfd, addr.Ip().c_str(), addr.Port());struct epoll_event ev;ev.events = EPOLLIN;ev.data.fd = sockfd;// 必須將sockfd加入到epoll里面,這樣才能讓epoll對提取出來的sockfd進行處理!::epoll_ctl(_epfd, EPOLL_CTL_ADD, sockfd, &ev);LOG(INFO, "add sucess to epoll,new sockfd:%d\n", sockfd);}void HandlerIO(int fd){char buffer[4096];int n = ::recv(fd, buffer, sizeof(buffer) - 1, 0);if (n > 0){buffer[n] = 0;std::cout << buffer;}else if (n == 0){LOG(INFO, "client,quit...\n");// 先將這個退出的fd從epoll中移除,再關閉fd::epoll_ctl(_epfd, EPOLL_CTL_DEL, fd, nullptr);::close(fd);}else{LOG(ERROR, "client,fail...\n");::epoll_ctl(_epfd, EPOLL_CTL_DEL, fd, nullptr);::close(fd);}}

6、測試

在這里插入圖片描述

四、LT和ET模式

思考一個問題:如果用戶通過epoll檢測到某個socket的事件已經就緒了,但是這個用戶沒有處理這個事情,下一次epoll_wait還要不要返回這一個事件?

就是基于這個問題,衍生了兩種epoll工作模式:LT模式與ET模式

1、LT

LT模式下,當用戶沒有處理事件,那么事件就一直保留在就緒鏈表rdlink中,每次調用epoll_wait都會返回這個事件

這種模式是epoll的默認模式

用戶接收到事件后,可能某個報文太長了,一次讀不完。那么LT模式下一次還會進行通知,用戶可以把剩下的報文讀完。但是這就可能導致一個報文,需要調用更多次的epoll_wait。

2、ET

ET模式下,當用戶通過epoll_wait拿到事件后,事件直接從rdlink中刪除,下一次不再進行通知

這種模式比LT更加高效,這可以從兩個角度解讀:

  1. 這種模式下,一個報文只需要調用一次epolll_wait,因此效率高一點
  2. 這倒逼程序員必須一次性把報文讀完,那么就會更快的進行業務處理,報文響應速度也更快

這里主要是第二點比較重要,當一個報文太長了,但是ET模式下只進行一次通知。那么程序員收到通知后,就需要用一個while循環一直讀取套接字,直到讀不出數據為止。這樣一次通知程序員就能拿到完整報文,進而更早的進行業務處理,更早響應。而且提早把數據讀走,內核的緩沖區也會被空出來,接收更多的新數據。

默認情況下,從文件讀取文件是阻塞的,當最后一次while循環讀取不出內容了,程序就會阻塞住。因此這種情況下,要把文件讀取改為非阻塞讀取,如果讀不出內容直接返回。

但是這也導致ET的程序會比LT更加復雜,實際開發中需要進行權衡。

五、項目代碼

epoll和select代碼

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

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

相關文章

uniapp 微信小程序 列表點擊分享 不同的信息

<button open-type"share" plain class"item share" click.stop"shareFn(item)"><text>分享</text> </button>import {onShareAppMessage} from dcloudio/uni-applet shareObj ref({})// 將點擊后的分享設置信息 關鍵…

C# 匿名方法詳解

C# 匿名方法詳解 引言 在C#編程語言中,匿名方法是使用Lambda表達式創建的沒有名稱的方法。它們在LINQ查詢、事件處理和其他場合中非常有用。本文將詳細介紹C#匿名方法的基本概念、語法、使用場景以及優勢。 匿名方法的概念 匿名方法是一種無需顯式定義名稱的方法。在C#中,…

SD卡簡介與驅動開發

基本概念 存儲卡有很多種類&#xff0c;CF卡、記憶棒、SD卡、XD卡、MMC卡、MS卡、TF卡、MicroSD卡等。平時最常見的有SD卡和MicroSD卡兩種&#xff0c; SD卡和MicroSD只是兩張卡的大小不同&#xff0c;規格版本是完全相同的&#xff0c;均由SD卡協會推出。 SD卡有不少規范&…

大數據平臺數倉數湖hive之拉鏈表高效實現

對于緩慢變化的維度表&#xff0c;如客戶表&#xff0c;員工表&#xff0c;為了不丟失歷史數據&#xff0c;又不至于太浪費存儲空間&#xff0c;我們采用拉鏈表實現。 實現過程如下&#xff1a; 1、采集初始數據&#xff1a; 1.1 從mysql導出數據到hdfs /data/dolphinschedu…

【VSCode】常用插件推薦(持續更新~)

以下的這些插件都有使用過&#xff0c;可取對自己編碼有用的選擇安裝。&#x1f9e0; 智能補全 / 提示類插件 1. Auto Import在編碼時選擇有對應導入包的選項&#xff0c;自動為 JS/TS 文件中的使用項補全并添加 import 聲明&#xff0c;極大提高開發效率。2. Iconify IntelliS…

ICML 2025 | 深度剖析時序 Transformer:為何有效,瓶頸何在?

本文介紹帝國理工學院等機構在 ICML 2025 發表的最新研究成果。該研究并未提出新模型&#xff0c;而是對現有時間序列 Transformer 模型進行了一次深刻的拷問——為何結構更簡單的 Transformer&#xff08; PatchTST, iTransformer&#xff09;在各大基準測試中&#xff0c;反而…

AIBOX內置5G天線設計

AIBOX內置5G天線設計AIBOX的天線種類天線種類及數量&#xff1a;運營商5G天線*4&#xff0c;1.4G天線*2、wifi天線*1天線形式&#xff1a;內置PCB天線。天線安裝方式&#xff1a;卡扣固定&#xff0c;安裝至設備外殼內壁。RG-178同軸線或UFL1.37mm同軸線連接至主板&#xff0c;…

低通濾波器的原理以及作用

低通濾波器&#xff08;Low-Pass Filter, LPF&#xff09;是一種允許低頻信號通過&#xff0c;同時衰減或阻止高頻信號的電子電路或數字信號處理算法。其核心原理和作用如下&#xff1a;一、工作原理 1. 頻率選擇性- 低通濾波器基于頻率對信號進行篩選&#xff0c;其傳遞函數在…

[AI Coding] 一.騰訊CodeBuddy IDE內測、安裝及基本用法(國產AI IDE啟航)

在人工智能迅猛發展的今天&#xff0c;AI Coding 正逐步改變傳統編程范式。廣義上&#xff0c;AI Coding 是指以大語言模型&#xff08;LLMs&#xff09;為核心驅動&#xff0c;借助自然語言理解能力&#xff0c;自動生成、補全、調試與解釋代碼的智能編程方式。它不僅顯著降低…

《網安處罰裁量基準》碼農合規指北 v1.0——if (違規) { 罰金++ } else { 合規運行 }

尊敬的審核&#xff1a; 本人文章《〈網安處罰裁量基準〉碼農合規指北 v1.0——if (違規) { 罰金 } else { 合規運行 }》 1. 純屬技術交流&#xff0c;無任何違法內容 2. 所有法律引用均來自公開條文 3. 請依據《網絡安全法》第12條“不得無故刪除合法內容”處理 附&#xff1a…

機器學習——邏輯回歸(LogisticRegression)實戰案例:信用卡欺詐檢測數據集

使用邏輯回歸識別 信用卡欺詐行為&#xff1a;基于creditcard.csv的實戰與評估分析 項目背景 在金融行業中&#xff0c;信用卡欺詐檢測是一項關鍵任務。欺詐交易在整個交易中占比極低&#xff0c;導致數據極度不平衡。本案例通過經典數據集 creditcard.csv&#xff0c;構建邏輯…

Helm在Kubernetes中的應用部署指南與案例解析

在上一章節中&#xff0c;我們已經介紹了Helm的部署和基本使用方法。本章將通過實際案例&#xff0c;詳細演示如何使用Helm在Kubernetes集群中部署應用。一、Helm 核心價值解析優勢解決的問題類比傳統方式應用模板化重復編寫 YAML 文件手動編寫 20 資源清單文件版本控制缺乏部署…

如何最簡單、通俗地理解線性回歸算法? 線性回歸模型在非線性數據上擬合效果不佳,如何在保持模型簡單性的同時改進擬合能力?

線性回歸作為統計學與機器學習領域中最基礎且最重要的算法之一&#xff0c;其應用廣泛且深遠。它不僅是回歸分析的入門方法&#xff0c;更是后續復雜模型構建的重要理論基礎。理解線性回歸算法的本質&#xff0c;既有助于提升數據分析的能力&#xff0c;也能為掌握更復雜的機器…

藍橋杯----超聲波

&#xff08;一&#xff09;、超聲波1、原理&#xff08;圖 一&#xff09;發送信號階段&#xff1a;單片機通過翻轉發送的引腳P1^0&#xff0c;發送8個40MHZ的方波&#xff0c;此時開始計時。等待接收信號&#xff1a;通過單片機的接收引腳P1^1檢測&#xff0c;未接收到信號時…

Java學習-運算符

1.在代碼中&#xff0c;如果有小數參與計算&#xff0c;結果有可能不精確。2.整數參與計算&#xff0c;結果只能是整數。3.數字進行運算時&#xff0c;數據類型不一樣不能運算&#xff0c;需要轉成一樣的&#xff0c;才能運算。&#xff08;1&#xff09;隱式轉換&#xff08;自…

一句話指令實現“2D轉3D”、“圖片提取線稿”

你是否曾為一張2D圖片無法完美展示3D效果而遺憾&#xff1f;是否曾因需要將手繪草圖轉為清晰線稿而耗時費力&#xff1f;這些曾讓設計師、電商賣家、內容創作者頭疼的難題&#xff0c;如今只需一句話指令&#xff0c;即可迎刃而解。一、案例一&#xff1a;2D圖片→3D模型痛點場…

層次聚類:無需“猜”K值,如何讓數據自己畫出“家族圖譜”?

層次聚類&#xff1a;無需“猜”K值&#xff0c;如何讓數據自己畫出“家族圖譜”&#xff1f;&#x1f44b; 大家好&#xff0c;我是小瑞瑞&#xff01;歡迎回到我的專欄&#xff01; 在上一期&#xff0c;我們學會了強大的K-Means算法&#xff0c;但它也給我們留下了一個“靈魂…

數據結構:鏈表(Linked List)

目錄 結構推導 回到最原始的問題 —— 我們如何存數據&#xff1f; 第二步&#xff1a;我們來看看數組的限制 第三步&#xff1a;那我們該怎么做呢&#xff1f; 第四步&#xff1a;我們推導鏈表的數據結構 結構講解 什么是鏈表&#xff1f; 什么是節點&#xff1f; …

[RK3566-Android11] U盤頻繁快速插拔識別問題

問題描述 做老化測試時&#xff0c;在使用U盤頻繁快速插拔的情況下&#xff0c;SDCard目錄會突然被Kill掉&#xff0c;然后又重新掛載上&#xff0c;這會導致系統及APP的數據因為讀寫異常&#xff0c;從而界面卡死正常U盤插拔不應該導致內部存儲卸載解決方案&#xff1a; SDK根…

【Golang】Go語言Map數據類型

Go語言Map數據類型 文章目錄Go語言Map數據類型一、Map1.1.1、map定義1.1.2、map的基本使用1.1.3、判斷某個鍵是否存在1.1.4、map的遍歷1.1.5、使用delete()函數刪除鍵值對1.1.6、按照指定順序遍歷map1.1.7、元素為map類型的切片1.1.8、值為切片類型的map一、Map map是一種無序…