【計算機網絡】非阻塞IO——poll實現多路轉接

🔥個人主頁🔥:孤寂大仙V
🌈收錄專欄🌈:計算機網絡
🌹往期回顧🌹:【計算機網絡】非阻塞IO——select實現多路轉接
🔖流水不爭,爭的是滔滔不息


一、poll實現多路轉接

在網絡編程或多路 IO 編程中,我們經常需要同時監聽多個文件描述符(fd),比如多個客戶端的 socket 連接。這時,poll 就登場了。
poll 是 Linux 提供的一種 IO 多路復用機制,用于監聽多個 fd 上的讀寫等事件,一旦有就緒,立刻通知我們處理。

poll的參數

int poll(struct pollfd fds[], nfds_t nfds, int timeout);
  1. 參數struct pollfd fds[]是關心的文件描述符數組,每一個元素代表一個要監聽的 fd 及其感興趣的事件和返回的事件。
struct pollfd {int fd;         // 要監聽的文件描述符short events;   // 感興趣的事件(由你設置)short revents;  // 實際返回的事件(由內核設置)
};
  1. 參數 nfds_t nfds這個是 fds[] 數組里有效元素的數量,簡單說就是你監聽幾個文件描述符就寫幾。
  2. int timeout
    單位是 毫秒(ms),表示阻塞多久。timeout > 0:等待指定毫秒數后返回、timeout == 0:立即返回(非阻塞)、timeout < 0:永遠阻塞,直到有事件發生。

函數返回值

  • 0:就緒的文件描述符數量
  • 0:超時,沒有任何事件發生
  • <0:出錯(比如信號中斷)

常用事件(events/revents)取值

POLLIN     // 有數據可讀
POLLOUT    // 可以寫數據而不會阻塞
POLLERR    // 錯誤(由 revents 設置)
POLLHUP    // 對端關閉連接
POLLNVAL   // 描述符非法

poll的缺點

  1. 每次調用都要傳入整個 fd 列表
    poll 的 pollfd 是個數組,內核不會保存狀態,每次都得重新傳。
    如果你有上千個連接,那每次 poll() 都會把這上千個 fd 重新復制到內核 → 代價大。
  2. 線性掃描效率低
    poll 返回的是“多少個 fd 就緒”,但你要線性掃描整個數組找出來。
    比如你監聽 1000 個連接,但只有 1 個可讀,你得從 0 掃到 999 才找到它。
  3. fd 數量仍有限制
    雖然 poll 相比 select 不再限制 1024 個,但:
    它還是受限于內核最大文件描述符數量(ulimit -n,比如 65535)
    每個 pollfd 占用空間較大,更不適合極大并發
  4. 無事件通知機制
    poll 只能“等和掃”,沒有像 epoll 的 EPOLLONESHOT、EPOLLET(邊緣觸發)那種高級機制。
    沒法在事件處理完后說“我暫時不關注這個 fd 了”。

二、poll實現非阻塞服務器

#pragma once
#include "Common.hpp"
#include "Log.hpp"
#include "Socket.hpp"
#include <sys/poll.h>using namespace std;
using namespace LogModule;
using namespace SocketModule;class PollServer
{const static int size = 4096;const static int defaultfd = -1;public:PollServer(int port): _isrunning(false), _listensockfd(make_unique<TcpSocket>()){_listensockfd->BuildTcpSocketServer(port); // 構造TCP服務器for (int i = 0; i < size; i++){_fds[i].fd = defaultfd;_fds[i].events = 0;_fds[i].revents = 0;}_fds[0].fd = _listensockfd->FD();_fds[0].events = POLLIN;}void Start() // 服務器啟動{int timeout = 1000; // 1000毫秒_isrunning = true;while (true){int n = poll(_fds, size, timeout); // 多路轉接只關系讀事件switch (n){case -1:LOG(LogLevel::ERROR) << "poll error"; // 異常break;case 0:LOG(LogLevel::WARNING) << "poll timeout"; // 超時default:LOG(LogLevel::INFO) << "事件就緒"; // 讀事件就緒Dispatcher();                      // 派發break;}}}void Dispatcher() // 事件派發{for (int i = 0; i < size; i++){if (_fds[i].fd == defaultfd) // 跳過continue;if (_fds[i].revents & POLLIN) // 是讀就緒{if (_fds[i].fd == _listensockfd->FD()){// listen 套接字Accept();}else{// 普通  套接字Recv(i);}}}}void Accept(){InetAddr client;int sockfd = _listensockfd->AcceptOrDie(&client);LOG(LogLevel::DEBUG) << "accept a new client" << client.StringAddr();int pos = 0;for (; pos < size; pos++){if (_fds[pos].fd == defaultfd)break; // 數組中找到空位}if (pos == size){LOG(LogLevel::WARNING) << "poll server full";close(sockfd);}else{_fds[pos].fd = sockfd;_fds[pos].events = POLLIN;_fds[pos].revents = 0;}}void Recv(int pos) // 讀數據{char buffer[1024];ssize_t n = recv(_fds[pos].fd, buffer, sizeof(buffer) - 1, 0); // 收信息if (n > 0){buffer[n] = 0;cout << "client say@ " << buffer << endl;}else if (n == 0) // 客戶端退出{LOG(LogLevel::INFO) << "client quit";_fds[pos].fd = defaultfd;_fds[pos].events = 0;_fds[pos].revents = 0;close(_fds[pos].fd);}else // 出現錯誤 異常{LOG(LogLevel::FATAL) << "recv error";_fds[pos].fd = defaultfd;_fds[pos].events = 0;_fds[pos].revents = 0;close(_fds[pos].fd);}}~PollServer(){}private:unique_ptr<Socket> _listensockfd;bool _isrunning;struct pollfd _fds[size];
};

構造

//私有成員變量
private:unique_ptr<Socket> _listensockfd;bool _isrunning;struct pollfd _fds[size];
//構造PollServer(int port): _isrunning(false), _listensockfd(make_unique<TcpSocket>()){_listensockfd->BuildTcpSocketServer(port); // 構造TCP服務器for (int i = 0; i < size; i++){_fds[i].fd = defaultfd;_fds[i].events = 0;_fds[i].revents = 0;}_fds[0].fd = _listensockfd->FD();_fds[0].events = POLLIN;}

私有成員變量中,把關心文件描述符的數組開好,構造服務器的時候遍歷這個數組,把要監聽的文件描述符設置進去,把要關系的狀態設置為關系讀事件。


服務器啟動

void Start() // 服務器啟動{int timeout = 1000; // 1000毫秒_isrunning = true;while (true){int n = poll(_fds, size, timeout); // 多路轉接只關系讀事件switch (n){case -1:LOG(LogLevel::ERROR) << "poll error"; // 異常break;case 0:LOG(LogLevel::WARNING) << "poll timeout"; // 超時default:LOG(LogLevel::INFO) << "事件就緒"; // 讀事件就緒Dispatcher();                      // 派發break;}}}

用poll函數,進行多路轉接,事件就緒就派發任務。


事件派發

void Dispatcher() // 事件派發{for (int i = 0; i < size; i++){if (_fds[i].fd == defaultfd) // 跳過continue;if (_fds[i].revents & POLLIN) // 是讀就緒{if (_fds[i].fd == _listensockfd->FD()){// listen 套接字Accept();}else{// 普通  套接字Recv(i);}}}}

對文件描述符數組,進行遍歷。如果不是合法位置(沒放文件描述符)就跳過這個位置。如果是讀就緒然后判斷是監聽套接字合適普通套接字。

收到客戶端的連接accept

void Accept(){InetAddr client;int sockfd = _listensockfd->AcceptOrDie(&client);LOG(LogLevel::DEBUG) << "accept a new client" << client.StringAddr();int pos = 0;for (; pos < size; pos++){if (_fds[pos].fd == defaultfd)break; // 數組中找到空位}if (pos == size){LOG(LogLevel::WARNING) << "poll server full";close(sockfd);}else{_fds[pos].fd = sockfd;_fds[pos].events = POLLIN;_fds[pos].revents = 0;}}

主要是收到客戶端的連接,創建了accept的套接字,要把這個套接字放到數組中。在文件描述符數組中找到空位,把這個acceptfd設置進去。


讀數據

void Recv(int pos) // 讀數據{char buffer[1024];ssize_t n = recv(_fds[pos].fd, buffer, sizeof(buffer) - 1, 0); // 收信息if (n > 0){buffer[n] = 0;cout << "client say@ " << buffer << endl;}else if (n == 0) // 客戶端退出{LOG(LogLevel::INFO) << "client quit";_fds[pos].fd = defaultfd;_fds[pos].events = 0;_fds[pos].revents = 0;close(_fds[pos].fd);}else // 出現錯誤 異常{LOG(LogLevel::FATAL) << "recv error";_fds[pos].fd = defaultfd;_fds[pos].events = 0;_fds[pos].revents = 0;close(_fds[pos].fd);}

這時候,讀數據已經是非阻塞的了,客戶端退出和異常要把文件描述符數組的內容清空,然后關閉文件描述符。


在這里插入圖片描述
源碼:poll多路轉接

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

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

相關文章

vscode使用系列之快速生成html模板

一.歡迎來到我的酒館 vscode&#xff0c;yyds! 目錄 一.歡迎來到我的酒館二.vscode下載安裝1.關于vscode你需要知道2.開始下載安裝 三.vscode快速創建html模板 二.vscode下載安裝 1.關于vscode你需要知道 Q&#xff1a;為什么使用vscode? A&#xff1a;使用vscode寫…

【C/C++】不同防止頭文件重復包含的措施

文章目錄 #pragma once vs #ifndef 文件宏1 原理層面區別&#xff08;core&#xff09;2 關鍵區別與優缺點分析3 總結與最佳實踐 #pragma once vs #ifndef 文件宏 在 C/C 中&#xff0c;#pragma once 和傳統的文件宏守衛 (#ifndef HEADER_H #define HEADER_H ... #endif) 都用…

java-springboot文件上傳校驗之只允許上傳excel文件,且檢查不能是腳本或者有害文件或可行性文件

四重驗證機制&#xff1a; 文件擴展名檢查&#xff08;.xlsx/.xls&#xff09;MIME類型檢查文件魔數驗證&#xff08;真實文件類型&#xff09;可執行文件特征檢測 防御措施&#xff1a; 使用try-with-resources確保流關閉限制文件大小防止DoS攻擊使用Apache POI的FileMagic進…

不確定性分析在LEAP能源-環境系統建模中的整合與應用

本內容突出與實例結合&#xff0c;緊密結合國家能源統計制度及《省級溫室氣體排放編制指南》&#xff0c;深入淺出地介紹針對不同級別研究對象時如何根據數據結構、可獲取性、研究目的&#xff0c;構建合適的能源生產、轉換、消費、溫室氣體排放&#xff08;以碳排放為主&#…

高性能分布式消息隊列系統(四)

八、客戶端模塊的實現 客戶端實現的總體框架 在 RabbitMQ 中&#xff0c;應用層提供消息服務的核心實體是 信道&#xff08;Channel&#xff09;。 用戶想要與消息隊列服務器交互時&#xff0c;通常不會直接操作底層的 TCP 連接&#xff0c;而是通過信道來進行各種消息的發布…

QPair 類說明

QPair 類說明 QPair 是一個模板類&#xff0c;用于存儲一對數據項。 頭文件&#xff1a; cpp #include <QPair> qmake 配置&#xff1a; QT core 所有成員列表&#xff08;包括繼承成員&#xff09; 公共類型 類型定義說明first_type第一個元素的類型&#xff…

4.大語言模型預備數學知識

大語言模型預備數學知識 復習一下在大語言模型中用到的矩陣和向量的運算&#xff0c;及概率統計和神經網絡中常用概念。 矩陣的運算 矩陣 矩陣加減法 條件&#xff1a;行數列數相同的矩陣才能做矩陣加減法 數值與矩陣的乘除法 矩陣乘法 條件&#xff1a;矩陣A的列數 矩陣…

uniapp 設置手機不息屏

在使用 UniApp 開發應用時&#xff0c;有時需要在設備長時間未操作時實現息屏保護功能&#xff0c;以節省電量和保護屏幕。以下是如何在 UniApp 中實現這一功能的步驟。 示例一 // 保持屏幕常亮 uni.setKeepScreenOn({keepScreenOn: true });// 監聽應用進入后臺事件 uni.onH…

智能推薦系統:協同過濾與深度學習結合

智能推薦系統&#xff1a;協同過濾與深度學習結合 系統化學習人工智能網站&#xff08;收藏&#xff09;&#xff1a;https://www.captainbed.cn/flu 文章目錄 智能推薦系統&#xff1a;協同過濾與深度學習結合摘要引言技術原理對比1. 協同過濾算法&#xff1a;基于相似性的推…

使用Python和OpenCV實現圖像識別與目標檢測

在計算機視覺領域&#xff0c;圖像識別和目標檢測是兩個非常重要的任務。圖像識別是指識別圖像中的內容&#xff0c;例如判斷一張圖片中是否包含某個特定物體&#xff1b;目標檢測則是在圖像中定位并識別多個物體的位置和類別。OpenCV是一個功能強大的開源計算機視覺庫&#xf…

《基于Apache Flink的流處理》筆記

思維導圖 1-3 章 4-7章 8-11 章 參考資料 源碼&#xff1a; https://github.com/streaming-with-flink 博客 https://flink.apache.org/bloghttps://www.ververica.com/blog 聚會及會議 https://flink-forward.orghttps://www.meetup.com/topics/apache-flink https://n…

LLaMA-Factory 微調 Qwen2-VL 進行人臉情感識別(二)

在上一篇文章中,我們詳細介紹了如何使用LLaMA-Factory框架對Qwen2-VL大模型進行微調,以實現人臉情感識別的功能。本篇文章將聚焦于微調完成后,如何調用這個模型進行人臉情感識別的具體代碼實現,包括詳細的步驟和注釋。 模型調用步驟 環境準備:確保安裝了必要的Python庫。…

Splash動態渲染技術全解析:從基礎到企業級應用(2025最新版)

引言 在Web 3.0時代&#xff0c;87%的網站采用JavaScript動態渲染技術。傳統爬蟲難以應對Ajax加載、SPA應用等場景&#xff0c;Splash作為專業的JavaScript渲染服務&#xff0c;憑借??Lua腳本控制??和??異步處理能力??&#xff0c;已成為動態數據抓取的核心工具。本文…

【應用】Ghost Dance:利用慣性動捕構建虛擬舞伴

Ghost Dance是葡萄牙大學的一個研究項目&#xff0c;研究方向是探索人與人之間的聯系&#xff0c;以及如何通過虛擬舞伴重現這種聯系。項目負責人Cecilia和Rui利用慣性動捕創造出具有流暢動作的虛擬舞伴&#xff0c;讓現實中的舞者也能與之共舞。 挑戰&#xff1a;Ghost Danc…

廣目軟件GM DC Monitor

廣目&#xff08;北京&#xff09;軟件有限公司成立于2024年&#xff0c;技術和研發團隊均來自于一家具有近10年監控系統研發的企業。廣目的技術團隊一共實施了9家政府單位、1家股份制銀行、1家芯片制造企業的數據中心監控預警項目。這11家政企單位由2家正部級、1家副部級、6家…

12-Oracle 23ai Vector 使用ONNX模型生成向量嵌入

一、Oracle 23ai Vector Embeddings 核心概念? 向量嵌入&#xff08;Vector Embeddings&#xff09;?? -- 將非結構化數據&#xff08;文本/圖像&#xff09;轉換為數值向量 - - 捕獲數據的語義含義而非原始內容 - 示例&#xff1a;"數據庫" → [0.24, -0.78, 0.5…

用 NGINX 構建高效 POP3 代理`ngx_mail_pop3_module`

一、模塊定位與作用 協議代理 ngx_mail_pop3_module 讓 NGINX 能夠充當 POP3 代理&#xff1a;客戶端與后端 POP3 服務器之間的所有請求均轉發到 NGINX&#xff0c;由 NGINX 負責與后端會話邏輯。認證方式控制 通過 pop3_auth 指令指定允許客戶端使用的 POP3 認證方法&#xf…

每日算法 -【Swift 算法】三數之和

Swift&#xff5c;三數之和&#xff08;3Sum&#xff09;詳細題解 注釋 拓展&#xff08;LeetCode 15&#xff09; ?題目描述 給你一個包含 n 個整數的數組 nums&#xff0c;判斷 nums 中是否存在三個元素 a, b, c&#xff0c;使得 a b c 0。請你找出所有和為 0 且不重…

服務器磁盤空間被Docker容器日志占滿處理方法

事發場景&#xff1a; 原本正常的服務停止運行了&#xff0c;查看時MQTT服務鏈接失敗&#xff0c;查看對應的容器服務發現是EMQX鏡像停止運行了&#xff0c;重啟也是也報錯無法正常運行&#xff0c;報錯如下圖&#xff1a; 報錯日志中連續出現兩個"no space left on devi…

令牌桶 滑動窗口->限流 分布式信號量->限并發的原理 lua腳本分析介紹

文章目錄 前言限流限制并發的實際理解限流令牌桶代碼實現結果分析令牌桶lua的模擬實現原理總結&#xff1a; 滑動窗口代碼實現結果分析lua腳本原理解析 限并發分布式信號量代碼實現結果分析lua腳本實現原理 雙注解去實現限流 并發結果分析&#xff1a; 實際業務去理解體會統一注…