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

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 运维知识 > linux >内容正文

linux

【Linux网络编程学习】I/O多路复用——epoll

發(fā)布時間:2023/11/30 linux 40 豆豆
生活随笔 收集整理的這篇文章主要介紹了 【Linux网络编程学习】I/O多路复用——epoll 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

此為??蚅inux C++課程和黑馬Linux系統(tǒng)編程筆記。

1. 關(guān)于epoll

epoll是Linux下多路復(fù)用IO接口select/poll的增強(qiáng)版本,它能顯著提高程序在大量并發(fā)連接中只有少量活躍的情況下的系統(tǒng)CPU利用率,因為它會復(fù)用文件描述符集合來傳遞結(jié)果而不用迫使開發(fā)者每次等待事件之前都必須重新準(zhǔn)備要被偵聽的文件描述符集合,另一點原因就是獲取事件的時候,它無須遍歷整個被偵聽的描述符集,只要遍歷那些被內(nèi)核IO事件異步喚醒而加入Ready隊列的描述符集合就行了。

目前epoll是linux大規(guī)模并發(fā)網(wǎng)絡(luò)程序中的熱門首選模型。

epoll除了提供select/poll那種IO事件的水平觸發(fā)(Level Triggered)外,還提供了邊沿觸發(fā)(Edge Triggered),這就使得用戶空間程序有可能緩存IO狀態(tài),減少epoll_wait/epoll_pwait的調(diào)用,提高應(yīng)用程序效率。

2. epoll API介紹

2.1 創(chuàng)建epoll實例:epoll_create

#include <sys/epoll.h> int epoll_create(int size);

功能:在內(nèi)核中創(chuàng)建一個新的epoll實例,并返回一個操縱該epoll的文件描述符,這個文件描述符和真正的文件沒有關(guān)系,僅僅是為了后續(xù)調(diào)用epoll而創(chuàng)建的。該函數(shù)調(diào)用后在內(nèi)核中創(chuàng)建了一個存儲事件的數(shù)據(jù)結(jié)構(gòu),這個數(shù)據(jù)結(jié)構(gòu)中有兩個比較重要的子結(jié)構(gòu),一個是需要檢測的文件描述符的信息(使用紅黑樹實現(xiàn)),還有一個是就緒列表,存放檢測到數(shù)據(jù)發(fā)送改變的文件描述符信息(使用雙向鏈表實現(xiàn)),關(guān)于epoll更詳細(xì)的內(nèi)部實現(xiàn)在這里不詳細(xì)討論。

參數(shù):size : 自從linux2.6.8之后,size參數(shù)是被忽略的。隨便寫一個數(shù),必須大于0。

返回值:
-1 : 失敗
> 0 : 用于操作epoll實例的文件描述符

2.2 注冊epoll的監(jiān)聽事件:epoll_ctl

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

功能:向內(nèi)核中的epoll實例中添加、修改、移除事件。epoll和select的一個顯著區(qū)別就在這里:select是在監(jiān)聽事件時告訴內(nèi)核要監(jiān)聽什么類型的事件,而epoll是在這里先注冊要監(jiān)聽的事件類型,然后再調(diào)用epoll_wait監(jiān)聽。

參數(shù):

  • epfd : epoll實例對應(yīng)的文件描述符
  • op : 要進(jìn)行什么操作
    EPOLL_CTL_ADD: 添加
    EPOLL_CTL_MOD: 修改
    EPOLL_CTL_DEL: 刪除
  • fd : 要檢測的文件描述符
  • event : 檢測文件描述符什么事件,這里涉及到epoll_event,定義如下:
struct epoll_event {uint32_t events; /* Epoll 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;

這里我們只需要關(guān)注兩個字段即可:events和data.fd:

其中events表示要檢測的事件,有以下選擇:
EPOLLIN :表示對應(yīng)的文件描述符可以讀(包括對端SOCKET正常關(guān)閉);
EPOLLOUT:表示對應(yīng)的文件描述符可以寫;
EPOLLPRI:表示對應(yīng)的文件描述符有緊急的數(shù)據(jù)可讀(這里應(yīng)該表示有帶外數(shù)據(jù)到來);
EPOLLERR:表示對應(yīng)的文件描述符發(fā)生錯誤;
EPOLLHUP:表示對應(yīng)的文件描述符被掛斷;
EPOLLET: 將EPOLL設(shè)為邊緣觸發(fā)(Edge Triggered)模式,這是相對于水平觸發(fā)(Level Triggered)來說的。
EPOLLONESHOT:只監(jiān)聽一次事件,當(dāng)監(jiān)聽完這次事件之后,如果還需要繼續(xù)監(jiān)聽這個socket的話,需要再次把這個socket加入到EPOLL隊列里。

其中data.fd表示該事件對應(yīng)的socket的文件描述符。

返回值:

  • 成功,返回發(fā)送變化的文件描述符的個數(shù) > 0
  • 失敗 -1

2.3 監(jiān)聽事件:epoll_wait

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

功能:等待已注冊的事件發(fā)生,返回事件的數(shù)目,并將已觸發(fā)的事件寫入events數(shù)組(第二個參數(shù))中。

參數(shù):

  • epfd : epoll實例對應(yīng)的文件描述符
  • events : 傳出參數(shù),保存了發(fā)送了變化的文件描述符的信息,需要調(diào)用者先創(chuàng)建好
  • maxevents : 第二個參數(shù)結(jié)構(gòu)體數(shù)組的大小
  • timeout : 阻塞時間
    • 0 : 不阻塞
    • -1 : 阻塞,直到檢測到fd數(shù)據(jù)發(fā)生變化,解除阻塞
    • > 0 : 阻塞的時長(毫秒)

返回值:

  • 成功,返回發(fā)送變化的文件描述符的個數(shù) > 0
  • 失敗 -1

3. 示例程序

以下用epoll實現(xiàn)了一個簡單的服務(wù)端,把客戶端傳來的小寫字母轉(zhuǎn)換成大寫字母并傳回給客戶端。

/*用epoll實現(xiàn)一個簡單的服務(wù)器-客戶端通信*/#include <stdio.h> #include <unistd.h> #include <arpa/inet.h> #include <stdlib.h> #include <pthread.h> #include <strings.h> #include <sys/epoll.h>// 設(shè)定一個服務(wù)器端口號 #define SERV_IP "127.0.0.1" #define SERV_PORT 9999int main() {int lfd = socket(AF_INET, SOCK_STREAM, 0);struct sockaddr_in serv_addr;serv_addr.sin_family = AF_INET;serv_addr.sin_port = htons(SERV_PORT); // 注意轉(zhuǎn)化成網(wǎng)絡(luò)字節(jié)序inet_pton(AF_INET, SERV_IP, &serv_addr.sin_addr.s_addr); // 注意轉(zhuǎn)化成網(wǎng)絡(luò)字節(jié)序bind(lfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr));listen(lfd, 128);int epfd = epoll_create(100); // 內(nèi)核創(chuàng)建epoll實例struct epoll_event epev;epev.events = EPOLLIN; // 要檢測lfd的讀事件epev.data.fd = lfd;// 注冊了對lfd的監(jiān)聽,此后如果不刪除這個注冊信息,每次調(diào)用epoll_wait都將監(jiān)聽lfd的讀事件(也就是客戶端的連接)epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &epev); struct epoll_event epevs[1024]; // 用作epoll_wait的第二個參數(shù)(傳出參數(shù)) while(1) {int ret = epoll_wait(epfd, epevs, 1024, -1); // 監(jiān)聽已注冊的事件,最后一個參數(shù)-1表示阻塞等待if(ret == -1) {perror("epoll_wait error");exit(-1);}// 一旦走到這里說明解除了阻塞,就是指epoll監(jiān)測到了事件的發(fā)生,遍歷每個事件:for(int i = 0; i < ret; ++i) {int curfd = epevs[i].data.fd; // 表示當(dāng)前觸發(fā)的事件對應(yīng)的fdif(curfd == lfd) { // 如果監(jiān)聽到lfd的讀事件了,說明有一個新客戶端建立連接struct sockaddr_in clie_addr;int clie_addr_len = sizeof(clie_addr); int cfd = accept(lfd, (struct sockaddr*)&clie_addr, &clie_addr_len);char clie_IP[BUFSIZ];printf("Client IP: %s, client port: %d connected\n", inet_ntop(AF_INET, &clie_addr.sin_addr.s_addr, clie_IP, sizeof(clie_IP)),ntohs(clie_addr.sin_port));epev.events = EPOLLIN; // 要檢測cfd的讀事件epev.data.fd = cfd;epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &epev); // 把對該cfd的讀事件監(jiān)聽注冊上,以后epoll會同時監(jiān)聽lfd和cfd} else { // 說明檢測到的是某個cfd的讀事件,讀該客戶端傳來的數(shù)據(jù)char buf[BUFSIZ] = {0};int len = read(curfd, buf, sizeof(buf));if(len > 0) {// 小寫轉(zhuǎn)大寫int i;for(i = 0; i < len; ++i) {if(buf[i] >= 'a' && buf[i] <= 'z') {buf[i] -= 32;}}write(curfd, buf, len); // 寫回給客戶端write(STDOUT_FILENO, buf, len);} else if(len == 0) {// 說明讀完了,客戶端已關(guān)閉,此時epoll已經(jīng)沒有必要繼續(xù)監(jiān)聽該cfd了epoll_ctl(epfd, EPOLL_CTL_DEL, curfd, NULL);close(curfd);} else {perror("read error");exit(-1);}}}}close(lfd);close(epfd); // 別忘了關(guān)epfdreturn 0; }

4. epoll的兩種觸發(fā)方式

EPOLL事件有兩種模型:

  • Edge Triggered (ET) 邊緣觸發(fā):只有數(shù)據(jù)到來才觸發(fā),不管緩存區(qū)中是否還有數(shù)據(jù)。
  • Level Triggered (LT) 水平觸發(fā):只要有數(shù)據(jù)都會觸發(fā)。

LT(level - triggered)是缺省的工作方式,并且同時支持 block 和 no-block socket。在這種做法中,內(nèi)核告訴你一個文件描述符是否就緒了,然后你可以對這個就緒的 fd 進(jìn)行 IO 操作。如果你不作任何操作,內(nèi)核還是會繼續(xù)通知你的。

ET(edge - triggered)是高速工作方式,只支持 no-block socket。在這種模式下,當(dāng)描述符從未就緒變?yōu)榫途w時,內(nèi)核通過epoll告訴你。然后它會假設(shè)你知道文件描述符已經(jīng)就緒,并且不會再為那個文件描述符發(fā)送更多的就緒通知,直到你做了某些操作導(dǎo)致那個文件描述符不再為就緒狀態(tài)了。但是請注意,如果一直不對這個 fd 作 IO 操作(從而導(dǎo)致它再次變成未就緒),內(nèi)核不會發(fā)送更多的通知(only once)。

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

關(guān)于LT和ET的詳細(xì)介紹,以及為什么ET模式要搭配非阻塞IO,見這篇博客,寫的極好。

總結(jié)

以上是生活随笔為你收集整理的【Linux网络编程学习】I/O多路复用——epoll的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。