【Linux】IO多路復用——select,poll,epoll的概念和使用,三種模型的特點和優缺點,epoll的工作模式

文章目錄

  • Linux多路復用
    • 1. select
      • 1.1 select的概念
      • 1.2 select的函數使用
      • 1.3 select的優缺點
    • 2. poll
      • 2.1 poll的概念
      • 2.2 poll的函數使用
      • 2.3 poll的優缺點
    • 3. epoll
      • 3.1 epoll的概念
      • 3.2 epoll的函數使用
      • 3.3 epoll的優點
      • 3.4 epoll工作模式

Linux多路復用

??IO多路復用是一種操作系統的技術,用于在單個線程或進程中管理多個輸入輸出操作。它的主要目的是通過將多個IO操作合并到一個系統調用中來提高系統的性能和資源利用率,避免了傳統的多線程或多進程模型中因為阻塞IO而導致的資源浪費和低效率問題。

??在IO多路復用中,通常使用的系統調用有 select()、poll()、epoll() 等,它們允許程序等待多個文件描述符(sockets、文件句柄等)中的任何一個變為可讀或可寫,然后再進行實際的IO操作。這種模型相比于傳統的多線程或多進程模型,具有更高的并發處理能力和更低的系統開銷。

在這里插入圖片描述
??

1. select

1.1 select的概念

??系統提供select函數來實現多路復用輸入/輸出模型。

??select系統調用是用來讓我們的程序監視多個文件描述符的狀態變化的;

??程序會停在select這里等待,直到被監視的文件描述符有一個或多個發生了狀態改變。

在這里插入圖片描述

??

1.2 select的函數使用

 int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);

函數參數:

??nfds:是需要監視的最大的文件描述符值+1。

??readfds:需要檢測的可讀文件描述符的集合。

??writefds:需要檢測的可寫文件描述符的集合。

??exceptfds:需要檢測的異常文件描述符的集合。

??timeout:為結構體timeval,用來設置select()的等待時間;

??當timeout等于NULL:則表示select()沒有timeout,select將一直被阻塞,直到某個文件描述符上發生了事件;

??當timeout為0:僅檢測描述符集合的狀態,然后立即返回,并不等待外部事件的發生。

??當timeout為特定的時間值:如果在指定的時間段里沒有事件發生,select將超時返回。

??其中的可讀,可寫,異常文件描述符的集合是一個fd_set類型,fd_set是系統提供的位圖類型,位圖的位置是否是1,表示是否關系該事件。

??例如:

????輸入時:假如我們要關心 0 1 2 3 文件描述符

????0000 0000->0000 1111 比特位的位置,表示文件描述符的編號
???????? 比特位的內容 0or1 表示是否需要內核關心

????輸出時:

????0000 0100->此時表示文件描述符的編號
???????? 比特位的內容 0or1哪些用戶關心的fd 上面的讀事件已經就緒了,這里表示2描述符就緒了

??

??系統提供了關于fd_set的接口,便于我們使用位圖:

 void FD_CLR(int fd, fd_set *set); // 用來清除描述詞組set中相關fd 的位int FD_ISSET(int fd, fd_set *set); // 用來測試描述詞組set中相關fd 的位是否為真void FD_SET(int fd, fd_set *set); // 用來設置描述詞組set中相關fd的位void FD_ZERO(fd_set *set); // 用來清除描述詞組set的全部位

??

函數返回值:

??執行成功則返回文件描述詞狀態已改變的個數。

??如果返回0代表在描述詞狀態改變前已超過timeout時間,沒有返回。

??當有錯誤發生時則返回-1,錯誤原因存于errno,此時參數readfds,writefds, exceptfds和timeout的值變成不可預測。

??錯誤值可能為:

??EBADF 文件描述詞為無效的或該文件已關閉
??EINTR 此調用被信號所中斷
??EINVAL 參數n 為負值。
??ENOMEM 核心內存不足

??

select的執行過程:

??(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)阻塞等待,表示最大文件描述符+1是6,監控可讀事件,立即返回。

??(5)若fd=1,fd=2上都發生可讀事件,則select返回,此時set變為0000,0011。注意:沒有事件發生的fd=5被清空。

??

1.3 select的優缺點

select的特點:

??(1)可監控的文件描述符個數取決與sizeof(fd_set)的值。一般大小是1024,但是fd_set的大小可以調整。

??(2)將fd加入select監控集的同時,還要再使用一個數據結構array保存放到select監控集中的fd。

????1. 是用于再select 返回后,array作為源數據和fd_set進行FD_ISSET判斷。

????2. 是select返回后會把以前加入的但并無事件發生的fd清空,則每次開始select前都要重新從array取得fd逐一加入(FD_ZERO最先),掃描array的同時取得fd最大值maxfd,用于select的第一個參數。

??

select缺點

??(1)每次調用select, 都需要手動設置fd集合, 從接口使用角度來說也非常不便。

??(2)每次調用select,都需要把fd集合從用戶態拷貝到內核態,這個開銷在fd很多時會很大。

??(3)同時每次調用select都需要在內核遍歷傳遞進來的所有fd,這個開銷在fd很多時也很大。

??(4)select支持的文件描述符數量太小。

??

select使用代碼:

#pragma once#include <iostream>
#include <sys/select.h>
#include <sys/time.h>
#include "Socket.hpp"using namespace std;static const uint16_t defaultport = 888;
static const int fd_num_max = (sizeof(fd_set) * 8);
int defaultfd = -1;class SelectServer
{
public:SelectServer(uint16_t port = defaultport) : _port(port){for (int i = 0; i < fd_num_max; i++){fd_array[i] = defaultfd;// std::cout << "fd_array[" << i << "]" << " : " << fd_array[i] << std::endl;}}bool Init(){_listensock.Socket();_listensock.Bind(_port);_listensock.Listen();return true;}void Accepter(){// 我們的連接事件就緒了std::string clientip;uint16_t clientport = 0;int sock = _listensock.Accept(&clientip, &clientport); // 會不會阻塞在這里?不會if (sock < 0) return;lg(Info, "accept success, %s: %d, sock fd: %d", clientip.c_str(), clientport, sock);// sock -> fd_array[]int pos = 1;for (; pos < fd_num_max; pos++) // 第二個循環{if (fd_array[pos] != defaultfd)continue;elsebreak;}if (pos == fd_num_max){lg(Warning, "server is full, close %d now!", sock);close(sock);}else{fd_array[pos] = sock;PrintFd();// TODO}}void Recver(int fd, int pos){// demochar buffer[1024];ssize_t n = read(fd, buffer, sizeof(buffer) - 1); // bug?if (n > 0){buffer[n] = 0;cout << "get a messge: " << buffer << endl;}else if (n == 0){lg(Info, "client quit, me too, close fd is : %d", fd);close(fd);fd_array[pos] = defaultfd; // 這里本質是從select中移除}else{lg(Warning, "recv error: fd is : %d", fd);close(fd);fd_array[pos] = defaultfd; // 這里本質是從select中移除}}void Dispatcher(fd_set &rfds){for (int i = 0; i < fd_num_max; i++) // 這是第三個循環{int fd = fd_array[i];if (fd == defaultfd)continue;if (FD_ISSET(fd, &rfds)){if (fd == _listensock.Fd()){Accepter(); // 連接管理器}else // non listenfd{Recver(fd, i);}}}}void Start(){int listensock = _listensock.Fd();fd_array[0] = listensock;for (;;){fd_set rfds;FD_ZERO(&rfds);int maxfd = fd_array[0];for (int i = 0; i < fd_num_max; i++) // 第一次循環{if (fd_array[i] == defaultfd)continue;FD_SET(fd_array[i], &rfds);if (maxfd < fd_array[i]){maxfd = fd_array[i];lg(Info, "max fd update, max fd is: %d", maxfd);}}// accept?不能直接accept!檢測并獲取listensock上面的事件,新連接到來,等價于讀事件就緒// struct timeval timeout = {1, 0}; // 輸入輸出,可能要進行周期的重復設置struct timeval timeout = {0, 0}; // 輸入輸出,可能要進行周期的重復設置// 如果事件就緒,上層不處理,select會一直通知你!// select告訴你就緒了,接下來的一次讀取,我們讀取fd的時候,不會被阻塞// rfds: 輸入輸出型參數。 1111 1111 -> 0000 0000int n = select(maxfd + 1, &rfds, nullptr, nullptr, /*&timeout*/ nullptr);switch (n){case 0:cout << "time out, timeout: " << timeout.tv_sec << "." << timeout.tv_usec << endl;break;case -1:cerr << "select error" << endl;break;default:// 有事件就緒了,TODOcout << "get a new link!!!!!" << endl;Dispatcher(rfds); // 就緒的事件和fd你怎么知道只有一個呢???break;}}}void PrintFd(){cout << "online fd list: ";for (int i = 0; i < fd_num_max; i++){if (fd_array[i] == defaultfd)continue;cout << fd_array[i] << " ";}cout << endl;}~SelectServer(){_listensock.Close();}private:Sock _listensock;uint16_t _port;int fd_array[fd_num_max];   // 數組, 用戶維護的!// int wfd_array[fd_num_max];
};

??

2. poll

2.1 poll的概念

??poll和select實現原理基本類似,

??poll只為了解決select的兩個硬傷:

??1.等待的fd是有上限的,(底層類似鏈表儲存實現,而不是位圖)

??2.每次要對關心的fd進行事件重置,(pollfd結構包含了要監視的event和發生的event,使用前后不用初始化fd_set)

??

2.2 poll的函數使用

int poll(struct pollfd *fds, nfds_t nfds, int timeout);// pollfd結構
struct pollfd {int fd; /* file descriptor */short events; /* requested events */short revents; /* returned events */
};

函數參數解釋:

??fds:是一個poll函數監聽的結構列表. 每一個元素中, 包含了三部分內容: 文件描述符, 監聽的事件集合, 返回的事件集合。

??nfds:表示fds數組的長度。

??timeout:表示poll函數的超時時間, 單位是毫秒(ms)。

在這里插入圖片描述

返回結果:

??返回值小于0, 表示出錯。

??返回值等于0, 表示poll函數等待超時。

??返回值大于0, 表示poll由于監聽的文件描述符就緒而返回。

??

2.3 poll的優缺點

poll的優點

??(1)pollfd結構包含了要監視的event和發生的event,不再使用select“參數-值”傳遞的方式. 接口使用比 select更方便。

??(2)poll并沒有最大數量限制 (但是數量過大后性能也是會下降)。

poll的缺點

??(1)和select函數一樣,poll返回后,需要輪詢pollfd來獲取就緒的描述符。

??(2)每次調用poll都需要把大量的pollfd結構從用戶態拷貝到內核中。

??(3)同時連接的大量客戶端在一時刻可能只有很少的處于就緒狀態, 因此隨著監視的描述符數量的增長, 其效率也會線性下降。

??

poll使用代碼:

#pragma once#include <iostream>
#include <poll.h>
#include <sys/time.h>
#include "../select/Socket.hpp"using namespace std;static const uint16_t defaultport = 8888;
static const int fd_num_max = 64;
int defaultfd = -1;
int non_event = 0;class PollServer
{
public:PollServer(uint16_t port = defaultport) : _port(port){for (int i = 0; i < fd_num_max; i++){_event_fds[i].fd = defaultfd;_event_fds[i].events = non_event;_event_fds[i].revents = non_event;// std::cout << "fd_array[" << i << "]" << " : " << fd_array[i] << std::endl;}}bool Init(){_listensock.Socket();_listensock.Bind(_port);_listensock.Listen();return true;}void Accepter(){// 我們的連接事件就緒了std::string clientip;uint16_t clientport = 0;int sock = _listensock.Accept(&clientip, &clientport); // 會不會阻塞在這里?不會if (sock < 0) return;lg(Info, "accept success, %s: %d, sock fd: %d", clientip.c_str(), clientport, sock);// sock -> fd_array[]int pos = 1;for (; pos < fd_num_max; pos++) // 第二個循環{if (_event_fds[pos].fd != defaultfd)continue;elsebreak;}if (pos == fd_num_max){lg(Warning, "server is full, close %d now!", sock);close(sock);// 擴容}else{// fd_array[pos] = sock;_event_fds[pos].fd = sock;_event_fds[pos].events = POLLIN;_event_fds[pos].revents = non_event;PrintFd();// TODO}}void Recver(int fd, int pos){// demochar buffer[1024];ssize_t n = read(fd, buffer, sizeof(buffer) - 1); // bug?if (n > 0){buffer[n] = 0;cout << "get a messge: " << buffer << endl;}else if (n == 0){lg(Info, "client quit, me too, close fd is : %d", fd);close(fd);_event_fds[pos].fd = defaultfd; // 這里本質是從select中移除}else{lg(Warning, "recv error: fd is : %d", fd);close(fd);_event_fds[pos].fd = defaultfd; // 這里本質是從select中移除}}void Dispatcher(){for (int i = 0; i < fd_num_max; i++) // 這是第三個循環{int fd = _event_fds[i].fd;if (fd == defaultfd)continue;if (_event_fds[i].revents & POLLIN){if (fd == _listensock.Fd()){Accepter(); // 連接管理器}else // non listenfd{Recver(fd, i);}}}}void Start(){_event_fds[0].fd = _listensock.Fd();_event_fds[0].events = POLLIN;int timeout = 3000; // 3sfor (;;){int n = poll(_event_fds, fd_num_max, timeout);switch (n){case 0:cout << "time out... " << endl;break;case -1:cerr << "poll error" << endl;break;default:// 有事件就緒了,TODOcout << "get a new link!!!!!" << endl;Dispatcher(); // 就緒的事件和fd你怎么知道只有一個呢???break;}}}void PrintFd(){cout << "online fd list: ";for (int i = 0; i < fd_num_max; i++){if (_event_fds[i].fd == defaultfd)continue;cout << _event_fds[i].fd << " ";}cout << endl;}~PollServer(){_listensock.Close();}private:Sock _listensock;uint16_t _port;struct pollfd _event_fds[fd_num_max]; // 數組, 用戶維護的!// struct pollfd *_event_fds;// int fd_array[fd_num_max];// int wfd_array[fd_num_max];
};

??

3. epoll

3.1 epoll的概念

在這里插入圖片描述

在這里插入圖片描述

??epoll: 是為處理大批量句柄而作了改進的poll(真的是大改進)。

??epoll是IO多路復用技術,在實現上維護了一個用于返回觸發事件的Socket的鏈表和一個記錄監聽事件的紅黑樹,epoll的高效體現在:

??(1)對監聽事件的修改是 log N(紅黑樹)。

??(2)用戶程序無需遍歷所有的Socket(發生事件的Socket被放到鏈表中直接返回)。

??(3)內核無需遍歷所有的套接字,內核使用回調函數在事件發生時直接轉到對應的處理函數。

??

3.2 epoll的函數使用

??epoll 有3個相關的系統調用:

epoll_create

int epoll_create(int size);

??創建一個epoll的句柄,自從linux2.6.8之后,size參數是被忽略的,用完之后, 必須調用close()關閉。

??

epoll_ctl

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

epoll的事件注冊函數:

??它不同于select()是在監聽事件時告訴內核要監聽什么類型的事件, 而是在這里先注冊要監聽的事件類型。

??第一個參數是epoll_create()的返回值(epoll的句柄)。

??第二個參數表示動作,用三個宏來表示。

??第三個參數是需要監聽的fd。

??第四個參數是告訴內核需要監聽什么事。

第二個參數的取值:

??EPOLL_CTL_ADD :注冊新的fd到epfd中。

??EPOLL_CTL_MOD :修改已經注冊的fd的監聽事件。

??EPOLL_CTL_DEL :從epfd中刪除一個fd。

struct epoll_event結構如下:

在這里插入圖片描述

??

events可以是以下幾個宏的集合:

??EPOLLIN : 表示對應的文件描述符可以讀 (包括對端SOCKET正常關閉)。

??EPOLLOUT : 表示對應的文件描述符可以寫。

??EPOLLPRI : 表示對應的文件描述符有緊急的數據可讀 (這里應該表示有帶外數據到來)。

??EPOLLERR : 表示對應的文件描述符發生錯誤。

??EPOLLHUP : 表示對應的文件描述符被掛斷。

??EPOLLET : 將EPOLL設為邊緣觸發(Edge Triggered)模式, 這是相對于水平觸發(Level Triggered)來說的。

??EPOLLONESHOT:只監聽一次事件, 當監聽完這次事件之后, 如果還需要繼續監聽這個socket的話, 需要再次把這個socket加入到EPOLL隊列里。

??

epoll_wait

int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);

收集在epoll監控的事件中已經發送的事件:

??參數events是分配好的epoll_event結構體數組。

??epoll將會把發生的事件賦值到events數組中 (events不可以是空指針,內核只負責把數據復制到這個events數組中,不會去幫助我們在用戶態中分配內存)。

??maxevents告之內核這個events有多大,這個 maxevents的值不能大于創建epoll_create()時的size。

??參數timeout是超時時間 (毫秒,0會立即返回,-1是永久阻塞)。

??如果函數調用成功,返回對應I/O上已準備好的文件描述符數目,如返回0表示已超時, 返回小于0表示函數失敗。

??

epoll原理:

在這里插入圖片描述

??(1)當某一進程調用epoll_create方法時,Linux內核會創建一個eventpoll結構體,這個結構體中有兩個成員與epoll的使用方式密切相關。

??(2)每一個epoll對象都有一個獨立的eventpoll結構體,用于存放通過epoll_ctl方法向epoll對象中添加進來的事件。

??(3)這些事件都會掛載在紅黑樹中,如此,重復添加的事件就可以通過紅黑樹而高效的識別出來(紅黑樹的插入時間效率是lgn,其中n為樹的高度)。

??(4)而所有添加到epoll中的事件都會與設備(網卡)驅動程序建立回調關系,也就是說,當響應的事件發生時會調用這個回調方法。

??(5)這個回調方法在內核中叫ep_poll_callback,它會將發生的事件添加到rdlist雙鏈表中。

??(6)在epoll中,對于每一個事件,都會建立一個epitem結構體。

??(7)當調用epoll_wait檢查是否有事件發生時,只需要檢查eventpoll對象中的rdlist雙鏈表中是否有epitem元素即可。

??(8)如果rdlist不為空,則把發生的事件復制到用戶態,同時將事件數量返回給用戶. 這個操作的時間復雜度是O(1)。
??

struct eventpoll{ .... /*紅黑樹的根節點,這顆樹中存儲著所有添加到epoll中的需要監控的事件*/ struct rb_root rbr; /*雙鏈表中則存放著將要通過epoll_wait返回給用戶的滿足條件的事件*/ struct list_head rdlist; .... 
};
struct epitem{ struct rb_node rbn;//紅黑樹節點 struct list_head rdllink;//雙向鏈表節點 struct epoll_filefd ffd; //事件句柄信息 struct eventpoll *ep; //指向其所屬的eventpoll對象 struct epoll_event event; //期待發生的事件類型 
}

總結一下, epoll的使用過程簡單看就三步:

??(1)調用epoll_create創建一個epoll句柄。

??(2)調用epoll_ctl, 將要監控的文件描述符進行注冊。

??(3)調用epoll_wait, 等待文件描述符就緒。

??

3.3 epoll的優點

??(1)接口使用方便: 雖然拆分成了三個函數,但是反而使用起來更方便高效,不需要每次循環都設置關注的文件描述符,也做到了輸入輸出參數分離開。

??(2)數據拷貝輕量: 只在合適的時候調用 EPOLL_CTL_ADD 將文件描述符結構拷貝到內核中,這個操作并不頻繁(而select/poll都是每次循環都要進行拷貝)。

??(3)事件回調機制: 避免使用遍歷,而是使用回調函數的方式,將就緒的文件描述符結構加入到就緒隊列中,epoll_wait 返回直接訪問就緒隊列就知道哪些文件描述符就緒,這個操作時間復雜度O(1),即使文件描述符數目很多,效率也不會受到影響。

??(4)沒有數量限制: 文件描述符數目無上限。

??

3.4 epoll工作模式

??epoll默認:LT模式,事件到來但是上層不處理,高電平,一直有效。
??? ? ? ? ET模式,數據或者連接,從無到有,從有到多,變化的時候才通知我們一次。

??ET的通知效率更高:倒逼程序員,每次通知都必須把本輪的數據取走 -> 循環讀取,讀取錯誤 -> fd默認是阻塞的 -> ET,所有的fd必須是非阻塞的。

??ET的IO效率也更高 -> tcp會向對方通告一個更大的窗口,從而概率上讓對方一次給我發生更多數據,如果LT每次也可以就緒,那效率差不多。

??本質就是向就緒隊列,添加一次或者是多次就緒節點。

??

Epoller.hpp Epoller對epoll進行封裝

#pragma once#include "nocopy.hpp"
#include <sys/epoll.h>
#include "Log.hpp"
#include <cstring>
#include <cerrno>//封裝我們的epoll,epoll公有繼承于我們的nocopy類,不能被拷貝
class Epoller: public nocopy 
{static const int size=128;public:Epoller(){_epfd=epoll_create(size);if(_epfd<0){lg(Error,"epoll_create error: %s",strerror(errno));}else{lg(Info,"epoll_create success: %d",_epfd);}}//進行epoll事件等待//返回的是就緒事件的數量int EpollerWait(struct epoll_event revents[], int num){int n=epoll_wait(_epfd,revents,num,-1/*_timeout*/);return n;}//我們所要更新的時間操作和套接字監控的事件int EpollerUpdate(int oper, int sock, uint32_t event){int n=0;if(oper==EPOLL_CTL_DEL) //刪除操作{n=epoll_ctl(_epfd,oper,sock,nullptr);if(n!=0){lg(Error,"epoll_ctl delete error!");}}else //新增和修改{struct epoll_event ev;ev.events=event;ev.data.fd=sock; //傳入sock,方便我們知道是哪一個fd就緒//完成了我們對于哪一個文件和那一個文件的描述符進行事件關心//接下來進行注冊n=epoll_ctl(_epfd,oper,sock,&ev);if(n!=0){lg(Error,"epoll_ctl error!");}}return n;}~Epoller(){if(_epfd>0){close(_epfd);}}private:int _epfd;int _timeout{3000};
};

??

EpollServer.hpp Epoll服務器

#pragma once#include <iostream>
#include <memory>
#include <sys/epoll.h>
#include "Socket.hpp"
#include "Epoller.hpp"
#include "Log.hpp"
#include "nocopy.hpp"uint32_t EVENT_IN = (EPOLLIN); //表示更新讀事件
uint32_t EVENT_OUT = (EPOLLOUT); //表示更新寫事件class EpollServer : public nocopy
{static const int num = 64;public:EpollServer(uint16_t port): _port(port),_listsocket_ptr(new Sock()),_epoll_ptr(new Epoller()){}void Init(){_listsocket_ptr->Socket();_listsocket_ptr->Bind(_port);_listsocket_ptr->Listen();lg(Info,"create listen socket success: %d\n",_listsocket_ptr->Fd());}void Accepter(){//獲取了一個連接   std::string clientip;uint16_t clientport;int sock=_listsocket_ptr->Accept(&clientip,&clientport);if(sock>0){//我們不能直接讀取數據//ssize_t n=read(sock,...);_epoll_ptr->EpollerUpdate(EPOLL_CTL_ADD,sock,EVENT_IN);lg(Info,"get a new link, client info @ %s:%d",clientip.c_str(),clientport);}}void Recver(int fd){// demochar buffer[1024];ssize_t n = read(fd, buffer, sizeof(buffer) - 1); // bug?if (n > 0){buffer[n] = 0;std::cout << "get a messge: " << buffer << std::endl;// wrirtestd::string echo_str = "server echo $ ";echo_str += buffer;write(fd, echo_str.c_str(), echo_str.size());}else if (n == 0){lg(Info, "client quit, me too, close fd is : %d", fd);//細節3_epoll_ptr->EpollerUpdate(EPOLL_CTL_DEL, fd, 0);close(fd);}else{lg(Warning, "recv error: fd is : %d", fd);_epoll_ptr->EpollerUpdate(EPOLL_CTL_DEL, fd, 0);close(fd);}}void Dispatcher(struct epoll_event revs[], int num){//遍歷獲取文件描述符中已經就緒的事件for(int i=0;i<num;i++){uint32_t events=revs[i].events;int fd=revs[i].data.fd;if(events & EVENT_IN) //判斷事件類型,這是讀事件就緒{if(fd==_listsocket_ptr->Fd()){//獲取了一個連接   Accepter();}else {//其他fd上面的普通讀取事件就緒Recver(fd);}}else if(events & EVENT_OUT) //寫事件就緒{}else {}}}//開始我們的epoll事件監聽void Start(){//將listensock添加到epoll中 -> listensock和他關心的事件,添加到內核epoll模型的rb_tree_epoll_ptr->EpollerUpdate(EPOLL_CTL_ADD,_listsocket_ptr->Fd(),EVENT_IN);//我們將我們的監聽套接字listsocket給epoll進行讀事件管理,接下來由紅黑樹自動關心我們的事件struct epoll_event revs[num];for(;;){int n=_epoll_ptr->EpollerWait(revs,num);if(n>0){//有事件就緒lg(Debug,"event happend, fd is %d",revs[0].data.fd);//處理就緒事件Dispatcher(revs,n);}else if(n==0){lg(Info,"time out...");}else{lg(Error,"epoll wait error");}}}~EpollServer(){_listsocket_ptr->Close();}private:std::shared_ptr<Sock> _listsocket_ptr;std::shared_ptr<Epoller> _epoll_ptr;uint16_t _port;
};

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

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

相關文章

MCU復位時GPIO是什么狀態?

大家一定遇到過上電或者復位時外部的MOS電路或者芯片使能信號意外開啟&#xff0c;至此有經驗的工程師就會經常關心一個問題&#xff0c;MCU復位時GPIO是什么狀態&#xff1f;什么電路需要外部加上下拉&#xff1f; MCU從上電到啟動&#xff0c;實際可分為復位前和復位后、初始…

【WPF】Windows系統桌面應用程序編程開發新手入門-打造自己的小工具

電腦Windows系統上的桌面程序通常是用Visual Studio 開發工具編寫出來的&#xff0c;有兩種開發方式供選擇&#xff0c;一種是WindowForm&#xff0c;簡稱WinForm&#xff0c;另一種是Windows Presentation Foundation&#xff0c;簡稱WPF&#xff0c;這里將學習WPF項目。 文章…

大物3錯題整理

平衡位置&#xff1a;在O點上的位置 相位&#xff1a; 當N很大的時候&#xff0c;wxwywz。因此&#xff0c;平均平動動能除以3&#xff0c;就是能量均分定理。 W F在x上的積分 Π時無單位 180&#xff0c;就是單位 1rad&#xff0c;rad就是單位 左手定則、右手定則、安培定…

C++模板類與繼承

1&#xff09;模板類繼承普通類&#xff08;常見&#xff09;。 2&#xff09;普通類繼承模板類的實例化版本。 3&#xff09;普通類繼承模板類。(常見) 4&#xff09;模板類繼承模板類。 5&#xff09;模板類繼承模板參數給出的基類&#xff08;不能是模板類&#xff09;。 示…

【抽代復習筆記】24-群(十八):循環群的兩道例題

例1&#xff1a;證明&#xff1a; &#xff08;1&#xff09;三次交錯群A3是循環群&#xff0c;它與(Z3,)同構&#xff0c;其中Z3 {[0],[1],[2]}&#xff1b; &#xff08;2&#xff09;G {1,i,-1,-i}&#xff0c;G上的代數運算是數的乘法&#xff0c;則G是一個循環群&…

如何解決三菱軟件提示 起動MELSOFT Mediative Server失敗

前言&#xff1a; 注意&#xff0c;這篇文章僅針對如何解決 起動MELSOFT Mediative Server失敗 的問題。對于其他相關的問題&#xff0c;請搜索其他相應的解決辦法。 本人是在重裝三菱GX Works軟件時遇到此問題的。后來搜索發現無人能妥善的關閉這個提示。因此本文介紹如何關…

【Web3項目案例】Ethers.js極簡入門+實戰案例:實現ERC20協議代幣查詢、交易

蘇澤 大家好 這里是蘇澤 一個鐘愛區塊鏈技術的后端開發者 本篇專欄 ←持續記錄本人自學智能合約學習筆記和經驗總結 如果喜歡拜托三連支持~ 目錄 簡介 前景科普-ERC20 Ethers極簡入門教程&#xff1a;HelloVitalik&#xff08;非小白可跳&#xff09; 教程概覽 開發工具 V…

魔行觀察-烤匠麻辣烤魚-開關店監測-時間段:2011年1月 至 2024年6月

今日監測對象&#xff1a;烤匠麻辣烤魚&#xff0c;監測時間段&#xff1a;2011年1月 至 2024年6月 本文用到數據源獲取地址 魔行觀察http://www.wmomo.com/ 品牌介紹&#xff1a; 2013年&#xff0c;第一家烤匠在成都藍色加勒比廣場開業&#xff0c;隨后幾年成都國金中心店…

超詳細的tomcat安裝以及簡略項目的部署

一、安裝包 安裝路徑&#xff1a; 鏈接&#xff1a;https://pan.baidu.com/s/1JzPQQ2zUdnXi_FaTTG0pvg?pwdriht 提取碼&#xff1a;riht 安裝完之后我們打開&#xff0c;可看見以下目錄結構 二、環境變量配置 首先打開我們電腦的高級環境變量配置 我們先配置一個系統變量…

Variables Reference for vscode

Predefined variables Visual Studio Code 支持在調試、任務配置文件以及一些特定的設置中使用變量替換。這些變量可以使用 ${variableName} 語法在 launch.json 和 tasks.json 文件的某些鍵和值字符串中使用。 Predefined variables Visual Studio Code 支持以下預定義變量…

Zookeeper:Zookeeper JavaAPI操作與分布式鎖

文章目錄 一、Zookeeper JavaAPI操作1、Curator介紹2、創建、查詢、修改、刪除節點3、Watch事件監聽 二、Zookeeper分布式鎖原理 一、Zookeeper JavaAPI操作 1、Curator介紹 Curator是Apache Zookeeper的Java客戶端。常見的Zookeeper Java API&#xff1a; 原生Java API。ZkC…

天氣網站爬蟲及可視化

摘要&#xff1a;隨著互聯網的快速發展&#xff0c;人們對天氣信息的需求也越來越高。本論文基于Python語言&#xff0c;設計并實現了一個天氣網站爬蟲及可視化系統。該系統通過網絡爬蟲技術從多個天氣網站上獲取實時的天氣數據&#xff0c;并將數據進行清洗和存儲。同時&#…

數據倉庫面試題(二)

1. 簡述星型模型和雪花模型的區別&#xff1f;應用場景 &#xff1f; 星型模型&#xff08;Star Schema&#xff09;和雪花模型&#xff08;Snowflake Schema&#xff09;是數據倉庫中常用的兩種維度建模方法&#xff0c;它們在數據組織和設計上有所不同。 星型模型&#xff…

【簡易版tinySTL】 哈希表與移動語義

基本概念 哈希表&#xff08;HashTable&#xff09;是一個重要的底層數據結構, 無序關聯容器包括unordered_set, unordered_map內部都是基于哈希表實現。 哈希表是一種通過哈希函數將鍵映射到索引的數據結構&#xff0c;存儲在內存空間中。哈希函數負責將任意大小的輸入映射到…

【C++】內存分區

目錄 內存分區代碼運行前后區別各分區詳細解釋C內存申請和釋放 內存分區 不同的操作系統對程序內存的管理和劃分會有所不同。 此處是C內存區域劃分主要是針對通用的情況&#xff0c;并不限定在某個特定操作系統上 一般分為4個區&#xff08;有時把全局區拆分成數據區未初始化…

git 命令學習之branch 和 tag 操作

引言 在項目一個迭代過程結束之時&#xff0c;或是一個版本發布之后&#xff0c;我們要進行 新版本的開發&#xff0c;這時就需要對原來的項目代碼進行封存&#xff0c;以及新項目代碼的開始&#xff0c;這時就需要用到 branch 和 tag 操作。下面簡單說說對這兩個操作的理解。…

微服務之服務保護策略【持續更新】

文章目錄 線程隔離一、滑動窗口算法二、漏桶算法三、令牌桶算法 面試題1、Sentinel 限流和Gateway限流的區別 線程隔離 兩種實現方式 線程池隔離&#xff08;Hystix隔離&#xff09;&#xff0c;每個被隔離的業務都要創建一個獨立的線程池&#xff0c;線程過多會帶來額外的CPU…

【C語言】C語言-體育彩票的模擬生成和兌獎(源碼+論文)【獨一無二】

&#x1f449;博__主&#x1f448;&#xff1a;米碼收割機 &#x1f449;技__能&#x1f448;&#xff1a;C/Python語言 &#x1f449;公眾號&#x1f448;&#xff1a;測試開發自動化【獲取源碼商業合作】 &#x1f449;榮__譽&#x1f448;&#xff1a;阿里云博客專家博主、5…

【涵子來信科技潮流】——WWDC24回顧與暑假更新說明

期末大關&#xff0c;即將來襲。在期末之前&#xff0c;我想發一篇文章&#xff0c;介紹有關WWDC24的內容和暑假中更新的說明。本篇文章僅為個人看法和分享&#xff0c;如需了解更多詳細內容&#xff0c;請通過官方渠道或者巨佬文章進行進一步了解。 OK, Lets go. 一、WWDC24 …

Linux grep技巧 刪除含有指定關鍵詞的行,創建新文件

一. 需求 ?有如下文件&#xff0c;現要求 刪除含有xuecheng關鍵字的行刪除含有192.168.1.1關鍵字的行也就是說&#xff0c;最終只會留下127.0.0.1 license.sublimehq.com 127.0.0.1 www.xuecheng.com 127.0.0.1 img.xuecheng.com 192.168.1.1 www.test.com 127.0.0.1 video…