【Linux】多路轉接之epoll

優化poll進行拷貝的開銷

poll開銷過大

  • 將整個 pollfd 數組拷貝到內核態,以便內核檢查 fd 是否就緒(從用戶態 → 內核態)。
  • 內核檢查 fd 狀態,并填充 revents。
  • 將 pollfd 數組從內核態拷貝回用戶態,讓應用程序可以讀取 revents 里的就緒狀態(從內核態 → 用戶態)。

這個拷貝過程涉及 所有 pollfd 結構體,而 pollfd 結構體的大小通常是 8~16字節(取決于架構),如果監聽 fd 數量很大(例如 上千個),拷貝數據量會很大,導致 系統調用的開銷上升。

優化遍歷開銷

  • 優化 poll 遍歷查詢空位置的問題:poll 需要遍歷整個 pollfd 數組來找到空閑位置,以便管理 fd,當 fd 數量龐大時,維護成本很高。
  • 優化遍歷查詢就緒 fd 的問題:poll 在返回時,仍然需要遍歷整個 pollfd 數組來尋找就緒的 fd,時間復雜度為 O(n)。

epoll 的優化點(epoll_ctl + epoll_wait 分離)

操作說明
epoll_ctl只在你要添加/修改/刪除監聽 fd 時才調用一次,相當于“注冊監聽列表”
epoll_wait每次只等待事件,不關心你監聽了哪些 fd,內核直接返回“就緒事件”

認識epoll的接口

poll 和select 都通過一個接口完成“注冊 + 等待”,每次調用都要傳遞和檢查全部 fd,效率低;
而epoll 將監聽與等待分離,通過epoll_ctl 注冊事件,通過epoll_wait 等待事件,減少了不必要的遍歷和數據拷貝

epoll_create()

作用

創建一個 epoll 實例,并返回一個 epoll 文件描述符(epfd),該 epfd 用于后續的 epoll_ctl() 和 epoll_wait() 操作。

函數原型

int epoll_create(int size);這個參數已經進行廢棄了,但是為了進行向前兼容沒有將該參數進行刪除。

返回值

  • 成功:返回 epfd(epoll 實例的文件描述符)。
  • 失敗:返回 -1,errno 指示錯誤類型。

epoll_ctl()

作用

管理 epoll 監控的文件描述符,對epoll模型進行操作,包括添加、修改、刪除操作。

函數原型

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

參數

  • epfd:由 epoll_create() 生成的 epoll 句柄。
  • op:操作類型,支持以下三種:

????????EPOLL_CTL_ADD:添加一個新的 FD 到 epoll 實例。

????????EPOLL_CTL_MOD:修改已有的 FD 監聽的事件。

????????EPOLL_CTL_DEL:從 epoll 實例中刪除 FD。

  • fd:需要監聽的文件描述符。
  • event:指向 struct epoll_event 結構體的指針,設置監聽的事件類型。

返回值

  • 成功:返回 0。
  • 失敗:返回 -1,errno 指示錯誤類型。

epoll_wait()

作用

等待被監聽的文件描述符發生事件,并返回就緒的文件描述符。

函數原型

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

參數

  • epfd:epoll_create() 生成的 epoll 句柄。
  • events:用于存儲發生事件的文件描述符,用戶需提供足夠的空間。
  • maxevents:events 數組的大小,建議大于 0。
  • timeout:超時時間(毫秒):

????????0:立即返回(非阻塞)。

????????-1:永遠阻塞,直到有事件發生。????????

????????>0:等待指定時間。

返回值

  • 成功:返回就緒的文件描述符數量(nfds)。
  • 失敗:返回 -1,errno 指示錯誤類型。

epoll的原理?

epoll服務器實現的框架

socket套接編程的封裝

#pragma once#include <iostream>
#include <string>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "log.hpp"
#include "err.hpp"class Sock
{const static int backlog = 32;public:static int Socket(){// 1. 創建socket文件套接字對象int sock = socket(AF_INET, SOCK_STREAM, 0);if (sock < 0){logMessage(FATAL, "create socket error");exit(SOCKET_ERR);}logMessage(NORMAL, "create socket success: %d", sock);int opt = 1;setsockopt(sock, SOL_SOCKET, SO_REUSEADDR|SO_REUSEPORT, &opt, sizeof(opt));return sock;}static void Bind(int sock, int port){// 2. bind綁定自己的網絡信息struct sockaddr_in local;memset(&local, 0, sizeof(local));local.sin_family = AF_INET;local.sin_port = htons(port);local.sin_addr.s_addr = INADDR_ANY;if (bind(sock, (struct sockaddr *)&local, sizeof(local)) < 0){logMessage(FATAL, "bind socket error");exit(BIND_ERR);}logMessage(NORMAL, "bind socket success");}static void Listen(int sock){// 3. 設置socket 為監聽狀態if (listen(sock, backlog) < 0) // 第二個參數backlog后面在填這個坑{logMessage(FATAL, "listen socket error");exit(LISTEN_ERR);}logMessage(NORMAL, "listen socket success");}static int Accept(int listensock, std::string *clientip, uint16_t *clientport){struct sockaddr_in peer;socklen_t len = sizeof(peer);int sock = accept(listensock, (struct sockaddr *)&peer, &len);if (sock < 0)logMessage(ERROR, "accept error, next");else{logMessage(NORMAL, "accept a new link success, get new sock: %d", sock); // ?*clientip = inet_ntoa(peer.sin_addr);*clientport = ntohs(peer.sin_port);}return sock;}
};

epoll模型的封裝

epollServer.hpp

#include <iostream>
#include<unistd.h>
#include<sys/epoll.h>
#include<functional>
#include<string>
#include"sock.hpp"
#include"log.hpp"
using namespace std;using func_t = function<string(const string&)>;
#define SIZE 1024const static int defultvalue=-1;
const static int defultnum=64;
class EpollServer
{
public:EpollServer(int port,func_t func,int num=defultnum):_listen_sockfd(defultvalue),_port(port),_num(num),_revs(nullptr),_func(func){}void serverInit(){//1、創建套接字_listen_sockfd=Sock::Socket();//2、進行bind綁定Sock::Bind(_listen_sockfd,_port);//3、設置監聽狀態Sock::Listen(_listen_sockfd);//4、創建epoll模型//4、1 創建epoll模型實例_epfd=epoll_create(1);if(_epfd<0){logMessage(FATAL,"創建epoll實例失敗 :%s",strerror(errno));exit(EPOLL_CREATE_ERROR);}//4、2 管理epoll進行監控的文件描述符struct epoll_event events;events.events=EPOLLIN;events.data.fd=_listen_sockfd;epoll_ctl(_epfd,EPOLL_CTL_ADD,_listen_sockfd,&events);//5、進行開辟就是事件的空間_revs=new struct epoll_event[_num];logMessage(NORMAL,"進行epoll服務器初始化成功");}void serverStart(){int timeout=-1;for(;;){// 進行等待管理的文件描述符發生事件int n = epoll_wait(_epfd,_revs,_num,timeout);switch (n){case -1:logMessage(ERROR,"epoll_wait等待文件描述符發生事件失敗");break;case 0:logMessage(NORMAL,"outtime....");break;default://一定有事件進行就緒HandlerEven(n); //將有多少個就緒的事件進行傳入break;}}}void HandlerEven(int readyNum){for(int i=0;i<readyNum;i++){int sockfd=_revs[i].data.fd;uint32_t events=_revs[i].events;//處理監聽套接字---listen套接字就緒if(sockfd==_listen_sockfd && events&EPOLLIN){string clienip;uint16_t port;int fd=Sock::Accept(sockfd,&clienip,&port);if(fd<0){logMessage(FATAL,"accept 獲取鏈接失敗");continue;;}//建立連接成功,我們可以直接進行讀取嗎???????  不可以!!!!//交給epollstruct epoll_event ev;ev.events=EPOLLIN;ev.data.fd=fd;epoll_ctl(_epfd,EPOLL_CTL_ADD,fd,&ev);}//處理普通套接字---普通套接字就緒else if(events&EPOLLIN){//進行讀取客戶端的消息char buffer[SIZE];ssize_t n=recv(sockfd,buffer,sizeof(buffer)-1,0);if(n>0){buffer[n]=0;logMessage(NORMAL,"client#  %s",buffer);//通過回調方法進行處理客戶端的消息string resp=_func(buffer);//將處理過后的消息進行返回send(sockfd,resp.c_str(),resp.size(),0);}else if(n==0){epoll_ctl(_epfd,EPOLL_CTL_DEL,sockfd,nullptr);close(sockfd);logMessage(NORMAL,"客戶端退出");}else{//細節:epoll_ctl(_epfd,EPOLL_CTL_DEL,sockfd,nullptr);close(sockfd);logMessage(ERROR,"進行讀取客戶端消息失敗");}}else{}}}~EpollServer(){if(_listen_sockfd!=defultvalue){close(_listen_sockfd);}if(_epfd!=defultvalue){close(_epfd);}if(_revs){delete[] _revs;}}
private:int _listen_sockfd;int _port;int _epfd;struct epoll_event* _revs;int _num;func_t _func;
};

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

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

相關文章

下載一個JeecgBoot-master項目 導入idea需要什么操作啟動項目

官網&#xff1a;開發環境搭建 | JEECG 文檔中心 一般做開發的電腦里都是有的&#xff0c;沒有的只能下載了 前端安裝 node官網:https://nodejs.org/zh-cnpnpm安裝:通過命令 后端安裝: jdk17 :https://www.oracle.com/cn/java/technologies/downloads/#java17maven :https://m…

解決 InputStream 只能讀取一次問題

是的&#xff0c;InputStream 的一個重要特性是它通常只能被讀取一次。這是因為&#xff1a;輸入流通常是單向的、順序訪問的數據源很多流&#xff08;如網絡流、文件流&#xff09;讀取后指針就移動了&#xff0c;無法回退有些流&#xff08;如Socket流&#xff09;甚至讀取后…

數據分析面試題

技都測試 1、請列舉5個 Excel 中常用的函數及寫法。[ if ] IF(A1>60, "及格", "不及格") —— 若 A1 單元格數值≥60&#xff0c;返回 “及格”&#xff0c;否則返回 “不及格”。IF(B2>100, B2*0.8, B2) —— 若 B2 數值 > 100&#xff0c…

【07】VisionMaster入門到精通——Blob分折

文章目錄0 視屏講解與演示1 案例演示2 參數詳解1 運行參數0 視屏講解與演示 1 案例演示 周長使能查找U型槽 短軸使能查找U型槽 面積篩選區域 當條件不符合是&#xff0c;該模塊顯示紅色&#xff0c;狀態為NG 顯示二值圖像 顯示Blob圖像 2 參數詳解 Blob分折&#xff0c;…

解釋 MySQL 中的 EXPLAIN 命令的作用和使用場景

解釋 MySQL 中的 EXPLAIN 命令的作用和使用場景 總結性回答 EXPLAIN 是 MySQL 中用于分析 SQL 查詢執行計劃的命令&#xff0c;它能展示 MySQL 如何執行一個查詢&#xff0c;包括使用的索引、表連接順序、掃描行數等關鍵信息。主要用于查詢性能優化&#xff0c;幫助開發者識別潛…

.env 文件

.env 文件其實就是一個純文本文件&#xff0c;用來寫“環境變量”鍵值對&#xff0c;格式非常簡單 &#x1f447;? .env 文件寫法格式&#xff1a;每一行就是一個變量名 值&#xff0c;不要加引號&#xff0c;不要加空格DEEPSEEK_API_KEYsk-xxxxxxxxxxxxxxxxxxxx完整例子&…

機器學習——K 折交叉驗證(K-Fold Cross Validation),案例:邏輯回歸 交叉尋找最佳懲罰因子C

什么是交叉驗證&#xff1f; 交叉驗證是一種將原始數據集劃分為若干個子集&#xff0c;反復訓練和驗證模型的策略。 交叉驗證&#xff08;Cross-Validation&#xff09;適用于你在模型調參&#xff08;如邏輯回歸中的 C&#xff09; 最常用的&#xff1a;K 折交叉驗證&#…

藍橋杯----串口

&#xff08;五&#xff09;、串口1、串口通信簡介制定通信的規則&#xff0c;通信雙方按照協議規則進行數據收發&#xff0c;將一個設備的數據傳送到另一個設備&#xff0c;擴展硬件系統&#xff0c;串口USART有兩根通信線Tx、Rx&#xff0c;可同時雙向通信&#xff0c;稱之為…

錯誤: 找不到或無法加載主類 原因: java.lang.ClassNotFoundException

背景&#xff1a; 代碼沒有更改&#xff0c;主類位置也沒有移動&#xff0c;運行時突然報找不到或無法加載主類的錯誤 錯誤: 找不到或無法加載主類 原因: java.lang.ClassNotFoundException編譯器上方顯示 Java file is located outside of the module source root so it wont …

Lock 接口及實現類詳解:從 ReentrantLock 到并發場景實踐

在 Java 并發編程中&#xff0c;除了synchronized關鍵字&#xff0c;java.util.concurrent.locks.Lock接口及其實現類是另一種重要的同步機制。自 JDK 5 引入以來&#xff0c;Lock接口憑借靈活的 API 設計、可中斷的鎖獲取、公平性控制等特性&#xff0c;成為復雜并發場景的首選…

「iOS」————SideTable

iOS學習前言sideTableSlideTablesSideTableBufSideTable前言 我們在上一篇中&#xff0c;簡單的介紹了weak的實現原理。其中弱引用表就是存儲在SideTable中的&#xff0c;這里我們來學習了解一下SideTable sideTable sideTable主要用于存儲和管理對象的額外信息&#xff0c;…

【PHP】CURL請求第三方API接口

當我們需要調用第三方接口時&#xff0c;就需要使用CURL&#xff0c;通過CURL操作去請求第三方API接口&#xff0c;有的是通過POST方式&#xff0c;有的是通過GET方式&#xff0c;下面介紹一個通用的使用CURL調用API接口的方法。一、CURL操作共兩個方法&#xff0c;分別是CURL操…

對于考研數學的理解

文章目錄高等數學總結補充說明1. 微分方程與無窮級數的特殊性2. 隱藏的邏輯鏈條3. 向量代數的橋梁作用核心框架為什么這樣設計&#xff1f;結論線性代數核心邏輯框架各講之間的本質聯系1. 行列式 → 矩陣2. 矩陣 → 向量組3. 矩陣 向量組 → 線性方程組4. 矩陣 → 特征值與特征…

基于 Hadoop 生態圈的數據倉庫實踐 —— OLAP 與數據可視化(四)

目錄 四、數據可視化與 Hue 簡介 1. 數據可視化簡介 &#xff08;1&#xff09;數據可視化的重要性 &#xff08;2&#xff09;數據可視化的用途 &#xff08;3&#xff09;實施數據可視化需要考慮的問題 &#xff08;4&#xff09;幾種主要的數據可視化工具 2. Hue 簡介…

HarmonyOS 開發:基于 ArkUI 實現復雜表單驗證的最佳實踐

摘要 在現代應用開發中&#xff0c;表單是最常見的交互方式之一。不管是用戶注冊、信息錄入&#xff0c;還是登錄驗證&#xff0c;表單的可靠性直接影響用戶體驗。而在鴻蒙 ArkUI 開發中&#xff0c;雖然表單結構清晰&#xff0c;但要實現 復雜驗證&#xff08;比如&#xff1a…

高效游戲狀態管理:使用雙模式位運算與數學運算

在游戲開發中&#xff0c;狀態管理是一個核心問題。無論是任務系統、成就系統還是玩家進度跟蹤&#xff0c;我們都需要高效地存儲和查詢大量狀態。本文將深入分析一個創新的游戲狀態管理工具類 GameStateUtil&#xff0c;它巧妙結合了位運算和數學運算兩種模式&#xff0c;在存…

linux-process-control

Linux進程控制 1. 進程終止 1.1. 進程終止的本質是回收資源 1.1 釋放資源 內存資源&#xff1a; 釋放進程的地址空間&#xff08;mm_struct&#xff09;&#xff0c;包括代碼段、數據段、堆、棧等&#xff0c;通過寫時復制&#xff08;CoW&#xff09;共享的頁會減少引用計數&a…

Autoswagger:揭露隱藏 API 授權缺陷的開源工具

Autoswagger 是一款免費的開源工具&#xff0c;用于掃描 OpenAPI 文檔中列出的 API&#xff0c;查找授權漏洞。 即使在擁有成熟安全團隊的大型企業中&#xff0c;這類漏洞仍然很常見&#xff0c;而且尤其危險&#xff0c;因為即使技術水平不高的人也能利用它們。 Autoswagger…

Golang 語言 Channel 的使用方式

一、無緩存 channel無緩沖channel 可用于兩個goroutine 之間 傳遞信號&#xff0c;比如以下示例&#xff1a;順序打印1 至 100 的奇數和偶數&#xff1a;import ("fmt""time" )func main() {block : make(chan struct{})go odd(block)go even(block)time.S…

Element Plus常見基礎組件(一)

基礎組件 Button 按鈕 一、基礎用法 <el-button>默認按鈕</el-button> <el-button type"primary">主要按鈕</el-button>二、按鈕類型 (type) 類型說明示例代碼default默認按鈕<el-button>默認</el-button>primary主要按鈕&a…