Linux網絡編程——I/O復用函數之epoll

https://blog.csdn.net/lianghe_work/article/details/46544567

一、epoll概述

epoll 是在 2.6 內核中提出的,是之前的 select() 和 poll() 的增強版本。相對于 select() 和 poll() 來說,epoll 更加靈活,沒有描述符限制。epoll 使用一個文件描述符管理多個描述符,將用戶關系的文件描述符的事件存放到內核的一個事件表中,這樣在用戶空間和內核空間的 copy 只需一次。

二、epoll操作過程需要的四個接口函數

四接口函數分別是:

  1. #include <sys/epoll.h>
  2. int epoll_create(int size);
  3. int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
  4. int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
  5. int close(int epfd);

int epoll_create(int size);

功能:

該函數生成一個epoll專用的文件描述符(其余的接口函數一般都用使用這個專用的文件描述符)

參數:

size: 用來告訴內核這個監聽的數目一共有多大,參數 size 并不是限制了 epoll 所能監聽的描述符最大個數,只是對內核初始分配內部數據結構的一個建議。自從 linux 2.6.8 之后,size 參數是被忽略的,也就是說可以填只有大于 0 的任意值。需要注意的是,當創建好 epoll 句柄后,它就是會占用一個 fd 值,在 linux 下如果查看 /proc/ 進程 id/fd/,是能夠看到這個 fd 的,所以在使用完 epoll 后,必須調用 close() 關閉,否則可能導致 fd 被耗盡

返回值:

成功:epoll 專用的文件描述符
失敗:-1


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

功能:

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

參數:

epfd: epoll 專用的文件描述符,epoll_create()的返回值

op: 表示動作,用三個宏來表示:

EPOLL_CTL_ADD:注冊新的 fd 到 epfd 中;
EPOLL_CTL_MOD:修改已經注冊的fd的監聽事件;
EPOLL_CTL_DEL:從 epfd 中刪除一個 fd;

fd: 需要監聽的文件描述符

event: 告訴內核要監聽什么事件,struct epoll_event?結構如下:


  1. // 保存觸發事件的某個文件描述符相關的數據(與具體使用方式有關)
  2. typedef union epoll_data {
  3. void *ptr;
  4. int fd;
  5. __uint32_t u32;
  6. __uint64_t u64;
  7. } epoll_data_t;
  8. // 感興趣的事件和被觸發的事件
  9. struct epoll_event {
  10. __uint32_t events; /* Epoll events */
  11. epoll_data_t data; /* User data variable */
  12. };
events?可以是以下幾個宏的集合:

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

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

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

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

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

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

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

返回值:

成功:0
失敗:-1


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

功能:
等待事件的產生,收集在 epoll 監控的事件中已經發送的事件,類似于 select() 調用。
參數:
epfd: epoll 專用的文件描述符,epoll_create()的返回值
events: 分配好的 epoll_event 結構體數組,epoll 將會把發生的事件賦值到events 數組中(events 不可以是空指針,內核只負責把數據復制到這個 events 數組中,不會去幫助我們在用戶態中分配內存)。
maxevents: maxevents 告之內核這個 events 有多大 。
timeout: 超時時間,單位為毫秒,為 -1 時,函數為阻塞
返回值:
成功:返回需要處理的事件數目,如返回 0 表示已超時。
失敗:-1


epoll 對文件描述符的操作有兩種模式:LT(level trigger)和 ET(edge trigger)。LT 模式是默認模式,LT 模式與 ET 模式的區別如下:

LT 模式:當 epoll_wait 檢測到描述符事件發生并將此事件通知應用程序,應用程序可以不立即處理該事件。下次調用 epoll_wait 時,會再次響應應用程序并通知此事件。


ET 模式:當 epoll_wait 檢測到描述符事件發生并將此事件通知應用程序,應用程序必須立即處理該事件。如果不處理,下次調用 epoll_wait 時,不會再次響應應用程序并通知此事件。


ET 模式在很大程度上減少了 epoll 事件被重復觸發的次數,因此效率要比 LT 模式高。epoll 工作在 ET 模式的時候,必須使用非阻塞套接口,以避免由于一個文件句柄的阻塞讀/阻塞寫操作把處理多個文件描述符的任務餓死


int close(int epfd);

在用完之后,記得用close()來關閉這個創建出來的epoll句柄


三、epoll示例:

接下來我們epoll實現udp同時收發數據

  1. #include <string.h>
  2. #include <stdio.h>
  3. #include <stdlib.h>
  4. #include <unistd.h>
  5. #include <sys/select.h>
  6. #include <sys/time.h>
  7. #include <sys/socket.h>
  8. #include <netinet/in.h>
  9. #include <arpa/inet.h>
  10. #include <sys/epoll.h>
  11. int main(int argc,char *argv[])
  12. {
  13. int udpfd = 0;
  14. int ret = 0;
  15. struct sockaddr_in saddr;
  16. struct sockaddr_in caddr;
  17. bzero(&saddr,sizeof(saddr));
  18. saddr.sin_family = AF_INET;
  19. saddr.sin_port = htons(8000);
  20. saddr.sin_addr.s_addr = htonl(INADDR_ANY);
  21. bzero(&caddr,sizeof(caddr));
  22. caddr.sin_family = AF_INET;
  23. caddr.sin_port = htons(8000);
  24. //創建套接字
  25. if( (udpfd = socket(AF_INET,SOCK_DGRAM, 0)) < 0)
  26. {
  27. perror("socket error");
  28. exit(-1);
  29. }
  30. //套接字端口綁字
  31. if(bind(udpfd, (struct sockaddr*)&saddr, sizeof(saddr)) != 0)
  32. {
  33. perror("bind error");
  34. close(udpfd);
  35. exit(-1);
  36. }
  37. printf("input: \"sayto 192.168.220.X\" to sendmsg to somebody\033[32m\n");
  38. struct epoll_event event; // 告訴內核要監聽什么事件
  39. struct epoll_event wait_event;
  40. int epfd = epoll_create(10); // 創建一個 epoll 的句柄,參數要大于 0, 沒有太大意義
  41. if( -1 == epfd ){
  42. perror ("epoll_create");
  43. return -1;
  44. }
  45. event.data.fd = 0; // 標準輸入
  46. event.events = EPOLLIN; // 表示對應的文件描述符可以讀
  47. // 事件注冊函數,將標準輸入描述符 0 加入監聽事件
  48. ret = epoll_ctl(epfd, EPOLL_CTL_ADD, 0, &event);
  49. if(-1 == ret){
  50. perror("epoll_ctl");
  51. return -1;
  52. }
  53. event.data.fd = udpfd; // 有名管道
  54. event.events = EPOLLIN; // 表示對應的文件描述符可以讀
  55. // 事件注冊函數,將有udp描述符udpfd 加入監聽事件
  56. ret = epoll_ctl(epfd, EPOLL_CTL_ADD, udpfd, &event);
  57. if(-1 == ret){
  58. perror("epoll_ctl");
  59. return -1;
  60. }
  61. while(1)
  62. {
  63. // 監視并等待多個文件(標準輸入,udp套接字)描述符的屬性變化(是否可讀)
  64. // 沒有屬性變化,這個函數會阻塞,直到有變化才往下執行,這里沒有設置超時
  65. ret = epoll_wait(epfd, &wait_event, 2, -1);
  66. write(1,"UdpQQ:",6);
  67. if(ret == -1){ // 出錯
  68. close(epfd);
  69. perror("epoll");
  70. }
  71. else if(ret > 0){ // 準備就緒的文件描述符
  72. char buf[100] = {0};
  73. if( ( 0 == wait_event.data.fd )
  74. && ( EPOLLIN == wait_event.events & EPOLLIN ) ){ // 標準輸入
  75. fgets(buf, sizeof(buf), stdin);
  76. buf[strlen(buf) - 1] = '\0';
  77. if(strncmp(buf, "sayto", 5) == 0)
  78. {
  79. char ipbuf[16] = "";
  80. inet_pton(AF_INET, buf+6, &caddr.sin_addr);//給addr套接字地址再賦值.
  81. printf("\rsay to %s\n",inet_ntop(AF_INET,&caddr.sin_addr,ipbuf,sizeof(ipbuf)));
  82. continue;
  83. }
  84. else if(strcmp(buf, "exit")==0)
  85. {
  86. close(udpfd);
  87. exit(0);
  88. }
  89. sendto(udpfd, buf, strlen(buf),0,(struct sockaddr*)&caddr, sizeof(caddr));
  90. }
  91. else if( ( udpfd == wait_event.data.fd )
  92. && ( EPOLLIN == wait_event.events & EPOLLIN )){ //udp套接字
  93. struct sockaddr_in addr;
  94. char ipbuf[INET_ADDRSTRLEN] = "";
  95. socklen_t addrlen = sizeof(addr);
  96. bzero(&addr,sizeof(addr));
  97. recvfrom(udpfd, buf, 100, 0, (struct sockaddr*)&addr, &addrlen);
  98. printf("\r\033[31m[%s]:\033[32m%s\n",inet_ntop(AF_INET,&addr.sin_addr,ipbuf,sizeof(ipbuf)),buf);
  99. }
  100. }
  101. else if(0 == ret){ // 超時
  102. printf("time out\n");
  103. }
  104. }
  105. return 0;
  106. }


運行結果:



源碼下載:


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

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

相關文章

操作系統【三】內存管理基礎+連續內存分配

內存的基礎知識 內存分為按字節編址&#xff08;8位&#xff09;和字編制&#xff08;不同計算機不一樣&#xff0c;64位計算機就是64位&#xff0c;即8個字節&#xff09; 相對地址邏輯地址 絕對地址物理地址 從邏輯地址到物理地址的轉換由裝入解決。 裝入的三種方式 絕對…

MSG_PEEK標志

https://blog.csdn.net/aspnet_lyc/article/details/28937229 MSG_PEEK標志可以用來讀取套接字接收隊列中可讀的數據&#xff0c;一些情況會用到它&#xff0c;比如為了避免不阻塞而先檢查套接字接收隊列中可讀的數據長度&#xff0c;再采取相應操作。當然&#xff0c;不阻塞也…

快速排序詳解+各種實現方式

快速排序的思想大體來說比較簡單&#xff0c;就是從數組中挑選一個數字當做樞紐&#xff0c;然后將比樞紐大的和比樞紐小的分別放在樞紐的兩邊&#xff0c;再遞歸地對兩邊進行操作&#xff0c;從而進行分治解決問題。平均情況下快速排序是復雜度為O(nlogn)O(nlogn)O(nlogn)&…

C++的單例模式與線程安全單例模式(懶漢/餓漢)

https://www.cnblogs.com/qiaoconglovelife/p/5851163.html1 教科書里的單例模式我們都很清楚一個簡單的單例模式該怎樣去實現&#xff1a;構造函數聲明為private或protect防止被外部函數實例化&#xff0c;內部保存一個private static的類指針保存唯一的實例&#xff0c;實例的…

計算矩陣的逆和行列式的值(高斯消元+LU分解)

計算矩陣的逆 選主元的高斯消元法 樸素的高斯消元法是將矩陣A和單位矩陣放在一起&#xff0c;通過行操作&#xff08;或者列操作&#xff09;將A變為單位矩陣&#xff0c;這個時候單位矩陣就是矩陣A的逆矩陣。從上到下將A變為上三角矩陣的復雜度為O(n3n^3n3)&#xff0c;再從下…

Linux網絡編程——tcp并發服務器(epoll實現)

https://blog.csdn.net/lianghe_work/article/details/46551871通過epoll實現tcp并發回執服務器&#xff08;客戶端給服務器發啥&#xff0c;服務器就給客戶端回啥&#xff09; 代碼如下&#xff1a;#include <string.h>#include <stdio.h>#include <stdlib.h&g…

證明AVL樹的上界和下界

對于n個節點的AVL樹&#xff0c;其高度最低的時候肯定為葉子節點只在最后一層和倒數第二層的時候。即對于2k?1<n≦2k1?12^k-1< n\leqq 2^{k1}-12k?1<n≦2k1?1的時候下界都為kkk。因此下界為h┌log2(n1)┐?1h\ulcorner log_2(n1)\urcorner-1h┌log2?(n1)┐?1 對…

淺談dup和dup2的用法

https://blog.csdn.net/u012058778/article/details/78705536一、dup和dup2函數 這兩個函數都可以來復制一個現有的文件描述符&#xff0c;他們的聲明如下&#xff1a;#include <unistd.h>int dup(int fd);int dup2(int fd, int fd 2); 123 關于dup函數&#xff0c;當我…

C++ cin 實現循環讀入

習慣了使用while(~scanf("%d",x)){}來實現循環讀入&#xff0c;但是有時候使用泛型編程的時候就必須使用C中的cin&#xff0c;但是當我想要實現循環讀入的時候卻發現有些困難。 我們可以看一下下面這個簡單的例子&#xff1a; #include <iostream>using name…

BFPTR算法詳解+實現+復雜度證明

BFPTR算法是由Blum、Floyed、Pratt、Tarjan、Rivest這五位牛人一起提出來的&#xff0c;其特點在于可以以最壞復雜度為O(n)O(n)O(n)地求解top?ktop-ktop?k問題。所謂top?ktop-ktop?k問題就是從一個序列中求解其第k大的問題。 top?ktop-ktop?k問題有許多解決方法&#xff…

C++子類對象隱藏了父類的同名成員函數(隱藏篇)

https://blog.csdn.net/alpha_love/article/details/75222175#include <iostream>#include <stdlib.h>#include <string>using namespace std;/*** 定義人類: Person* 數據成員: m_strName* 成員函數: attack()*/class Person{public:Person(){cout<<&…

隨機化快速排序+快速選擇 復雜度證明+運行測試

對于快速排序和快速選擇我之前的文章已經有詳細的說明&#xff0c;需要了解的同學可以移步 傳送門&#xff1a;快速排序&#xff5c;快速選擇(BFPTR) 所謂隨機化其實就是選擇樞紐的時候使用隨機數選擇而已&#xff0c;實現起來很簡單。但是我們使用隨機數如何保證復雜度呢&am…

C++子類父類成員函數的覆蓋和隱藏實例詳解

https://www.jb51.net/article/117380.htm函數的覆蓋覆蓋發生的條件&#xff1a; &#xff08;1&#xff09; 基類必須是虛函數&#xff08;使用virtual 關鍵字來進行聲明&#xff09; &#xff08;2&#xff09;發生覆蓋的兩個函數分別位于派生類和基類 &#xff08;3&#xf…

【Linux基礎】Linux的5種IO模型詳解

引入 為了更好的理解5種IO模型的區別&#xff0c;在介紹IO模型之前&#xff0c;我先介紹幾個概念 1.進程的切換 &#xff08;1&#xff09;定義 為了控制進程的執行&#xff0c;內核必須有能力掛起正在CPU上運行的進程&#xff0c;并恢復以前掛起的某個進程的執行。即從用戶…

計算機網絡【五】廣播通信+以太網

局域網的拓撲 廣域網使用點到點通信 局域網使用廣播通信 可以隨意向網絡中添加設備。 總線網星形網&#xff0c;使用集線器。現在多使用星形網絡。環狀網樹形網 其中匹配電阻用來吸收總線上傳播的信號。 共享通信媒體 靜態劃分信道 頻分復用、時分復用、波分復用、碼分復用…

聊聊Linux 五種IO模型

一篇《聊聊同步、異步、阻塞與非阻塞》已經通俗的講解了&#xff0c;要理解同步、異步、阻塞與非阻塞重要的兩個概念點了&#xff0c;沒有看過的&#xff0c;建議先看這篇博文理解這兩個概念點。在認知上&#xff0c;建立統一的模型。這樣&#xff0c;大家在繼續看本篇時&#…

操作系統【四】分頁存儲管理

連續分配方式的缺點&#xff1a; 固定分區分配&#xff1a;缺乏靈活性&#xff0c;產生大量的內部碎片&#xff0c;內存的利用率較低 動態分區分配&#xff1a;會產生許多外部碎片&#xff0c;雖然可以用緊湊技術處理&#xff0c;但是緊湊技術的時間代價較高 基本分頁存儲管理…

聊聊同步、異步、阻塞與非阻塞

近來遇到了一些常見的概念&#xff0c;尤其是網絡編程方面的概念&#xff0c;如&#xff1a;阻塞、非阻塞、異步I/O等等&#xff0c;對于這些概念自己也沒有太清晰的認識&#xff0c;只是很模糊的概念&#xff0c;說了解吧也了解&#xff0c;但是要讓自己準確的描述概念方面的具…

操作系統【五】分段內存管理+段頁式內存管理

基本分段存儲管理 與分頁最大的區別&#xff1a;離散分配時所分配地址空間的基本單位不同 進程的地址空間&#xff1a;按照程序自身的邏輯關系劃分為若干個段&#xff0c;每個段都有一個段名&#xff0c;每段從0開始編址 內存分配規則&#xff1a;以段位單位進行分配&#xff…

計算機網絡【六】網絡層協議

網絡層負責在不同網絡之間盡力轉發數據包&#xff08;基于數據包的IP地址轉發&#xff09;。不負責丟失重傳&#xff0c;也不負責順序&#xff08;每一個數據包都是單獨選擇路徑&#xff09;。 可靠傳輸是由傳輸層實現。 網絡設備和OSI參考模型 通過分層&#xff0c;屏蔽了…