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

epoll:

Linux下性能最高的多路轉接模型

epoll 有3個相關的系統調用.

epoll_create

功能:創建epoll,在內核中創建eventpoll結構體,size決定了epoll最多監控多少個描述符,在Linux2.6.8之后被忽略,但是必須>0。返回一個文件描述符,作為epoll的操作句柄

struct eventpoll{
...rb_root rbr(紅黑樹)...struct list_head rdlist(雙向鏈表)...
}
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) 

功能:對內核中的eventpoll 結構體進行操作:epoll采用事件結構方式對描述符進行事件監控;用戶定義struct epoll_event描述符事件結構信息;將事件信息可以拷貝到內核添加到eventpoll結構體中的紅黑數中

參數說明
  • 它不同于select()是在監聽事件時告訴內核要監聽什么類型的事件, 而是在這里先注冊要監聽的事件類型.
  • 第一個參數是epoll_create()的返回值(epoll的句柄).
  • 第二個參數表示動作,用三個宏來表示. 、
  • 第三個參數是需要監聽的fd.
  • 第四個參數是告訴內核需要監聽什么事. 描述符對應的事件結構信息
第二個參數的取值:
  • EPOLL_CTL_ADD :注冊新的fd到epfd中;向紅黑數中添加描述符的監控事件結構信息event
  • EPOLL_CTL_MOD :修改已經注冊的fd的監聽事件;修改描述符在紅黑數中的對應事件結構信息event
  • EPOLL_CTL_DEL :從epfd中刪除一個fd,從紅黑數中移除描述符的監控事件結構信息event

struct epoll_event結構如下

struct epoll_event { 
uint32_t events; /* 用戶對描述符進行監控的事件 */ 
epoll_data_t data; /* User data variable */ 
};typedef union epoll_data { void *ptr; int fd; uint32_t u32; uint64_t u64; } epoll_data_t;

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

  1. EPOLLIN : 表示對應的文件描述符可以讀 (包括對端SOCKET正常關閉);
  2. EPOLLOUT : 表示對應的文件描述符可以寫;
  3. EPOLLPRI : 表示對應的文件描述符有緊急的數據可讀 (這里應該表示有帶外數據到來);
  4. EPOLLERR : 表示對應的文件描述符發生錯誤;
  5. EPOLLHUP : 表示對應的文件描述符被掛斷;
  6. EPOLLET : 將EPOLL設為邊緣觸發(Edge Triggered)模式, 這是相對于水平觸發(Level Triggered)來說的.
  7. EPOLLONESHOT:只監聽一次事件, 當監聽完這次事件之后, 如果還需要繼續監聽這個socket的話, 需要 再次把這個socket加入到EPOLL隊列里.

epoll_wait

int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout) 
  • epfd: epoll 操作句柄
  • events:epoll_event 事件結構信息數組的結點數量
  • maxevents : epoll_event事件結構信息數組的結點數量
  • timeout:epoll_wait 監控的等待超時時間
  • 返回值:<0----監控出錯 ==0----監控超時 >0----就緒的描述符事件個數
    收集在epoll監控的事件中已經發送的事件
  1. 參數events是分配好的epoll_event結構體數組.
  2. epoll將會把發生的事件賦值到events數組中 (events不可以是空指針,內核只負責把數據復制到這個 events數組中,不會去幫助我們在用戶態中分配內存).
  3. maxevents告之內核這個events有多大,這個 maxevents的值不能大于創建epoll_create()時的size.
  4. 參數timeout是超時時間 (毫秒,0會立即返回,-1是永久阻塞).
  5. 如果函數調用成功,返回對應I/O上已準備好的文件描述符數目,如返回0表示已超時, 返回小于0表示函 數失敗

epoll_wait 會將就緒的描述符對應事件結構信息拷貝到events結構數組中;相當于直接告訴用戶哪個描述符就緒;用戶直接就從epoll_event 結構體數組中取出信息,對描述符直接進行相應事件操作

epoll監控流程

在這里插入圖片描述

  1. epoll對描述符的事件監控是一個異步操作;epoll_wait發起調用,讓操作系統對描述符進行相應事件監控
  2. 操作系統對每個要監控的描述符都定義了就緒事件回調函數;當描述符相應事件就緒的時候,觸發事件,調用回調函數(將描述符事件結構信息指針添加到eventpoll的雙向鏈表中)
  3. 但是epoll_wait并沒有直接返回(是一個阻塞操作),每隔一會就看一下eventpoll中雙向鏈表是否為空;來判斷是否有描述符就緒;若為空,則沒有描述符就緒;則等待一會,重新查看
  4. 若雙向鏈表不為空------表示有描述符事件就緒;將這個描述符對應的事件結構信息,拷貝到epoll_wait傳入的事件結構數組中后調用返回。

epoll工作原理

struct eventpoll{
...rb_root rbr(紅黑樹)...struct list_head rdlist(雙向鏈表)...
}

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

  • 每一個epoll對象都有一個獨立的eventpoll結構體,用于存放通過epoll_ctl方法向epoll對象中添加進來 的事件.
  • 這些事件都會掛載在紅黑樹中,如此,重復添加的事件就可以通過紅黑樹而高效的識別出來(紅黑樹的插 入時間效率是lgn,其中n為樹的高度).
  • 而所有添加到epoll中的事件都會與設備(網卡)驅動程序建立回調關系,也就是說,當響應的事件發生時 會調用這個回調方法.
  • 這個回調方法在內核中叫ep_poll_callback,它會將發生的事件添加到rdlist雙鏈表中.
  • 在epoll中,對于每一個事件,都會建立一個epitem結構體.
    在這里插入圖片描述
  • 當調用epoll_wait檢查是否有事件發生時,只需要檢查eventpoll對象中的rdlist雙鏈表中是否有epitem 元素即可.
  • 如果rdlist不為空,則把發生的事件復制到用戶態,同時將事件數量返回給用戶. 這個操作的時間復雜度 是O(1).
/*                                                                                                                                                                                                               * 這個程序完成epoll接口的基本封裝* bool Init()* bool Add(TcpSocket &sock)* bool Del(TcpSocket &sock)* bool Wait(std::vector<TcpSocket>&list,int timeout_msec)*/#include<iostream>
#include<vector>
#include<sys/epoll.h>
#include"tcpsocket.hpp"#define MAX_EVENTS 10
class Epoll
{public:bool Init(){//int epoll_create(int size)_epfd = epoll_create(1);if(_epfd < 0){ perror("epoll create error");return false;}   return true;}   bool Add(TcpSocket &sock){//int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event) int fd = sock.GetFd();struct epoll_event ev; ev.data.fd = fd; ev.events = EPOLLIN;int ret = epoll_ctl(_epfd, EPOLL_CTL_ADD, fd , &ev);if(ret < 0){ perror("epoll add error");return false;}   return true;}   bool Del(TcpSocket &sock){int fd = sock.GetFd();int ret =epoll_ctl(_epfd, EPOLL_CTL_DEL, fd,NULL);if(ret < 0){perror("epoll del error");return false;}return true;}bool Wait(std::vector<TcpSocket>&list,int timeout_msec = 3000){//int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout) struct epoll_event evs[MAX_EVENTS];int nfds = epoll_wait(_epfd, evs, MAX_EVENTS, timeout_msec);if(nfds < 0){perror("epoll wait error");}else if(nfds == 0){std::cerr<<"epoll wait timeout";return false;}for(int i =0 ; i < nfds; i++ ){TcpSocket sock;sock.SetFd(evs[i].data.fd);list.push_back(sock);}return true;}private:int _epfd;};int  main()
{TcpSocket lst_sock;CHECK_RET(lst_sock.Socket());CHECK_RET(lst_sock.Bind("0.0.0.0",9000));CHECK_RET(lst_sock.Listen());Epoll epoll;CHECK_RET(epoll.Init());CHECK_RET(epoll.Add(lst_sock));while(1){std::vector<TcpSocket>list;bool ret = epoll.Wait(list);if(ret == false){            continue;}for(int i =0 ;i < list.size();i++){if(lst_sock.GetFd() == list[i].GetFd()){TcpSocket cli_sock;std::string cli_ip;uint16_t cli_port;ret = lst_sock.Accept(cli_sock,cli_ip,cli_port);if(ret == false){continue;}epoll.Add(cli_sock);}else{std::string buf;ret = list[i].Recv(buf);if(ret == false){//接收出錯epoll.Del(list[i]);list[i].Close();continue;}std::cout<< "client-say:"<< buf <<std::endl;}}}lst_sock.Close();return 0;
}

epoll事件觸發方式:

水平觸發–EPOLLT/邊緣觸發EPOLLET

水平觸發方式

可讀事件就緒:接受緩沖區數據大小,大于低水位標記(默認1字節)
可寫事件就緒:發送緩沖區中空閑大小,大于低水位標記(默認1字節)
只要接受/發送緩沖區中數據/剩余空間大小大于低水平標記就會一直觸發事件

邊緣觸發方式

可讀事件就緒:接受緩沖區中,只有新數據到來的時候才會觸發一次
可寫事件就緒:發送緩沖區中,只有從剩余空間大小從0變為大于0的時候才會觸發

注意事項

邊緣觸發方式中,只有新數據到來的時候,可讀事件才會觸發一次
需要用戶在這一次事件觸發中將緩沖區中的數據全部讀取完畢(循環讀,直到不能讀為止)
但是套接字默認recv沒有數據的時候會阻塞;為了避免循環讀取數據導致程序流程因為阻塞而無法繼續推進,因此需要將描述符設置為非阻塞

fcntl
int fcntl(int fd,int cmd, .../*arg*/);fd : 要設置的描述符cmd : 對描述符要進行的操作F_SETFL 通過arg參數設置描述符屬性狀態F_GETFL 返回描述符的屬性狀態信息 ,arg被忽略arg:要設置的描述符屬性狀態信息O_NONBLOCK  將描述符設置為非阻塞

epoll優缺點分析

  1. epoll采用事件結構方式對描述符進行監控,簡化了select集合操作的流程
  2. epoll描述符監控數量無上限
  3. 每個epoll監控的描述符事件信息,只需要向內核拷貝一次
  4. epoll_wait使用異步阻塞操作在內核中完成事件監控;事件監控是操作系統通過事件回調的方式就緒描述符事件信息添加到雙向鏈表中;而epoll_wait只是每隔一段時間看一下雙向鏈表是否為空判斷是否有描述符就緒(并非輪詢遍歷)性能不會隨著描述符增多而降低
  5. epoll直接通過epoll_wait傳入的時間結構數組向用戶返回就緒的事件信息;可以直接告訴用戶哪些描述符就緒,不需要用戶進行空遍歷查找

缺點

  1. 不能跨平臺

IO多路轉接模型的適用場景

對大量描述符進行監控,但是同一時間只有少量描述符活躍的場景

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

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

相關文章

再寫順序表(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 …

c++中的智能指針詳解(RAII, auto_ptr的原理及其改進,unique_ptr的原理,shared_ptr的原理,shared_ptr的缺陷及其改進)

為什么需要智能指針 代碼中途退出&#xff0c;也能保證資源的合理釋放&#xff0c;在c中沒有垃圾回收機制的情況下&#xff0c;智能指針就可以保證我們申請的資源&#xff0c;最后忘記釋放的問題&#xff0c;防止內存泄露&#xff0c;也幫我們減少了一定的負擔&#xff0c;不用…