IO多路轉接模型----(select的模型,select的優缺點,poll的模型,poll的優缺點)

IO多路轉接模型:select/poll/epoll

對大量描述符進行事件監控(可讀/可寫/異常)

select模型

在這里插入圖片描述

  1. 用戶定義描述符的事件監控集合 fd_set(這是一個位圖,用于存儲要監控的描述符); 用戶將需要監控的描述符添加到集合中,這個描述符集合的大小取決于一個宏 _FD_SETSIZE = 1024
  2. 將集合拷貝到內核中進行監控;在內核中對所有描述符進行輪詢遍歷判斷是否有關心的事件就緒
  3. 若有描述符就緒,從監控集合中將未就緒的描述符移除;然后調用返回(返回給用戶就緒描述符饑集合)
  4. 用戶遍歷所有描述符,判斷描述符是否在集合中,若在集合中,則這個描述符是就緒描述符
  5. 用戶針對這個就緒的描述符事件進行相應的處理,用戶僅僅對大量描述符中就緒的描述符進行處理,sock程序就可以避免accept/recv處因為沒有數據到來而阻塞
#include <sys/select.h> 
/* According to earlier standards */ 
#include <sys/time.h> 
#include <sys/types.h> 
#include <unistd.h> int select(int nfds, fd_set *readfds,fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
  • nfds: 監控的文件描述符集里最大文件描述符加1,因為此參數會告訴內核檢測前多少個文件描述符的狀態
  • readfds: 監控有讀數據到達文件描述符集合,傳入傳出參數
  • writefds: 監控寫數據到達文件描述符集合,傳入傳出參數
  • exceptfds: 監控異常發生達文件描述符集合,如帶外數據到達異常,傳入傳出參數
  • timeout: 定時阻塞監控時間,3種情況
    1. NULL,永遠等下去
    2. 設置timeval,等待固定時間
    3. 設置timeval里時間均為0,檢查描述字后立即返回,輪詢
struct timeval 
{ 
long tv_sec; /* seconds */ //秒
long tv_usec; /* microseconds */ //微秒 
}; 
  • void FD_CLR(int fd, fd_set *set); //把文件描述符集合里fd清0 將指定的描述符從集合中移除
  • int FD_ISSET(int fd, fd_set *set); //測試文件描述符集合里fd是否置1 判斷指定的描述符是否在集合中
  • void FD_SET(int fd, fd_set *set); //把文件描述符集合里fd位置1 將指定的描述符添加到集合中
  • void FD_ZERO(fd_set *set); //把文件描述符集合里所有位清0 清空描述符集合

select優缺點

  1. select 能監聽的文件描述符個數受限于 FD_SETSIZE,一般為 1024,單純改變進程打開的文件描述符個數并不 能改變 select 監聽文件個數
  2. 每次都需要重新將監控集合拷貝到內核(select會修改集合)
  3. 解決 1024 以下客戶端時使用 select 是很合適的,但如果鏈接客戶端過多,select 采用的是輪詢模型,會大大降低服務器響應效率
  4. select返回給用戶就緒的描述符集合(將未就緒的描述符從集合中移除),但是并沒有告訴用戶具體哪一個描述符就緒,需要用戶遍歷描述符是否在集合中來判斷哪個描述符就緒,這個判斷是一個遍歷的過程,性能隨著描述符增多而下降,并且復雜度更高
  5. select每次返回都會修改監控集合,因此每次都需要用戶重新向集合中添加所有描述符
  6. select遵循posix標準,支持跨平臺;
  7. 監控的超時等待時間可以精細到微秒
class Select
{
public:Add(TcpSocket &sock); //將用戶關心socket描述符添加到監控集合中Del(TcpSocket &sock); //從監控集合中移除不再關心的socket描述符Wait(std::vector<TcpSocket>&list,init timeout_sec,int timeout_sec); //從開始監控,并且向用戶返回就緒的socket
private:fd_set _rfds;int_max_fd;
};

實現

select服務端

/** 這個文件封裝一個select類,向外界提供更加簡單點的select監控接口*  將用戶關心socket描述符添加到監控集合中* 從監控集合中移除不再關心的socket描述符* 從開始監控,并且向用戶返回就緒的socket*/#include<vector>
#include<sys/select.h>
#include"tcpsocket.hpp"class Select
{public:Select(): _max_fd (-1){FD_ZERO(&_rfds);//清空集合}   bool Add(TcpSocket &sock){int fd = sock.GetFd();//void FD_SET(int fd,fd_set *set)//向set描述符集合中添加fd描述符FD_SET(fd,&_rfds);_max_fd = _max_fd > fd ? _max_fd : fd; return true;}   bool Del(TcpSocket &sock){int fd = sock.GetFd();//void FD_CLR(int fd, fd_set *set)//從set描述符集合中移除FD_CLR(fd,&_rfds);//從最大的往前遍歷for(int i = _max_fd ; i >= 0; i--){//int FD_ISSET(int fd, fd_set *set);//判斷fd描述符是否還在set集合中if(FD_ISSET(i,&_rfds)){_max_fd = i;break;}}} bool Wait(std::vector<TcpSocket>&list,int timeout_sec = 3){struct timeval tv; //超時時間tv.tv_sec = timeout_sec;tv.tv_usec = 0;fd_set set = _rfds;int ret =select(_max_fd + 1, &set, NULL ,NULL,&tv);if(ret < 0){perror("select error");return false;}else if(ret == 0){std::cout<< "select wait timeout\n";return false;}for(int i =0 ;i <= _max_fd; i++){if(FD_ISSET(i,&set)){TcpSocket sock;sock.SetFd(i);list.push_back(sock);}}return true;} private:fd_set _rfds;int _max_fd;
};int main()
{TcpSocket sock;CHECK_RET(sock.Socket());CHECK_RET(sock.Bind("192.168.145.132",9000));CHECK_RET(sock.Listen());Select s;s.Add(sock);                                                                while(1){std::vector<TcpSocket>list;if(s.Wait(list)==false){continue;}for(int i =0 ; i < list.size(); i++){//判讀socket是監聽socket還是通信socketif(list[i].GetFd() == sock.GetFd()){TcpSocket clisock;std::string cli_ip;uint16_t cli_port;if(sock.Accept(clisock,cli_ip,cli_port) == false){continue;}s.Add(clisock);}else{std::string buf;if(list[i].Recv(buf) == false){s.Del(list[i]);list[i].Close();continue;}std::cout<<"client say:"<<buf <"\n";}}}sock.Close();return 0;
}    

客戶端

  
#include <signal.h>
#include "tcpsocket.hpp"void sigcb(int signo){printf("connection closed\n");
}
int main(int argc, char *argv[])
{if (argc != 3) {std::cout<<"./tcp_cli 192.168.122.132 9000\n";return -1; }   std::string ip = argv[1];uint16_t port = atoi(argv[2]);signal(SIGPIPE, sigcb);TcpSocket sock;CHECK_RET(sock.Socket());CHECK_RET(sock.Connect(ip, port));while(1) {std::string buf;std::cout <<"client say:";fflush(stdout);std::cin >>buf;sock.Send(buf);}sock.Close();return 0;
}       

在這里插入圖片描述
在這里插入圖片描述
在這里插入圖片描述

poll模型

poll函數接口

#include <poll.h>int poll(struct pollfd *fds, nfds_t nfds, int timeout);// pollfd結構 
struct pollfd {  int   fd;         /* 用戶監控的描述符 */   short events;     /* 描述符關心的事件 POLLIN/POLLOUT */  short revents;    /* 描述符實際就緒的事件 */ }
參數說明
  • fds是一個poll函數監聽的結構列表. 每一個元素中, 包含了三部分內容: 文件描述符, 監聽的事件集合, 返回的事件集合. 描述事件結構數組
  • nfds表示fds數組的長度. 要監控事件個數
  • timeout表示poll函數的超時時間, 單位是毫秒(ms).
events和revents的取值:
  1. POLLIN 普通或帶外優先數據可讀,即POLLRDNORM | POLLRDBAND
  2. POLLRDNORM 數據可讀
  3. POLLRDBAND 優先級帶數據可讀
  4. POLLPRI 高優先級可讀數據
  5. POLLOUT 普通或帶外數據可寫
  6. POLLWRNORM 數據可寫
  7. POLLWRBAND 優先級帶數據可寫
  8. POLLERR 發生錯誤
  9. POLLHUP 發生掛起
  10. POLLNVAL 描述字不是一個打開的文件

實現原理

  1. 用戶定義描述符事件數組,向數組中添加關心的描述符事件
  2. 將pollfd事件數組,拷貝到內核中進行遍歷輪詢監控,判斷是否就緒了關心的事件
  3. 將描述符實際就緒的事件信息,標記到revents中
  4. 當poll返回。用戶遍歷pollfd事件數組,通過revents判斷描述符就緒了什么事件,進而進行相應操作

使用poll監控標準輸入

#include <poll.h>
#include <unistd.h>
#include <stdio.h>int main() { struct pollfd poll_fd;  //一個結構就是一個事件poll_fd.fd = 0;  poll_fd.events = POLLIN;  //可讀事件for (;;) {    //開始監控int ret = poll(&poll_fd, 1, 1000); //遍歷輪詢   if (ret < 0) {    //出錯perror("poll");    continue;    }if (ret == 0) {      //超時printf("poll timeout\n");     continue;   } //ret>0if (poll_fd.revents == POLLIN) {  //看事件是否為我們所關心的事件 //對事件進行操作    char buf[1024] = {0};      read(0, buf, sizeof(buf) - 1);     printf("stdin:%s", buf);    }  } 
} 

poll有缺點分析

優點:
  1. poll采用事件結構形式對描述符關心的事件進行監控,簡化了select三種集合操作的流程
  2. poll沒有描述符上限的設置
缺點
  1. 不能跨平臺,只能用于Linux下
  2. 在內核中進行輪詢遍歷判斷就緒,性能隨著描述符事件增多而下降
  3. 也不會告訴用戶具體哪一個描述符就緒,需要用戶輪詢遍歷判斷事件中的revents;進而對描述符進行相應事件操作 revents & POLLIN/POLLOUT
  4. 需要每次都向內核中拷貝監控信息

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

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

相關文章

IO多路轉接模型-----epoll

epoll&#xff1a; Linux下性能最高的多路轉接模型 epoll 有3個相關的系統調用. epoll_create 功能&#xff1a;創建epoll&#xff0c;在內核中創建eventpoll結構體&#xff0c;size決定了epoll最多監控多少個描述符&#xff0c;在Linux2.6.8之后被忽略&#xff0c;但是必須…

再寫順序表(c語言實現,外加冒泡排序,二分查找)

概念 順序表是用一段物理地址連續的存儲單元依次存儲數據元素的線性結構&#xff0c;一般情況下采用數組存儲。在數組 上完成數據的增刪查改。 順序表一般可以分為&#xff1a; 靜態順序表&#xff1a;使用定長數組存儲。動態順序表&#xff1a;使用動態開辟的數組存儲。 頭…

再寫單鏈表(不帶頭單鏈表)

單鏈表 實際中鏈表的結構非常多樣&#xff0c;以下情況組合起來就有8種鏈表結構&#xff1a; 單向、雙向帶頭、不帶頭循環、非循環 雖然有這么多的鏈表的結構&#xff0c;但是我們實際中最常用還是兩種結構&#xff1a; 無頭單向非循環鏈表&#xff1a;結構簡單&#xff0…

再寫雙向循環鏈表

#pragma once #include<assert.h> #include<malloc.h> #include<stdio.h> typedef int DLDataType;//定義鏈表結點結構 typedef struct DListNode{DLDataType value;struct DListNode *prev; //指向前一個結點struct DListNode *next; //指向后一個結點 } DL…

鏈表題目--1 刪除鏈表中所有等于val的值

注意事項 要刪除的結點相鄰第一個結點就是要刪除的結點 /*** Definition for singly-linked list.* struct ListNode {* int val;* struct ListNode *next;* };*/struct ListNode* removeElements(struct ListNode* head, int val){if(headNULL){return NULL;}struct …

鏈表題目--2 求鏈表的中間結點 和 求鏈表中倒數第k個結點

求鏈表的中間結點 思路 一個走兩步&#xff0c;一個走一步。一個走到尾&#xff0c;另外一個就走到了中間 /*** Definition for singly-linked list.* struct ListNode {* int val;* struct ListNode *next;* };*/struct ListNode* middleNode(struct ListNode* head…

鏈表題目---3 合并兩個有序單鏈表 和 分割鏈表

合并兩個有序單鏈表 /*** Definition for singly-linked list.* struct ListNode {* int val;* struct ListNode *next;* };*/struct ListNode* mergeTwoLists(struct ListNode* l1, struct ListNode* l2){if(l1 NULL){return l2;}else if(l2 NULL){return l1;}struc…

鏈表題目---4 刪除鏈表中重復的結點 和 判斷鏈表是否為回文鏈表

刪除鏈表中重復的結點 /* struct ListNode {int val;struct ListNode *next;ListNode(int x) :val(x), next(NULL) {} }; */ class Solution { public:ListNode* deleteDuplication(ListNode* pHead){if(pHead NULL){return NULL;}ListNode *prev NULL; //用于刪除的結點, 是…

鏈表題目----5 相交鏈表 和 環形鏈表 和 返回鏈表開始入環的第一個節點

相交鏈表 思路 鏈表交叉不可能是x型因為有可能兩個鏈表不等長&#xff0c;所以我們必須讓他們從同一起跑位置去起跑從同一起跑位置出發&#xff0c;依次比較每個結點的地址是否相同 /*** Definition for singly-linked list.* struct ListNode {* int val;* struct L…

鏈表題目---6 復制帶隨機指針的鏈表

思路 將新結點放在老結點的后面 復制random 將鏈表拆開 /* // Definition for a Node. class Node { public:int val;Node* next;Node* random;Node() {}Node(int _val, Node* _next, Node* _random) {val _val;next _next;random _random;} }; */ class Solution { publi…

括號匹配問題(c和c++版本實現)

括號匹配問題 思路 遇見左括號入棧&#xff0c;遇見一個右括號彈出棧頂元素右括號入棧前如果棧已經為空&#xff0c;則不匹配如果不為空則讀取并彈出&#xff0c;彈出來的元素與右括號做比較&#xff0c;必須匹配&#xff0c;不匹配返回false;如果最后棧里還有元素&#xff0c…

用隊列實現棧 AND 用棧實現隊列

用隊列實現棧 思路 入隊列就是入棧出隊列的時候&#xff0c;就是把前面size-1個隊列中的元素先出&#xff0c;這樣最后一個元素就成隊首元素了&#xff0c;再把出去的元素再次入隊列讀棧頂元素&#xff0c;過程和第二步是一樣的&#xff0c;就是彈出后&#xff0c;再把它入隊列…

最小棧的實現(設計一個支持 push,pop,top 操作,并能在常數時間內檢索到最小元素的棧。)

最小棧的實現 思路 兩個棧&#xff0c;左邊棧接受元素&#xff0c;右邊棧存最小的元素入棧時&#xff0c;先入左邊棧&#xff0c;隨后進行比較&#xff0c;左邊和右邊棧頂元素進行比較&#xff0c;如果新元素小&#xff0c;就把新元素放在右邊的棧頂位置&#xff0c;如果新元素…

再寫循環隊列----c++實現

再寫循環隊列 class MyCircularQueue { public:/** Initialize your data structure here. Set the size of the queue to be k. */MyCircularQueue(int k) {array (int *)malloc(sizeof(int)*k);capacity k;size 0;front 0;rear 0;}/** Insert an element into the circu…

再談二叉樹(二叉樹概念,二叉樹的性質,二叉樹的存儲結構)

樹的概念 樹的概念 樹是一種非線性的數據結構&#xff0c;它是由n&#xff08;n>0&#xff09;個有限結點組成一個具有層次關系的集合。把它叫做樹是因 為它看起來像一棵倒掛的樹&#xff0c;也就是說它是根朝上&#xff0c;而葉朝下的。它具有以下的特點&#xff1a;每個…

二叉樹題目----1 前序中序后序遍歷二叉樹并返回相應的遍歷(不是打印)

前序遍歷 /*** Definition for a binary tree node.* struct TreeNode {* int val;* struct TreeNode *left;* struct TreeNode *right;* };*//*** Note: The returned array must be malloced, assume caller calls free().*/ int *array; int size;void _preorde…

二叉樹題目----2 檢查兩顆樹是否相同 和 對稱二叉樹的判定

檢查兩顆樹是否相同 思路 根要相等 p->val q->val左子樹相等 isSameTree(p->left,q->left)右子樹也要相等 isSameTree(p->right,q->right) /*** Definition for a binary tree node.* struct TreeNode {* int val;* struct TreeNode *left;* …

二叉樹題目---3 另一個樹的子樹 AND 二叉樹最大深度

另一個樹的子樹 思路 兩個數都遍歷一遍&#xff0c;找到一個根結點相同時&#xff0c;判斷以這個根結點為首的二叉樹是否相等 前序遍歷判斷兩棵樹是否相同對于返回值的處理是難點 bool isSameTree(struct TreeNode *p, struct TreeNode *q) {if(p NULL && q NULL)…

二叉樹題目----4 前序遍歷重構二叉樹 AND 求二叉樹中所有結點的個數

前序遍歷重構二叉樹 思路 整個二叉樹用數組存儲因為先序遍歷它先遍歷根&#xff0c;再遍歷左&#xff0c;左邊沒有跑完是不會去遍歷右邊的&#xff0c;所以遍歷左子樹&#xff0c;就是數組元素每回向后一個&#xff0c;個數-1遍歷右邊時&#xff0c;就是數組起始位置左子樹跑到…

二叉樹的進階操作---(求二叉樹中所有結點個數,求葉子結點個數,求第k層結點個數;在二叉樹中查找某一結點;層序遍歷;判斷是否為完全二叉樹)

typedef struct TreeNode {struct TreeNode *left;struct TreeNode *right;char val; }TreeNode;typedef struct Result{TreeNode * root; //構建的樹的根結點int used; //構建過程中用掉的val個數 } Result;求二叉樹中所有結點個數 void TreeSize(TreeNode *root, int …