一、SOCKET-IO復用技術
定義:SOCKET - IO
復用技術是一種高效處理多個套接字(socket
)的手段,能讓單個線程同時監聽多個文件描述符(如套接字)上的I/O
事件(像可讀、可寫、異常),進而提高程序的并發處理能力,避免為每個套接字創建一個線程或進程帶來的資源開銷(像一個管家)
是由函數select、poll和?Epoll?支持的
1.select、poll和?Epoll三者的區別:
select:輪回機制,存儲容器數組,固定大小
poll:輪回機制,存儲容器鏈表,動態擴展
epoll:事件驅動機制
2. 數據結構與擴展性
機制 | 存儲容器類型 | 最大連接數限制 | 動態擴展能力 |
---|---|---|---|
select | 固定大小的位掩碼數組 | 通常為 1024(FD_SETSIZE) | ? 無法擴展 |
poll | 動態鏈表(struct pollfd) | 無硬性限制(取決于系統資源) | ? 動態添加 |
epoll | 內核紅黑樹 + 就緒鏈表 | 無硬性限制(僅受內存約束) | ? 自動管理 |
- select:使用固定大小的?
fd_set
(位掩碼)存儲文件描述符,需手動管理位操作,擴展性差。 - poll:使用鏈表?
struct pollfd
?存儲文件描述符,可動態添加,突破了?select
?的限制。 - epoll:使用內核紅黑樹高效管理所有待監控的文件描述符,自動擴容。
3. 工作機制
機制 | 事件觸發方式 | 輪詢方式 | 性能特性 |
---|---|---|---|
select | 水平觸發(Level Triggered) | 遍歷所有文件描述符 | O (n) 時間復雜度 |
poll | 水平觸發 | 遍歷所有文件描述符 | O (n) 時間復雜度 |
epoll | 邊緣觸發(Edge Triggered)或水平觸發 | 僅遍歷就緒鏈表 | O (1) 時間復雜度 |
- 水平觸發(LT):只要文件描述符就緒(如可讀),就會持續通知。
- 邊緣觸發(ET):僅在文件描述符狀態變化(如從不可讀到可讀)時通知一次,需立即處理,否則數據可能丟失。
4. 性能對比
場景 | select/poll 表現 | epoll 表現 |
---|---|---|
連接數少且活躍 | 效率較高 | 優勢不明顯 |
連接數多但不活躍 | 性能急劇下降(輪詢所有連接) | 性能穩定(僅處理就緒連接) |
大量并發連接 | 不適用(受 FD_SETSIZE 限制) | 非常高效 |
二、Epoll函數
基于以上三種方法對比,所以我們選用epoll進行使用較為合適
epoll兩種模式的區別:
LT邏輯簡單,但效率低,ET反之
核心數據結構是:1個紅黑樹和1個鏈表
1.創建
int epoll_create(int size);
參數:size表明內核要監聽的描述符數量。調用成功時返回一個epoll句柄描述符,失敗時返回-1
2.注冊要監聽的事件類型
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
- 參數:用于控制 epoll 實例對文件描述符的監聽。
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
以指示錯誤原因。
3.等待事件的就緒
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
- 功能:等待 epoll 實例所監聽的文件描述符上有事件發生。
epfd
是 epoll 實例的文件描述符。events
是一個struct epoll_event
類型的數組,用于存儲發生事件的文件描述符及其相關事件信息。maxevents
指定了events
數組的大小,即最多能返回的事件數量。timeout
指定等待的超時時間,以毫秒為單位。如果設置為 -1,則表示無限期等待,直到有事件發生;如果設置為 0,則表示立即返回,不進行等待。
- 返回值:成功時返回發生事件的文件描述符數量;如果超時則返回 0;失敗時返回 -1,并設置
errno
以指示錯誤原因。
#include "epollServer.h"epollServer::epollServer(int port)
{this->server = new TCPServer(port);init_epoll();
}void epollServer::init_epoll()
{// 創建epollepoll_fd = epoll_create(10);if (epoll_fd < 0) {perror("epoll_create error");return;}// 添加epoll關注事件//struct epoll_event epoll_event;epoll_event.data.fd = this->server->getServerfd();epoll_event.events = EPOLLIN;epoll_ctl(epoll_fd, EPOLL_CTL_ADD, this->server->getServerfd(), &epoll_event);cout << "epoll初始化完成..." << endl;
}void epollServer::start()
{struct epoll_event event_array[10] = { 0 };int event_num = 0;this->thread_pool = new ThreadPool2(5);// 主循環while (1) {cout << "epoll wait..." << endl;event_num = epoll_wait(epoll_fd, event_array, 10, -1);// cout<<event_arrayif (event_num < 0) {perror("epoll_wait error");continue;}for (int i = 0; i < event_num; i++) {if (event_array[i].data.fd == this->server->getServerfd()) {//處理連接請求cout << "有新客戶端連接請求" << endl;int client_fd = accept(this->server->getServerfd(), NULL, NULL);if (client_fd < 0) {continue;}// 將新的客戶端連接添加到epoll關注列表epoll_event.data.fd = client_fd;epoll_event.events = EPOLLIN;epoll_ctl(epoll_fd, EPOLL_CTL_ADD, client_fd, &epoll_event);}else if (event_array[i].events & EPOLLIN) {//處理請求監聽的事件}}}
}