5.9-selcct_poll_epoll 和 reactor 的模擬實現

5.9-select_poll_epoll

本文演示 select 等 io 多路復用函數的應用方法,函數具體介紹可以參考我過去寫的博客。

先綁定監聽的文件描述符

int sockfd = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in serveraddr;
memset(&serveraddr, 0, sizeof(struct sockaddr_in));serveraddr.sin_family = AF_INET;
serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);
serveraddr.sin_port = htons(2052);if (-1 == bind(sockfd, (struct sockaddr*)&serveraddr, sizeof(struct sockaddr)))
{perror("bind");return -1;
}listen(sockfd, 10);

1. select

select 函數適用于 win/linux 平臺,但是使用時每次都需要檢查位圖內所有客戶端的狀態變化情況,屬于針對于每一個文件進行處理而非針對事件處理,效率較低。打個比方:如果客戶端是小區內的住戶,那么 selcet 作為快遞員,會從快遞倉庫中挑選出要被配送到該小區指定住戶的快遞,并對于每一個住戶是否有快遞/寄快遞。

演示如下:

fd_set rfds, rset;FD_ZERO(&rfds);
FD_SET(sockfd, &rfds);int maxfd = sockfd;
while (1)
{rset = rfds;int nready = select(maxfd + 1, &rset, NULL, NULL, NULL);if (FD_ISSET(sockfd, &rset)){struct sockaddr_in clientaddr;socklen_t len = sizeof(struct sockaddr_in);int clientfd = accept(sockfd, (struct sockaddr*)&clientaddr, &len);printf("sockfd: %d\n", clientfd);FD_SET(clientfd, &rfds);maxfd = clientfd;}int i = 0;for (i = sockfd + 1; i <= maxfd; i++){if (FD_ISSET(i, &rset)){char buffer[128] = { 0 };int count = recv(i, buffer, 128, 0);if (0 == count){printf("disconnect\n");close(i);FD_CLR(i, &rfds);break;}send(i, buffer, count, 0);printf("clientfd: %d, count: %d, buffer: %s\n", i, count, buffer);}}
}

2. poll

poll 函數適用于 Linux 平臺,效率相比于 select 有所提升。如果 selcet 是快遞員要對于每一個住戶確定一次需求,poll 則是可以直接鎖定不同客戶的需求。

演示如下:


struct pollfd fds[1024] = { 0 };fds[sockfd].fd = sockfd;
fds[sockfd].events = POLLIN;int maxfd = sockfd;while (1)
{int nready = poll(fds, maxfd + 1, -1);if (fds[sockfd].revents & POLLIN){struct  sockaddr_in clientaddr;int len = sizeof(struct sockaddr_in);int clientfd = accept(sockfd, (struct sockaddr*)&clientaddr, &len);printf("hello, %d\n", clientfd);fds[clientfd].fd = clientfd;fds[clientfd].events = POLLIN;maxfd = clientfd;}int i = 0;for (i = sockfd + 1; i <= maxfd; i++){if (fds[i].revents & POLLIN){char buffer[128] = { 0 };int count = recv(fds[i].fd, buffer, 128, 0);if (count == 0){printf("disconnect\n");close(i);fds[i].fd = -1;fds[i].events = 0;continue;}send(i, buffer, count, 0);printf("clientfd: %d, sount: %d, lbuffer: %s\n", i, count, buffer);}}
}

3.1 epoll

在支持 Linux 的函數中,epoll 是最高效的。還是上面的比方,epoll 則是在一定程度上結合了前兩者的優點,并且底層使用紅黑樹,查找速度更快。

演示如下:

int epfd = epoll_create(1);struct epoll_event ev;
ev.events = EPOLLIN;
ev.data.fd = sockfd;epoll_ctl(epfd, EPOLL_CTL_ADD, sockfd, &ev);struct epoll_event events[1024] = { 0 };while (1)
{int nready = epoll_wait(epfd, events, 1024, -1);int i = 0;for (i = 0; i < nready; i++){int curfd = events[i].data.fd;if (sockfd == curfd){struct sockaddr_in clientaddr;socklen_t len = sizeof(struct sockaddr_in);int clientfd = accept(sockfd, (struct sockaddr*)&clientaddr, &len);ev.events = EPOLLIN;ev.data.fd = clientfd;epoll_ctl(epfd, EPOLL_CTL_ADD, clientfd, &ev);printf("clientfd: %d\n", clientfd);}else if (events[i].events & EPOLLIN){char buffer[10] = { 0 }; // 只要有數據就會一直觸發,因此會回復多次int count = recv(curfd, buffer, 10, 0);if (count == 0){printf("disconnect\n");close(curfd);epoll_ctl(epfd, EPOLL_CTL_DEL, curfd, NULL);continue;}send(curfd, buffer, count, 0);printf("clientfd: %d, sount: %d, buffer: %s\n", curfd, count, buffer);}}}

3.2 基于 epoll 模擬實現面對事件的 reactor 的底層原理

定義結構體,為了方便,直接把 epfd 定位全局變量

#define BUFFER_LENGTH	1024typedef int (* RCALLBACK)(int fd);// save buffer data
struct conn_item
{int fd;char rbuffer[BUFFER_LENGTH];int rlen;char wbuffer[BUFFER_LENGTH];int wlen;union // 聯合,在后續代碼中會用到{RCALLBACK accept_callback;RCALLBACK recv_callback;}recv_t;RCALLBACK send_callback;
};struct conn_item connlist[1024];#if 1
int epfd;
#elif
struct reactor
{int epfd;struct conn_item connlist[1024];
};
#endif

reactor 的模擬借助以下回調函數實現,可以簡化代碼。

int set_cb(int fd, int event, int flag);
int accept_cb(int fd);
int recv_cb(int fd);
int send_cb(int fd);

具體實現如下,體現出面對事件的處理思想。

/*
1. listenfd 觸發 EPOLLIN 事件 -> 執行 accept_cb
2. client 觸發 EPOLLIN 事件 -> recv_cb
3. client 觸發 EPOLLOUT 事件 -> send_cb
*/// ADD: flag == 1 else 0
int set_cb(int fd, int event, int flag)
{if (flag){struct epoll_event ev;ev.events = event;ev.data.fd = fd;epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &ev);}else{struct epoll_event ev;ev.events = event;ev.data.fd = fd;epoll_ctl(epfd, EPOLL_CTL_MOD, fd, &ev);}
}int accept_cb(int fd)
{struct sockaddr_in clientaddr;socklen_t len = sizeof(struct sockaddr_in);int clientfd = accept(fd, (struct sockaddr*)&clientaddr, &len);set_cb(clientfd, EPOLLIN, 1);// set connlistconnlist[clientfd].fd = clientfd;memset(connlist[clientfd].wbuffer, 0, BUFFER_LENGTH);connlist[clientfd].wlen = 0;memset(connlist[clientfd].rbuffer, 0, BUFFER_LENGTH);connlist[clientfd].rlen = 0;// set callbackconnlist[clientfd].recv_t.recv_callback = recv_cb;connlist[clientfd].send_callback = send_cb;printf("clientfd: %d\n", clientfd);return clientfd;
}int recv_cb(int fd)
{char * buffer = connlist[fd].rbuffer;int index = connlist[fd].rlen;int count = recv(fd, buffer + index, BUFFER_LENGTH - index, 0);if (count == 0){printf("disconnect\n");epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL);close(fd);return -1;}connlist[fd].rlen += count;#if ENABLE_HTTP_RESPONSE// 此處可以自行修改,使對應不同輸入實現特定輸出,如 http 相應:
http_response(&connlist[fd]);#else// 不做處理直接發送返回
memcpy(connlist[fd].wbuffer, connlist[fd].rbuffer, connlist[fd].rlen);
connlist[fd].wlen = connlist[fd].rlen;#endif// eventset_cb(fd, EPOLLOUT, 0);return count;
}int send_cb(int fd)
{char * buffer = connlist[fd].wbuffer;int index = connlist[fd].wlen;int count = send(fd, buffer, index, 0);// 事件來一次執行一次set_cb(fd, EPOLLIN, 0);return count;
}

mainloop 部分

int main()
{int sockfd = socket(AF_INET, SOCK_STREAM, 0);struct sockaddr_in serveraddr;memset(&serveraddr, 0, sizeof(struct sockaddr_in));serveraddr.sin_family = AF_INET;serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);serveraddr.sin_port = htons(2048);if (-1 == bind(sockfd, (struct sockaddr*)&serveraddr, sizeof(struct sockaddr))){perror("bind");return -1;}listen(sockfd, 10);connlist[sockfd].fd = sockfd;connlist[sockfd].recv_t.accept_callback = accept_cb;// epoll 邊緣觸發/*對于 IO 處理:隨著時間增多會越來越復雜if(listenfd){...}else // clientfd{...}對于事件處理 -> reactor 對事件反應更緩和if (events & EPOLLIN){...}else if (events & EPOLLOUT){...}*/epfd = epoll_create(1); // int sizeset_cb(sockfd, EPOLLIN, 1);struct epoll_event events[1024] = { 0 };while (1) // main loop{int nready = epoll_wait(epfd, events, 1024, -1);int i = 0;for (i = 0; i < nready; i++){int curfd = events[i].data.fd;// 由于結構體內 accept_callback() 與 recv_callback() 共享同一塊內存,故此處 if 條件判斷可以省略。// if (sockfd == curfd)// {// 	// accept_cb()// 	// int clientfd = accept_cb(sockfd);// 	int clientfd = connlist[sockfd].recv_t.accept_callback(sockfd);// 	printf("client: %d\n", clientfd);// }if (events[i].events & EPOLLIN){// int count = recv_cb(curfd);int count = connlist[curfd].recv_t.recv_callback(curfd);if (count != -1)printf("recv <- clientfd: %d, count: %d, buffer: %s\n", curfd, count, connlist[curfd].rbuffer);}else if (events[i].events & EPOLLOUT){// int count = send_cb(curfd);int count = connlist[curfd].send_callback(curfd);printf("send -> clientfd: %d, count: %d, buffer: %s\n", curfd, count, connlist[curfd].wbuffer);}}}return 0;
}

附:本文的 http_response() 函數定義,以供測試使用。

#include <time.h>typedef struct conn_item connection_t;int http_response(connection_t *conn)
{const char *html_body = "<html><head><title>chipen.com</title></head><body><h1>chipen</h1></body></html>";int content_length = strlen(html_body);// 生成符合 HTTP 標準格式的 Date 字符串time_t now = time(NULL);struct tm *gmt = gmtime(&now);char date_str[128];strftime(date_str, sizeof(date_str), "Date: %a, %d %b %Y %H:%M:%S GMT\r\n", gmt);// 構建完整的 HTTP 響應conn->wlen = snprintf(conn->wbuffer, BUFFER_LENGTH,"HTTP/1.1 200 OK\r\n""Content-Type: text/html\r\n""Content-Length: %d\r\n""Accept-Ranges: bytes\r\n""%s""\r\n"  // Header 與 Body 的分隔符"%s",   // HTML 內容content_length,date_str,html_body);return conn->wlen;
}

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

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

相關文章

Python基礎語法(上)

常量和表達式 我們可以使用python來進行一些 - * / %的一些運算 print(1 2 - 3) print(1 2 * 3) print(1 2 / 3) python中的運算規則與數學當中一致&#xff0c;先乘除后加減&#xff0c;有括號的先計算括號里面的。 形如 1 2 - 3 這樣是算式, 在編程語言中稱為 表達式,…

數圖閃耀2025深圳CCFA中國零售博覽會:AI+零售數字化解決方案引發現場熱潮

展會時間&#xff1a;2025年5月8日—10日 地點&#xff1a;深圳國際會展中心&#xff08;寶安新館&#xff09; 【深圳訊】5月8日&#xff0c;亞洲規模最大的零售行業盛會——2025 CCFA中國零售博覽會在深圳盛大開幕。本屆展會匯聚全球25個國家和地區的900余家參展商&#xff…

方法:批量識別圖片區域文字并重命名,批量識別指定區域內容改名,基于QT和阿里云的實現方案,詳細方法

基于QT和阿里云的圖片區域文字識別與批量重命名方案 項目場景 ??企業檔案管理??:批量處理掃描合同、發票等文檔,根據編號或關鍵信息自動重命名文件??醫療影像管理??:識別X光、CT等醫學影像中的患者信息,按姓名+檢查日期重命名??電商訂單處理??:從訂單截圖中提…

Mybatis解決以某個字段存在,批量更新,不存在批量插入(高效)(一)

背景 在開發企業級應用時&#xff0c;我們經常需要處理批量數據的插入和更新操作。傳統的逐條處理方式性能低下&#xff0c;而簡單的REPLACE INTO或INSERT ... ON DUPLICATE KEY UPDATE在某些場景下又不夠靈活。本文將介紹一種基于臨時表的高效批量插入/更新方案&#xff0c;解…

JVM、JRE、JDK的區別

JVM JVM全稱Java虛擬機(Java Virtual Machine, JVM),它是運行java字節碼的虛擬機&#xff0c;JVM針對不同的系統有不同的實現&#xff0c;目的運行相同的字節碼有同樣的結果&#xff0c;JVM是“一次編譯&#xff0c;到處運行”實現的關鍵。如下不同的編程語言編譯生成字節碼文…

神經元和神經網絡定義

在深度學習中&#xff0c;神經元和神經網絡是構成神經網絡模型的基本元素。讓我們從基礎開始&#xff0c;逐步解釋它們的含義和作用。 1?? 神經元是什么&#xff1f; 神經元是神經網絡中的基本計算單元&#xff0c;靈感來自于生物神經系統中的神經元。每個人的腦中有數以億…

CDGP重點知識梳理

寫在前面 全文11700字,共82個重點知識 目 錄 考點分布 考試要求 第一章 數據管理-5%

Ubuntu 22.04 安裝配置遠程桌面環境指南

在云服務器或遠程主機上安裝圖形化桌面環境,可以極大地提升管理效率和用戶體驗。本文將詳細介紹如何在 Ubuntu 22.04 (Jammy Jellyfish) 系統上安裝和配置 Xfce4 桌面環境,并通過 VNC 實現遠程訪問。 系統環境 操作系統:Ubuntu 22.04 LTS (Jammy Jellyfish)架構:AMD64安裝…

node提示node:events:495 throw er解決方法

前言 之前開發的時候喜歡使用高版本&#xff0c;追求新的東西&#xff0c;然后回頭運行一下之前的項目提示如下 項目技術棧&#xff1a;node egg 報錯 node:events:495 throw er; // Unhandled error event ^ Error: ENOENT: no such file or directory, scandir F:\my\gi…

【軟件設計師:軟件工程】9.軟件開發模型與方法

一、軟件危機與軟件工程 軟件危機與軟件工程是計算機科學發展中密切相關的兩個概念。 1.軟件危機(Software Crisis) 背景:20世紀60年代至80年代,隨著計算機硬件性能提升,軟件規模與復雜度劇增,傳統開發方法難以應對,導致大量項目失敗。 表現: 成本失控:開發周期長、…

范式之殤-關系代數與參照完整性在 Web 后臺的落寞

最近參加了一個PostgreSQL相關的茶會&#xff0c;感慨良多。原本話題是PostgreSQL 在 SELECT 場景中憑借其成熟的查詢優化器、豐富的功能特性和靈活的執行策略&#xff0c;展現出顯著優勢。在窗口函數&#xff08;Window Functions&#xff09;、JOIN 優化、公共表表達式&#…

WebRTC工作原理詳細介紹、WebRTC信令交互過程和WebRTC流媒體傳輸協議介紹

簡介 WebRTC&#xff08;Web Real-Time Communication&#xff09;是一項允許在網頁瀏覽器之間進行音視頻通信的技術&#xff0c;基本不需要安裝額外的插件。它的核心特點是支持低延遲的點對點&#xff08;P2P&#xff09;通訊&#xff0c;常用于視頻聊天、實時文件共享、多人…

【大語言模型ChatGPT4/4o 】“AI大模型+”多技術融合:賦能自然科學暨ChatGPT在地學、GIS、氣象、農業、生態與環境領域中的應用

以ChatGPT、LLaMA、Gemini、DALLE、Midjourney、Stable Diffusion、星火大模型、文心一言、千問為代表AI大語言模型帶來了新一波人工智能浪潮&#xff0c;可以面向科研選題、思維導圖、數據清洗、統計分析、高級編程、代碼調試、算法學習、論文檢索、寫作、翻譯、潤色、文獻輔助…

提升編程效率的利器:Zed高性能多人協作代碼編輯器

在當今這個快節奏的開發環境中&#xff0c;一個高效、靈活的代碼編輯器無疑對開發者們起著至關重要的支持作用。Zed&#xff0c;作為來自知名編輯器Atom和語法解析器Tree-sitter的創造者的心血之作&#xff0c;正是這樣一款高性能支持多人合作的編輯神器。本文將帶領大家深入探…

基于51單片機步進電機控制—9個等級

基于51單片機步進電機控制 &#xff08;仿真&#xff0b;程序&#xff0b;設計報告&#xff09; 功能介紹 具體功能&#xff1a; 1.使用L298驅動步進電機轉動&#xff0c;可分為9個速度等級&#xff08;1級最快&#xff0c;9級最慢&#xff09;&#xff1b; 2.使用74HC595驅…

【某OTA網站】phantom-token 1004

新版1004 phantom-token 請求頭中包含phantom-token 定位到 window.signature 熟悉的vmp 和xhs一樣 最新環境檢測點 最新檢測 canvas 下的 toDataURL方法較嚴 過程中 會用setAttribute給canvas 設置width height 從而使toDataURL返回不同的值 如果寫死toDataURL的返回值…

LSTM的簡單模型

好的&#xff0c;我來用通俗易懂的語言解釋一下這個 LSTMTagger 類是如何工作的。 1?? 類的目的 這個 LSTMTagger 類是一個用于自然語言處理&#xff08;NLP&#xff09;任務的模型&#xff0c;目的是標注輸入的句子&#xff0c;通常用于詞性標注&#xff08;例如&#xff…

每天批次導入 100 萬對賬數據到 MySQL 時出現死鎖

一、死鎖原因及優化策略 1.1 死鎖原因分析 批量插入事務過大&#xff1a; Spring Batch 默認將整個 chunk&#xff08;批量數據塊&#xff09;作為一個事務提交&#xff0c;100 萬數據可能導致事務過長&#xff0c;增加鎖競爭。 并發寫入沖突&#xff1a; 多個線程或批處理作…

DeepResearch深度搜索實現方法調研

DeepResearch深度搜索實現方法調研 Deep Research 有三個核心能力 能力一&#xff1a;自主規劃解決問題的搜索路徑&#xff08;生成子問題&#xff0c;queries&#xff0c;檢索&#xff09;能力二&#xff1a;在探索路徑時動態調整搜索方向&#xff08;劉亦菲最好的一部電影是…

跟我學C++中級篇——STL容器的查找對比

一、C標準庫的查找 在C的STL中&#xff0c;對容器或相關序列的查找中&#xff0c;有兩種方式&#xff0c;一種是std::find&#xff0c;另外一種是std::search。而且在它們的基礎上&#xff0c;還衍生出std::find_if、std::find_if_not、std::find_end等和std::search_n、range…