深入剖析 I/O 復用之 select 機制

深入剖析 I/O 復用之 select 機制

在網絡編程中,I/O 復用是一項關鍵技術,它允許程序同時監控多個文件描述符的狀態變化,從而高效地處理多個 I/O 操作。select 作為 I/O 復用的經典實現方式,在眾多網絡應用中扮演著重要角色。本文將深入探討 select 的原理、使用方法、相關數據結構以及實際應用示例。

一、I/O 復用概述

I/O 復用使得程序能夠同時監聽多個文件描述符,適用于多種場景:

  • 客戶端程序需要同時處理多個套接字。
  • 客戶端要兼顧用戶輸入和網絡連接處理。
  • TCP 服務器需同時管理監聽套接字和已連接套接字。
  • 服務器要同時處理 TCP 請求和 UDP 請求。
  • 服務器需要監聽多個端口。

二、select 原理

select 系統調用通過維護三個文件描述符集合(讀集合、寫集合和異常集合)來監視不同類型的事件。它會阻塞當前進程,直到有一個或多個文件描述符就緒(有數據可讀、可寫或發生異常),或者達到指定的超時時間。

三、select 使用方法

函數原型

#include <sys/select.h>  
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);  
  • nfds:需要監視的最大文件描述符編號加 1。
  • readfds:讀文件描述符集合,用于監視文件描述符是否有數據可讀。
  • writefds:寫文件描述符集合,用于監視文件描述符是否可寫。
  • exceptfds:異常文件描述符集合,用于監視文件描述符是否發生異常。
  • timeout:超時時間,指定 select 函數的最大阻塞時間。如果為 NULL,則會一直阻塞直到有文件描述符就緒。

fd_set 數據結構

fd_set 用于存儲文件描述符集合,本質上是一個位圖(bitmask)。每個文件描述符對應位圖中的一位,若該位置為 1,則表示該文件描述符在集合內;若為 0,則表示不在集合內。

在這里插入圖片描述

操作 fd_set 的宏函數

  • FD_ZERO(fd_set *set):將 fd_set 集合初始化為空,即把集合中所有位都置為 0。
  • FD_SET(int fd, fd_set *set):將指定的文件描述符 fd 添加到 fd_set 集合中,把對應位設置為 1。
  • FD_CLR(int fd, fd_set *set):從 fd_set 集合中移除指定的文件描述符 fd,將對應位設置為 0。
  • FD_ISSET(int fd, fd_set *set):檢查指定的文件描述符 fd 是否在 fd_set 集合中。若在集合中則返回非零值,否則返回 0。

timeout 結構體

struct timeval {  long tv_sec; // 秒數  long tv_usec; // 微秒數  
};  

用于指定 select 函數的超時時間。

四、使用 select 實現 TCP 服務器示例代碼解析

代碼功能

此代碼創建了一個 TCP 服務器,借助 select 函數實現 I/O 復用,能夠同時處理多個客戶端的連接與數據收發。服務器監聽本地地址 127.0.0.16000 端口,當有新的客戶端連接時會接受連接,接收客戶端發送的數據,并向客戶端回復 "ok"

代碼逐段解析

頭文件與常量定義
#include <stdio.h>  
#include <stdlib.h>  
#include <string.h>  
#include <unistd.h>  
#include <sys/select.h>  
#include <sys/socket.h>  
#include <arpa/inet.h>  
#include <netinet/in.h>  #define MAXARR 10  

引入所需頭文件,并定義常量 MAXARR 表示存儲文件描述符數組的最大長度。

函數聲明與實現
  • socket_init 函數
    int socket_init() {  int sockfd = socket(AF_INET, SOCK_STREAM, 0);  if (sockfd == -1) {  perror("socket err");  return -1;  }  struct sockaddr_in saddr;  memset(&saddr, 0, sizeof(saddr));  saddr.sin_family = AF_INET;  saddr.sin_port = htons(6000);  saddr.sin_addr.s_addr = inet_addr("127.0.0.1");  int res = bind(sockfd, (struct sockaddr *)&saddr, sizeof(saddr));  if (res == -1) {  perror("bind err");  return -1;  }  if ((res = listen(sockfd, 5) < 0)) {  perror("listen err");  return -1;  }  return sockfd;  
    }  
    
    該函數用于初始化服務器套接字,包括創建套接字、綁定地址和端口、開始監聽連接。若出現錯誤則返回 -1
  • arr_init 函數
    void arr_init(int arr[]) {  for (int i = 0; i < MAXARR; i++) {  arr[i] = -1;  }  
    }  
    
    把存儲文件描述符的數組初始化為 -1,表示數組中沒有有效的文件描述符。
  • arr_add 函數
    void arr_add(int arr[], int fd) {  for (int i = 0; i < MAXARR; i++) {  if (arr[i] == -1) {  arr[i] = fd;  break;  }  }  
    }  
    
    把新的文件描述符添加到數組中,找到第一個值為 -1 的位置,將新的文件描述符存入該位置。
  • arr_del 函數
    void arr_del(int arr[], int fd) {  for (int i = 0; i < MAXARR; i++) {  if (arr[i] == fd) {  arr[i] = -1;  break;  }  }  
    }  
    
    從數組中刪除指定的文件描述符,找到該文件描述符所在的位置,將其值置為 -1
  • accept_cli 函數
    void accept_cli(int sockfd, int arr[]) {  int c = accept(sockfd, NULL, NULL);  if (c == -1) {  perror("accept err");  return;  }  printf("cli(%d) accept\n", c);  arr_add(arr, c);  
    }  
    
    接受新的客戶端連接,若接受成功則將客戶端的套接字文件描述符添加到數組中。
  • recv_cli 函數
    void recv_cli(int fd, int arr[]) {  char buff[128] = {0};  int n = recv(fd, buff, 127, 0);  if (n < 0) {  perror("recv err");  printf("cli(%d) close\n", fd);  close(fd);  arr_del(arr, fd);  return;  }  if (n == 0) {  printf("cli(%d) close\n", fd);  close(fd);  arr_del(arr, fd);  return;  }  printf("buff(c=%d):%s\n", fd, buff);  send(fd, "ok", 2, 0);  
    }  
    
    接收客戶端發送的數據,若接收出錯或者客戶端關閉連接,則關閉對應的套接字并從數組中刪除該文件描述符;若接收到數據,則打印數據并向客戶端回復 "ok"
main 函數
int main() {  int sockfd = socket_init();  if (sockfd == -1) {  exit(1);  }  int arr[MAXARR];  arr_init(arr);  arr_add(arr, sockfd);  fd_set fdset;  while (1) {  FD_ZERO(&fdset);  int maxfd = -1;  for (int i = 0; i < MAXARR; i++) {  if (arr[i] == -1) {  continue;  }  FD_SET(arr[i], &fdset);  if (arr[i] > maxfd) {  maxfd = arr[i];  }  }  struct timeval tv = {5, 0};  int n = select(maxfd + 1, &fdset, NULL, NULL, &tv);  if (n == -1) {  perror("select err");  continue;  } else if (n == 0) {  printf("TIME OUT\n");  continue;  } else {  for (int i = 0; i < MAXARR; i++) {  if (arr[i] == -1) {  continue;  } else {  if (FD_ISSET(arr[i], &fdset)) {  if (arr[i] == sockfd) {  accept_cli(arr[i], arr);  } else {  recv_cli(arr[i], arr);  }  }  }  }  }  }  
}  
  • 初始化服務器套接字,若失敗則退出程序。
  • 初始化存儲文件描述符的數組,并將服務器套接字文件描述符添加到數組中。
  • 進入無限循環:
    • 每次循環開始時,清空 fd_set 集合。
    • 遍歷數組,將有效的文件描述符添加到 fd_set 集合中,并找出最大的文件描述符。
    • 設置 select 函數的超時時間為 5 秒。
    • 調用 select 函數進行監聽,根據返回值判斷情況:若返回 -1 表示出錯,打印錯誤信息并繼續循環;若返回 0 表示超時,打印超時信息并繼續循環;若返回大于 0 的值,表示有文件描述符就緒。
    • 再次遍歷數組,檢查哪些文件描述符就緒。若為服務器套接字,則調用 accept_cli 函數接受新的連接;若為客戶端套接字,則調用 recv_cli 函數接收數據。

五、select 的優缺點

優點

  • 跨平臺支持select 是一種標準的系統調用,幾乎所有的 Unix/Linux 系統和 Windows 系統都支持,具有良好的跨平臺性。
  • 簡單易用select 的接口相對簡單,使用起來比較方便,對于小規模的應用場景非常適用。

缺點

  • 文件描述符數量限制select 有最大文件描述符數量的限制,一般為 1024。如果需要處理大量的文件描述符,可能會受到限制。
  • 性能問題select 需要遍歷所有的文件描述符來檢查其狀態,時間復雜度為 O(n),當文件描述符數量較多時,性能會受到影響。
  • 內核和用戶空間數據拷貝:每次調用 select 時,都需要將文件描述符集合從用戶空間拷貝到內核空間,在文件描述符數量較多時,會帶來一定的開銷。

六、適用場景

由于 select 存在一些局限性,它適用于文件描述符數量較少、對性能要求不是特別高的場景,例如一些簡單的網絡服務器、嵌入式系統等。在實際應用中,若需要處理大量文件描述符或對性能有更高要求,可以考慮使用 pollepoll 等更高級的 I/O 復用機制。

通過深入理解 select 的原理、使用方法和優缺點,我們能夠在網絡編程中更好地運用這一技術,構建高效穩定的網絡應用。

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

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

相關文章

【Linux系列】目錄大小查看

&#x1f49d;&#x1f49d;&#x1f49d;歡迎來到我的博客&#xff0c;很高興能夠在這里和您見面&#xff01;希望您在這里可以感受到一份輕松愉快的氛圍&#xff0c;不僅可以獲得有趣的內容和知識&#xff0c;也可以暢所欲言、分享您的想法和見解。 推薦:kwan 的首頁,持續學…

《AI大模型應知應會100篇》第48篇:構建企業級大模型應用的架構設計

第48篇&#xff1a;構建企業級大模型應用的架構設計 摘要&#xff1a;本文將提供企業級大模型應用的端到端架構設計方案&#xff0c;從系統設計原則到技術棧選擇&#xff0c;從高可用保障到安全合規&#xff0c;全面覆蓋構建穩健、可擴展、安全的大模型應用所需的工程實踐。適合…

人協同的自動化需求分析

多人協同的自動化需求分析是指通過技術工具和協作流程&#xff0c;讓多個參與者&#xff08;如產品經理、開發人員、測試人員等&#xff09;在需求分析階段高效協作&#xff0c;并借助自動化手段提升需求收集、整理、驗證和管理的效率與質量。以下是其核心要點&#xff1a; 1. …

【戰略合作】開封大學_閥門產業學院+智橙PLM

12月20日&#xff0c;在核電廠閥門系列團體標準啟動會上&#xff0c;開封大學閥門產業學院與橙色云互聯網設計有限公司達成戰略合作。 以平臺賦能行業&#xff0c;讓閥門教育“有的放矢” 會議與會者包括&#xff1a; 開封大學副校長 李治 中國國際科技促進會標準化工作委員…

element-ui日期時間選擇器禁止輸入日期

需求解釋&#xff1a;時間日期選擇器&#xff0c;下方日期有禁止選擇范圍&#xff0c;所以上面的日期輸入框要求禁止輸入&#xff0c;但時間輸入框可以輸入&#xff0c;也就是下圖效果&#xff0c;其中日歷中的禁止選擇可以通過【picker-options】這個屬性實現&#xff0c;此屬…

計算機網絡:深入分析三層交換機硬件轉發表生成過程

三層交換機的MAC地址轉發表生成過程結合了二層交換和三層路由的特性,具體可分為以下步驟: 一、二層MAC地址表學習(基礎轉發層) 初始狀態 交換機啟動時,MAC地址表為空,處于學習階段。 數據幀接收與源MAC學習 當主機A發送數據幀到主機B時,交換機會檢查數據幀的源MAC地址。…

【開源解析】基于Python的智能文件備份工具開發實戰:從定時備份到托盤監控

&#x1f4c1;【開源解析】基于Python的智能文件備份工具開發實戰&#xff1a;從定時備份到托盤監控 &#x1f308; 個人主頁&#xff1a;創客白澤 - CSDN博客 &#x1f525; 系列專欄&#xff1a;&#x1f40d;《Python開源項目實戰》 &#x1f4a1; 熱愛不止于代碼&#xff0…

Windows 環境變量完全指南:系統變量、用戶變量與 PATH 詳解

1. 什么是環境變量&#xff1f; 環境變量&#xff08;Environment Variables&#xff09;是 Windows 系統中用于存儲配置信息的鍵值對&#xff0c;它們可以影響系統和應用程序的行為。例如&#xff1a; PATH&#xff1a;告訴系統在哪里查找可執行文件&#xff08;如 python、j…

詳解RabbitMQ工作模式之工作隊列模式

目錄 工作隊列模式 概念 特點 應用場景 工作原理 注意事項 代碼案例 引入依賴 常量類 編寫生產者代碼 編寫消費者1代碼 編寫消費者2代碼 先運行生產者&#xff0c;后運行消費者 先運行消費者&#xff0c;后運行生產者 工作隊列模式 概念 在工作隊列模式中&#x…

數據結構-非線性結構-二叉樹

概述 /** * 術語 * 根節點&#xff08;root node&#xff09;&#xff1a;位于二叉樹頂層的節點&#xff0c;沒有父節點。 * 葉節點&#xff08;leaf node&#xff09;&#xff1a;沒有子節點的節點&#xff0c;其兩個指針均指向 None 。 * 邊&#xff08;edge&#xff09;&…

芯片筆記 - 手冊參數注釋

芯片手冊參數注釋 基礎參數外圍設備USB OTG&#xff08;On-The-Go&#xff09;以太網存儲卡&#xff08;SD&#xff09;SDIO 3.0(Secure Digital Input/Output)GPIO&#xff08;General Purpose Input/Output 通用輸入/輸出接口&#xff09;ADC&#xff08;Analog to Digital C…

力扣94. 二叉樹的中序遍歷

94. 二叉樹的中序遍歷 給定一個二叉樹的根節點 root &#xff0c;返回 它的 中序 遍歷 。 示例 1&#xff1a; 輸入&#xff1a;root [1,null,2,3] 輸出&#xff1a;[1,3,2]示例 2&#xff1a; 輸入&#xff1a;root [] 輸出&#xff1a;[]示例 3&#xff1a; 輸入&#…

深度學習:AI為老年癡呆患者點亮希望之光

引言 隨著全球人口老齡化進程的加速&#xff0c;老年癡呆癥已成為嚴重威脅老年人健康和生活質量的公共衛生問題。據世界衛生組織統計&#xff0c;全球每 3 秒鐘就有 1 人被診斷為癡呆&#xff0c;預計到 2050 年&#xff0c;全球癡呆患者人數將從目前的約 5000 萬激增至 1.52 億…

拋物線法(二次插值法)

拋物線法簡介 拋物線法&#xff08;Quadratic Interpolation Method&#xff09;是一種用于一維單峰函數極值搜索的經典優化方法。該方法通過在區間內選取三個不同的點&#xff0c;擬合一條二次拋物線&#xff0c;并求取這條拋物線的極值點作為新的迭代點&#xff0c;從而逐步…

FreeRTOS如何檢測內存泄漏

在嵌入式系統中&#xff0c;內存資源通常非常有限&#xff0c;內存泄漏可能導致系統性能下降甚至崩潰。內存泄漏是指程序分配的內存未被正確釋放&#xff0c;逐漸耗盡可用內存。 FreeRTOS作為一種輕量級實時操作系統&#xff08;RTOS&#xff09;&#xff0c;廣泛應用于資源受限…

Mockoon 使用教程

文章目錄 一、簡介二、模擬接口1、Get2、Post 一、簡介 1、Mockoon 可以快速模擬API&#xff0c;無需遠程部署&#xff0c;無需帳戶&#xff0c;免費&#xff0c;跨平臺且開源&#xff0c;適合離線環境。 2、支持get、post、put、delete等所有格式。 二、模擬接口 1、Get 左…

如何進行APP安全加固

進行APP安全加固的關鍵在于代碼混淆、加密敏感數據、權限管理、漏洞掃描與修復。其中&#xff0c;代碼混淆能有效阻止逆向工程與篡改攻擊&#xff0c;提升應用的安全防護能力。通過混淆代碼&#xff0c;攻擊者難以輕易理解源代碼邏輯&#xff0c;從而降低被破解或攻擊的風險。 …

【C++】手搓一個STL風格的string容器

C string類的解析式高效實現 GitHub地址 有夢想的電信狗 1. 引言&#xff1a;字符串處理的復雜性 ? 在C標準庫中&#xff0c;string類作為最常用的容器之一&#xff0c;其內部實現復雜度遠超表面認知。本文將通過一個簡易仿照STL的string類的完整實現&#xff0c;揭示其設…

辰鰻科技朱越洋:緊扣時代契機,全力投身能源轉型戰略賽道

國家能源局于4月28日出臺的《關于促進能源領域民營經濟發展若干舉措的通知》&#xff08;以下簡稱《通知》&#xff09;&#xff0c;是繼2月民營企業座談會后深化能源領域市場化改革的關鍵政策&#xff0c;標志著民營經濟在“雙碳”目標引領下正式進入能源轉型的核心賽道。 自…

Vue實現不同網站之間的Cookie共享功能

前言 最近有小伙伴在聊天室中提到這么一個需求&#xff0c;就是說希望用戶在博客首頁中登錄了之后&#xff0c;可以跳轉到管理系統去發布文章。這個需求的話就涉及到了不同網站之間cookie共享的功能&#xff0c;那么咱們就來試著解決一下這個功能。 實現方式 1. 后端做中轉 …