Linux网络编程---I/O复用模型之epoll
https://blog.csdn.net/men_wen/article/details/53456474
Linux網(wǎng)絡(luò)編程—I/O復(fù)用模型之epoll
1. epoll模型簡(jiǎn)介
epoll是Linux多路服用IO接口select/poll的加強(qiáng)版,e對(duì)應(yīng)的英文單詞就是enhancement,中文翻譯為增強(qiáng),加強(qiáng),提高,充實(shí)的意思。所以epoll模型會(huì)顯著提高程序在大量并發(fā)連接中只有少量活躍的情況下的系統(tǒng)CPU利用率。
- epoll把用戶關(guān)心的文件描述符上的時(shí)間放在內(nèi)核的一個(gè)事件表中,無需像select和poll那樣每次調(diào)用都重復(fù)傳入文件描述符集。
- epoll在獲取事件的時(shí)候,無需遍歷整個(gè)被監(jiān)聽的文件描述符集合,而是遍歷那些被內(nèi)核IO事件異步喚醒而加入ready隊(duì)列的描述符集合。
所以,epoll是Linux大規(guī)模高并發(fā)網(wǎng)絡(luò)程序的首選模型。
2.epoll模型的API
epoll使用一組函數(shù)來完成任務(wù)
2.1 函數(shù)epoll_create
創(chuàng)建一個(gè)epoll句柄,句柄的英文是handle,相通的意思是把手,把柄。
#include <sys/epoll.h>int epoll_create(int size); //返回值:若成功,返回一個(gè)非負(fù)的文件描述符,若出錯(cuò),返回-1。- 該函數(shù)返回一個(gè)文件描述符,用來唯一標(biāo)示內(nèi)核中這個(gè)事件表,sizeof參數(shù)提示內(nèi)核要監(jiān)聽的文件描述符個(gè)數(shù),這與內(nèi)存大小有關(guān)。
- 返回的文件描述符將是其他所有epoll系統(tǒng)調(diào)用的第一個(gè)參數(shù),以指定要訪問的內(nèi)核時(shí)間表,所以用該返回的文件描述符相當(dāng)與其他epoll調(diào)用的把手、把柄一樣。
查看進(jìn)程能夠打開的最大數(shù)目的文件描述符
? ~ cat /proc/sys/fs/file-max 1215126 //該值與內(nèi)存大小有關(guān)修改最大文件描述符限制
? ~ sudo vim /etc/security/limits.conf //重啟生效2.2 函數(shù)epoll_ctl
該函數(shù)用來操作epoll的內(nèi)核事件表
#include <sys/epoll.h>int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); //返回值:若成功,返回0,若出錯(cuò)返回-1。- epfd就是函數(shù)epoll_create創(chuàng)建的句柄。
- op是指定操作類型,有一下三種?
- EPOLL_CTL_ADD,向epfd注冊(cè)fd的上的event
- EPOLL_CTL_MOD,修改fd已注冊(cè)的event
- EPOLL_CTL_DEL,從epfd上刪除fd的event?
- fd是操作的文件描述符
- event指定內(nèi)核要監(jiān)聽事件,它是struct epoll_event結(jié)構(gòu)類型的指針。epoll_event定義如下:
vents成員描述事件類型,將以下宏定義通過位或方式組合
- EPOLLIN :表示對(duì)應(yīng)的文件描述符可以讀(包括對(duì)端SOCKET正常關(guān)閉)
- POLLOUT:表示對(duì)應(yīng)的文件描述符可以寫
- EPOLLPRI:表示對(duì)應(yīng)的文件描述符有緊急的數(shù)據(jù)可讀(這里應(yīng)該表示有帶外數(shù)據(jù)到來)
- EPOLLERR:表示對(duì)應(yīng)的文件描述符發(fā)生錯(cuò)誤
- EPOLLHUP:表示對(duì)應(yīng)的文件描述符被掛斷;
- EPOLLET: 將EPOLL設(shè)為邊緣觸發(fā)(Edge Triggered)模式,這是相對(duì)于水平觸發(fā)(Level Triggered)來說的
- EPOLLONESHOT:只監(jiān)聽一次事件,當(dāng)監(jiān)聽完這次事件之后,如果還需要繼續(xù)監(jiān)聽這個(gè)socket的話,需要再次把這個(gè)socket加入到EPOLL隊(duì)列里
data用于存儲(chǔ)用戶數(shù)據(jù),是epoll_data_t結(jié)構(gòu)類型,該結(jié)構(gòu)定義如下:
- epoll_data_t是一個(gè)聯(lián)合體,fd指定事件所從屬的目標(biāo)文件描述符。ptr可以用來指定fd相關(guān)的用戶數(shù)據(jù),但兩者不能同時(shí)使用。
2.3 函數(shù)epoll_wait
函數(shù)epoll_wait用來等待所監(jiān)聽文件描述符上有事件發(fā)生
#include <sys/epoll.h>int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout); //返回值:若成功,返回就緒的文件描述符個(gè)數(shù),若出錯(cuò),返回-1,時(shí)間超時(shí)返回0- epfd就是函數(shù)epoll_create創(chuàng)建的句柄
- timeout是超時(shí)事件,-1為阻塞,0為立即返回,非阻塞,大于0是指定的微妙
- events是一個(gè) 傳入傳出參數(shù),它是epoll_event結(jié)構(gòu)的指針,用來從內(nèi)核得到事件的集合
- maxevents告知內(nèi)核events的大小,但不能大于epoll_create()時(shí)創(chuàng)建的size
3. LT和ET模式
- LT(Level Triggered,電平觸發(fā)):LT模式是epoll默認(rèn)的工作模式,也是select和poll的工作模式,在LT模式下,epoll相當(dāng)于一個(gè)效率較高的poll。?
- 采用LT模式的文件描述符,當(dāng)epoll_wait檢測(cè)到其上有事件發(fā)生并將此事件通知應(yīng)用程序后,應(yīng)用程序可以不立即處理此事件,當(dāng)下一次調(diào)用epoll_wait是,epoll_wait還會(huì)將此事件通告應(yīng)用程序。
- ET(Edge Triggered,邊沿觸發(fā)):當(dāng)調(diào)用epoll_ctl,向參數(shù)event注冊(cè)EPOLLET事件時(shí),epoll將以ET模式來操作該文件描述符,ET模式是epoll的高效工作模式.?
- 對(duì)于采用ET模式的文件描述符,當(dāng)epoll_wait檢測(cè)到其上有事件發(fā)生并將此通知應(yīng)用程序后,應(yīng)用程序必須立即處理該事件,因?yàn)楹罄m(xù)的epoll_wait調(diào)用將不在向應(yīng)用程序通知這一事件。ET模式降低了同意epoll事件被觸發(fā)的次數(shù),效率比LT模式高。
4. LT和ET的服務(wù)端和客戶端代碼
4.1 服務(wù)器端
#include <sys/epoll.h> #include <fcntl.h> #include "wrap.h"#define MAX_EVENT_NUM 1024 #define BUFFER_SIZE 10 #define true 1 #define false 0int setnonblocking(int fd) {int old_opt = fcntl(fd, F_GETFD);int new_opt = old_opt | O_NONBLOCK;fcntl(fd, F_SETFD, new_opt);return old_opt; }//將文件描述符設(shè)置為非阻塞的void addfd(int epollfd, int fd, int enable_et) {struct epoll_event event;event.data.fd = fd;event.events = EPOLLIN;if(enable_et){event.events |= EPOLLET;}epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &event); // setnonblocking(fd); }//將文件描述符fd的EPOLLIN注冊(cè)到epollfd指示的epoll內(nèi)核事件表中,enable_et表示是否對(duì)fd啟用ET模式void lt(struct epoll_event *events, int num, int epollfd, int listenfd) {char buf[BUFFER_SIZE];for(int i = 0; i < num; i++){int sockfd = events[i].data.fd;if(sockfd == listenfd){struct sockaddr_in clientaddr;socklen_t clilen = sizeof(clientaddr);int connfd = Accept(listenfd, (struct sockaddr *)&clientaddr, &clilen);addfd(epollfd, connfd, false);//對(duì)connfd使用默認(rèn)的lt模式}else if(events[i].events & EPOLLIN){//只要socket讀緩存中還有未讀的數(shù)據(jù),這段代碼就會(huì)觸發(fā)printf("event trigger once\n");memset(buf, '\0', BUFFER_SIZE);int ret = recv(sockfd, buf, BUFFER_SIZE-1, 0);if(ret <= 0){Close(sockfd);continue;}printf("get %d bytes of content:%s\n", ret, buf);}else{printf("something else happened\n");}} }void et(struct epoll_event *event, int num, int epollfd, int listenfd) {char buf[BUFFER_SIZE];for(int i = 0; i < num; i++){int sockfd = event[i].data.fd;if(sockfd == listenfd){struct sockaddr_in clientaddr;int clilen = sizeof(clientaddr);int connfd = Accept(listenfd, (struct sockaddr *)&clientaddr, &clilen);addfd(epollfd, connfd, true);//多connfd開啟ET模式}else if(event[i].events & EPOLLIN){printf("event trigger once\n");while(1){//這段代碼不會(huì)重復(fù)觸發(fā),所以要循環(huán)讀取數(shù)據(jù)memset(buf, '\0', BUFFER_SIZE);int ret = recv(sockfd, buf, BUFFER_SIZE-1, 0);if(ret < 0){if((errno == EAGAIN) || (errno == EWOULDBLOCK)){printf("read later\n");break;}Close(sockfd);break;}else if(ret == 0){Close(sockfd);}else{printf("get %d bytes of content:%s\n", ret, buf);}}}else{printf("something else happened \n");}} }int start_ser(char *ipaddr, char *port) {int sock = Socket(AF_INET, SOCK_STREAM, 0);struct sockaddr_in serveraddr;bzero(&serveraddr, sizeof(serveraddr));serveraddr.sin_family = AF_INET;serveraddr.sin_port = htons(atoi(port));inet_pton(AF_INET, ipaddr, &serveraddr.sin_addr);Bind(sock, (struct sockaddr *)&serveraddr, sizeof(serveraddr));Listen(sock, 128);return sock; }int main(int argc, char *argv[]) {int listenfd = start_ser(argv[1], argv[2]);struct epoll_event events[MAX_EVENT_NUM];int epollfd = epoll_create(5);if(epollfd < 0){perr_exit("epoll_create err");}addfd(epollfd, listenfd, true);while(1){int ret = epoll_wait(epollfd, events, MAX_EVENT_NUM, -1);if(ret < 0){printf("epoll failure\n");break;}lt(events, ret, epollfd, listenfd);//lt模式//et(events, ret, epollfd, listenfd);//et模式}Close(listenfd);return 0; } //warp.h文件是將socket,bind,listen等函數(shù)封裝為第一個(gè)字母大寫的頭文件4.2 客戶端
#include "wrap.h" int main(int argc, char *argv[]) {int connfd;struct sockaddr_in serveraddr;char buf[1024];connfd = Socket(AF_INET, SOCK_STREAM, 0);bzero(&serveraddr, sizeof(serveraddr));serveraddr.sin_family = AF_INET;serveraddr.sin_port = htons(atoi(argv[2]));inet_pton(AF_INET, argv[1], &serveraddr.sin_addr);Connect(connfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr));while(fgets(buf, 1024, stdin) != NULL){Write(connfd, buf, strlen(buf));}Close(connfd);return 0; }4.3 兩種模式結(jié)果對(duì)比
?
當(dāng)發(fā)送超過緩沖區(qū)大小的數(shù)據(jù)量,LT會(huì)多次調(diào)用epoll_wait函數(shù)接受數(shù)據(jù),則打印了多次“event level once”,而ET則是循環(huán)讀取數(shù)據(jù)知道讀完,打印了一次“event trigger once”。
總結(jié)
以上是生活随笔為你收集整理的Linux网络编程---I/O复用模型之epoll的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 成都欢乐谷忘了带残疾证,但有照片,可以进
- 下一篇: 得多囊卵巢的人多吗