日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

I/O多路转接之select

發布時間:2024/4/11 编程问答 27 豆豆
生活随笔 收集整理的這篇文章主要介紹了 I/O多路转接之select 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

I/O多路轉接之select

文章目錄

  • I/O多路轉接之select
    • 一、五種IO模型
    • 二、I/O多路轉接之select原理

一、五種IO模型

  • 阻塞IO: 在內核將數據準備好之前, 系統調用會一直等待. 所有的套接字, 默認都是阻塞方式

  • 非阻塞IO: 如果內核還未將數據準備好, 系統調用仍然會直接返回,并且返回EWOULDBLOCK錯誤碼.非阻塞IO往往需要程序員循環的方式反復嘗試讀寫文件描述符, 這個過程稱為輪詢.這對CPU來說是較大的浪費

  • 信號驅動IO: 內核將數據準備好的時候, 使用SIGIO信號通知應用程序進行IO操作

  • IO多路轉接: 雖然從流程圖上看起來和阻塞IO類似. 實際上最核心在于IO多路轉接能夠同時等待多個文件描述符的就緒狀態

  • 異步IO: 由內核在數據拷貝完成時, 通知應用程序(而信號驅動是告訴應用程序何時可以開始拷貝數據

高級IO相關概念:

  • 同步通信 vs 異步通信

  • 同步和異步關注的是消息通信機制(注意和線程同步區分)

  • 所謂同步,就是在發出一個調用時,在沒有得到結果之前,該調用就不返回. 但是一旦調用返回,就得 到返回值了;換句話說,就是由調用者主動等待這個調用的結果;

  • 異步則是相反,調用在發出之后,這個調用就直接返回了,所以沒有返回結果; 換句話說,當一個異步過程調用發出后,調用者不會立刻得到結果; 而是在調用發出后,被調用者通過狀態、通知來通知調用者,或通過回調函數處理這個調用

  • 阻塞 vs 非阻塞

  • 阻塞和非阻塞關注的是程序在等待調用結果(消息,返回值)時的狀態.

  • 阻塞調用是指調用結果返回之前,當前線程會被掛起. 調用線程只有在得到結果之后才會返回.

  • 非阻塞調用指在不能立刻得到結果之前,該調用不會阻塞當前線程

二、I/O多路轉接之select原理

  • 1.函數原型
int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout); 參數作用
nfds是需要監視的最大文件描述值+1
fd_set *readfds需要檢測的可讀文件描述符的集合,
fd_set *writefds需要檢測的可寫文件描述符的集合
fd_set *exceptfds需要檢測的異常文件描述符的集合;
struct timeval *timeout為結構timeval,用來設置select()的等待時間
  • 函數返回值
1.執行成功則返回文件描述符狀態已經改變的個數;2.如果返回0代表在描述詞狀態改變之前已經超過 timeout時間,沒有返回;3.當有錯誤發生時則返回-1,錯誤原因存于error,此時參數readfd、writefd、exceptfd和timeout的值變成不可預測。
  • 2.理解fd_set結構

  • 其實這個結構就是一個整型數組,更嚴格的說,是一個“位圖”,使用位圖中對應的位表示要監視的文件描述符

  • 用來消除描述詞組 set中相關fd的位

void FD_CLR(int fd,fd_set *set);
  • 用來測試描述詞組set中的相關fd的位是否為真;
void FD_ISSET(int fd,fd_set *set);
  • 用來設置描述詞組set中相關的位;
void FD_SET(int fd,fd_set *set);
  • 用來消除描述詞組set的全部位;
void FD_ZERO(int fd,fd_set *set);
  • 3.socket就緒條件

  • 讀就緒:

    1.socket內核中,接收緩沖區中的字節數,大于等于低水位標記SO_RCVLOWAT。此時就可以無阻塞的讀該文件描述符,并且返回值大于0;2.socket TCP通信中,對端關閉連接,此時對該socket讀,則返回0(四次揮手);3.監聽的socket上有新的連接請求;4.socket上有未處理的錯誤。
  • 寫就緒

    1.socket內核中,發送緩沖區中的可用字節數(發送緩沖區的空閑位置大小),大于等于低水位標記SO_RCVLOWAT。此時可以無阻塞的寫,并且返回值大于0;2.socket的寫操作被關閉。對一個寫操作被關閉的socket進行寫操作,會觸發SIGPIPE信號;3.socket使用非阻塞connect連接成功或失敗之后;4.socket上有未讀取的錯誤;
  • 異常就緒:

    socket上帶外數據。關于帶外數據,和TCP緊急模式相關(TCP協議頭中的緊急指針字段)
  • 4.理解select執行過程

理解select模型的關鍵在于理解fd_set,為說明方便,取fd_set長度為1字節,fd_set中的每一bit可以對應一個文件描述符fd。則1字節長的fd_set最大可以對應8個fd.

  • (1)執行fd_set set; FD_ZERO(&set);則set用位表示0000,0000。
  • (2)若fd=5,執行FD_SET(fd,&set);后set變為0001,0000(第5位置為1)
  • (3)若再加入fd=2,fd=1,則set變為0001,0011
  • (4)執行select(6,&set,0,0,0)阻塞等待
  • (5)若fd=1,fd=2上都發生可讀事件,則select返回,此時set變為0000,0011。注意:沒有事件發生的fd=5被清空
  • 5.select優缺點

select的特點

  • 可監控的文件描述符個數取決與sizeof(fd_set)的值. 我這邊服務器上sizeof(fd_set)=512,每bit表示一個文件描述符,則我服務器上支持的最大文件描述符是512*8=4096.
  • 將fd加入select監控集的同時,還要再使用一個數據結構array保存放到select監控集中的fd,
  • 一是用于再select 返回后,array作為源數據和fd_set進行FD_ISSET判斷。
  • 二是select返回后會把以前加入的但并無事件發生的fd清空,則每次開始select前都要重新從array取得 fd逐一加入(FD_ZERO最先),掃描array的同時取得fd最大值maxfd,用于select的第一個參數

select缺點

  • 每次調用select, 都需要手動設置fd集合, 從接口使用角度來說也非常不便.
  • 每次調用select,都需要把fd集合從用戶態拷貝到內核態,這個開銷在fd很多時會很大
  • 同時每次調用select都需要在內核遍歷傳遞進來的所有fd,這個開銷在fd很多時也很大
  • select支持的文件描述符數量太小
  • 6.select實現tcp服務器(支持多用戶)
#pragma once #include <vector> #include <unordered_map> #include <functional> #include <sys/select.h> #include "tcp_socket.hpp"// 必要的調試函數inline void PrintFdSet(fd_set* fds, int max_fd) {printf("select fds: ");for (int i = 0; i < max_fd + 1; ++i) {if (!FD_ISSET(i, fds)) {continue;}printf("%d ", i);}printf("\n"); }typedef std::function<void (const std::string& req, std::string* resp)> Handler;class Selector {public:Selector() {// [注意!] 初始化千萬別忘了!!max_fd_ = 0;FD_ZERO(&read_fds_);}bool Add( TcpSocket& sock) {int fd = sock.GetFd();printf("[Selector::Add] %d\n", fd);if (fd_map_.find(fd) != fd_map_.end()) {printf("Add failed! fd has in Selector!\n");return false;}fd_map_[fd] = sock;FD_SET(fd, &read_fds_);if (fd > max_fd_) {max_fd_ = fd;}return true;}bool Del( TcpSocket& sock) {int fd = sock.GetFd();printf("[Selector::Del] %d\n", fd);if (fd_map_.find(fd) == fd_map_.end()) {printf("Del failed! fd has not in Selector!\n");return false;}fd_map_.erase(fd);FD_CLR(fd, &read_fds_);// 重新找最大的文件描述符, 從右往左找比較快for (int i = max_fd_; i >= 0; --i) {if (!FD_ISSET(i, &read_fds_)) {continue;}max_fd_ = i;break;}return true;}// 返回讀就緒的文件描述符集bool Wait(std::vector<TcpSocket>* output) {output->clear();// [注意] 此處必須要創建一個臨時變量, 否則原來的結果會被覆蓋掉fd_set tmp = read_fds_;PrintFdSet(&tmp, max_fd_);int nfds = select(max_fd_ + 1, &tmp, NULL, NULL, NULL);if (nfds < 0) {perror("select");return false;}// [注意!] 此處的循環條件必須是 i < max_fd_ + 1for (int i = 0; i < max_fd_ + 1; ++i) {if (!FD_ISSET(i, &tmp)) {continue;}output->push_back(fd_map_[i]);}return true;}private:fd_set read_fds_;int max_fd_;// 文件描述符和 socket 對象的映射關系std::unordered_map<int, TcpSocket> fd_map_; };class TcpSelectServer {public:TcpSelectServer(const std::string& ip, uint16_t port) : ip_(ip), port_(port) {}bool Start(Handler handler) const {//1. 創建 socketTcpSocket listen_sock;bool ret = listen_sock.Socket();if (!ret) {return false;}// 2. 綁定端口號ret = listen_sock.Bind(ip_, port_);if (!ret) {return false;}// 3. 進行監聽ret = listen_sock.Listen(5);if (!ret) {return false;}// 4. 創建 Selector 對象Selector selector;selector.Add(listen_sock);// 5. 進入事件循環for (;;) {std::vector<TcpSocket> output;bool ret = selector.Wait(&output);if (!ret) {continue;}// 6. 根據就緒的文件描述符的差別, 決定后續的處理邏輯for (size_t i = 0; i < output.size(); ++i) {if (output[i].GetFd() == listen_sock.GetFd()) {// 如果就緒的文件描��符是 listen_sock, 就執行 accept, 并加入到 select 中TcpSocket new_sock;listen_sock.Accept(&new_sock, NULL, NULL);selector.Add(new_sock);}else {// 如果就緒的文件描述符是 new_sock, 就進行一次請求的處理std::string req, resp;bool ret = output[i].Recv(&req);if (!ret) {selector.Del(output[i]);// [注意!] 需要關閉 socketoutput[i].Close();continue;}// 調用業務函數計算響應handler(req, &resp);// 將結果寫回到客戶端output[i].Send(resp);}} // end for} // end for (;;)return true;}private:std::string ip_;uint16_t port_; };

總結

以上是生活随笔為你收集整理的I/O多路转接之select的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。