文章目錄:
一:select
1.基礎API?
select函數(shù)
思路分析
select優(yōu)缺點
2.server.c
3.client.c
二:poll
1.基礎API?
poll函數(shù)?
poll優(yōu)缺點
read函數(shù)返回值
突破1024 文件描述符限制
2.server.c
3.client.c
三:epoll
1.基礎API
epoll_create創(chuàng)建? ?epoll_ctl操作? epoll_wait阻塞
epoll實現(xiàn)多路IO轉(zhuǎn)接思路
epoll優(yōu)缺點?
ctags使用
2.server.c
3.client.c
4.事件模型(epoll 事件觸發(fā)模型ET和LT)
4.1 server.c
4.2 client.c
5.epoll 反應堆模型
select、poll以及epoll都是系統(tǒng)內(nèi)核來對網(wǎng)絡通信中的通信套接字(文件描述符)來進行監(jiān)視
能夠在與服務器連接的大量客戶端中識別出與服務器請求了數(shù)據(jù)交換的客戶端,并把它們所對應的套接字通過函數(shù)返回,交給服務器
此時服務器只需要和請求了數(shù)據(jù)交換的客戶端進行通信即可,而其它的套接字則不做任何處理
因此,比起服務器自身每次去輪詢查詢并處理每個套接字的效率要高很多
一:select
1.基礎API?
select函數(shù)
?
原理: 借助內(nèi)核, select 來監(jiān)聽, 客戶端連接、數(shù)據(jù)通信事件 //將給定的套接字fd從位圖set中清除出去 void FD_CLR(int fd,fd_set* set); FD_CLR(4, &rset); 將一個文件描述符從監(jiān)聽集合中 移除 //檢查給定的套接字fd是否在位圖里面,返回值 在1 不在0 int FD_ISSET(int fd,fd_set* set); FD_ISSET(4,&rset); 判斷一個文件描述符是否在監(jiān)聽集合中 //將給定的套接字fd設置到位圖set中 void FD_SET(int fd,fd_set* set); 將待監(jiān)聽的文件描述符,添加到監(jiān)聽集合中 FD_SET(3, &rset); FD_SET(5, &rset); FD_SET(6, &rset); //將整個位圖set置零 void FD_ZERO(fd_set* set); fd_set rset; 清空一個文件描述符集 FD_ZERO(&rset); //select 是一個系統(tǒng)調(diào)用,用于監(jiān)控多個文件描述符(sockets, files等)的 I/O 活動 //它等待某個文件描述符集變?yōu)榭勺x、可寫或出現(xiàn)異常,然后返回 int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout); nfds :監(jiān)聽 所有文件描述符中,最大文件描述符+1 readfds :讀 文件描述符監(jiān)聽集合。 傳入、傳出參數(shù) writefds :寫 文件描述符監(jiān)聽集合。 傳入、傳出參數(shù) NULL exceptfds:異常 文件描述符監(jiān)聽集合 傳入、傳出參數(shù) NULL timeout: > 0 : 設置監(jiān)聽超時時長 NULL:阻塞監(jiān)聽 0 :非阻塞監(jiān)聽,輪詢 返回值: > 0:所有監(jiān)聽集合(3個)中, 滿足對應事件的總數(shù) 0:沒有滿足監(jiān)聽條件的文件描述符 -1:errno
思路分析
int maxfd = 0; lfd = socket() ; 創(chuàng)建套接字 maxfd = lfd; 備份 bind(); 綁定地址結(jié)構(gòu) listen(); 設置監(jiān)聽上限 fd_set rset, allset; 創(chuàng)建r讀監(jiān)聽集合 FD_ZERO(&allset); 將r讀監(jiān)聽集合清空 FD_SET(lfd, &allset); 將 lfd 添加至讀集合中 lfd文件描述符在監(jiān)聽期間沒有滿足讀事件發(fā)生,select返回的時候rset不會在集合中 while(1) { rset = allset; 保存監(jiān)聽集合 ret = select(lfd+1, &rset, NULL, NULL, NULL); 監(jiān)聽文件描述符集合對應事件 if(ret > 0) { 有監(jiān)聽的描述符滿足對應事件 //處理連接:一次監(jiān)聽 if (FD_ISSET(lfd, &rset)) { 1 在集合中,0不在 cfd = accept(); 建立連接,返回用于通信的文件描述符 maxfd = cfd; FD_SET(cfd, &allset); 添加到監(jiān)聽通信描述符集合中 } //處理通信:剩下的 for (i = lfd+1; i <= 最大文件描述符; i++){ //嵌套 FD_ISSET(i, &rset) 有read、write事件 read() 小 -- 大 write(); } } }
select優(yōu)缺點
?當你只需要監(jiān)聽幾個指定的套接字時, 需要對整個1024的數(shù)組進行輪詢, 效率降低
缺點:監(jiān)聽上限受文件描述符限制。 最大1024 檢測滿足條件的fd,自己添加業(yè)務邏輯提高小,提高了編碼難度 如果監(jiān)聽的文件描述符比較散亂、而且數(shù)量不多,效率會變低 優(yōu)點: 跨平臺win、linux、macOS、Unix、類Unix、mips
2.server.c
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <arpa/inet.h> #include <ctype.h> #include "wrap.h" #define SERV_PORT 6666 void FD_CLR(int fd,fd_set* set); //將給定的套接字fd從位圖set中清除出去 int FD_ISSET(int fd,fd_set* set); //檢查給定的套接字fd是否在位圖里面,返回0或1 void FD_SET(int fd,fd_set* set); //將給定的套接字fd設置到位圖set中 void FD_ZERO(fd_set* set); //將整個位圖set置零 int main(int argc, char *argv[]){ int i, j, n, maxi; /*數(shù)組:將需要輪詢的客戶端套接字放入數(shù)組client[FD_SETSIZE],防止遍歷1024個文件描述符 FD_SETSIZE默認為1024*/ int nready, client[FD_SETSIZE]; int listenFd, connectFd, maxFd, socketFd; char buf[BUFSIZ], str[INET_ADDRSTRLEN]; //#define INET_ADDRSTRLEN 16 struct sockaddr_in serverAddr, clientAddr; socklen_t clientAddrLen; fd_set rset, allset; //rset讀事件文件描述符集合,allset用來暫存 /*得到監(jiān)聽套接字*/ listenFd = Socket(AF_INET, SOCK_STREAM, 0); /*定義兩個集合,將listenFd放入allset集合當中*/ fd_set rset, allset; FD_ZERO(&allset); //將整個位圖set置零 //將給定的套接字fd設置到位圖set中 FD_SET(listenFd, &allset); //將connectFd加入集合:構(gòu)造select監(jiān)控文件描述符集 /*設置地址端口復用*/ int opt = 1; setsockopt(listenFd, SOL_SOCKET, SO_REUSEADDR, (void *)&opt, sizeof(opt)); /*填寫服務器地址結(jié)構(gòu)*/ bzero(&serverAddr, sizeof(serverAddr)); serverAddr.sin_family = AF_INET; serverAddr.sin_addr.s_addr = htonl(INADDR_ANY); serverAddr.sin_port = htons(SERVER_PORT); /*綁定服務器地址結(jié)構(gòu)*/ Bind(listenFd, (struct sockaddr *)&serverAddr, sizeof(serverAddr)); Listen(listenFd, 128); /*將listenFd設置為數(shù)組中最大的Fd*/ maxFd = listenFd; //起初 listenfd 即為最大文件描述符 maxi = -1; //將來用作client[]的下標, 初始值指向0個元素之前下標位置 /*數(shù)組:初始化自己的數(shù)組為-1*/ for (i = 0; i < FD_SETSIZE; ++i) client[i] = -1; while (1){ /*把allset給rest,讓他去用*/ rset = allset; //備份:每次循環(huán)時都從新設置select監(jiān)控信號集 nready = select(maxFd + 1, &rset, NULL, NULL, NULL); //使用select監(jiān)聽文件描述符集合對應事件 if (nready == -1) //出錯返回 perr_exit("select error"); /*listen滿足監(jiān)聽的事件:如果有了新的連接請求,得到connectFd,并將其放入自定義數(shù)組中*/ if (FD_ISSET(listenFd, &rset)){ //檢查給定的套接字fd是否在位圖里面,返回0或1 clientAddrLen = sizeof(clientAddr); //建立鏈接,不會阻塞 connectFd = Accept(listenFd, (struct sockaddr *)&clientAddr, &clientAddrLen); printf( "Recived from %s at PORT %d\n", inet_ntop(AF_INET, &(clientAddr.sin_addr.s_addr), str, sizeof(str)), ntohs(clientAddr.sin_port)); for (i = 0; i < FD_SETSIZE; ++i) if (client[i] < 0){ //找client[]中沒有使用的位置 client[i] = connectFd; //保存accept返回的文件描述符到client[]里 break; } /*自定義數(shù)組滿了:達到select能監(jiān)控的文件個數(shù)上限 1024 */ if(i==FD_SETSIZE){ fputs("Too many clients\n",stderr); exit(1); } /*connectFd加入監(jiān)聽集合:向監(jiān)控文件描述符集合allset添加新的文件描述符connectFd*/ FD_SET(connectFd, &allset); //將給定的套接字fd設置到位圖set中 /*更新最大的Fd*/ if (maxFd < connectFd) maxFd = connectFd; /*更新循環(huán)上限*/ if(i>maxi) maxi=i; //保證maxi存的總是client[]最后一個元素下標 /*select返回1,說明只有建立連接請求,沒有數(shù)據(jù)傳送請求,跳出while循環(huán)剩余部分(下面的for循環(huán)輪詢過程)*/ //如果只有l(wèi)isten事件,只需建立連接即可,無需數(shù)據(jù)傳輸,跳出循環(huán)剩余部分 if (--nready == 0) continue; } /*檢測哪個clients 有數(shù)據(jù)就緒:select返回不是1,說明有connectFd有數(shù)據(jù)傳輸請求,遍歷自定義數(shù)組*/ //否則,說明有數(shù)據(jù)傳輸需求 for (i = 0; i <= maxi; ++i){ if((socketFd=client[i])<0) continue; /*遍歷檢查*/ if (FD_ISSET(socketFd, &rset)){ //檢查給定的套接字fd是否在位圖里面,返回0或1 /*read返回0說明傳輸結(jié)束,關(guān)閉連接:當client關(guān)閉鏈接時,服務器端也關(guān)閉對應鏈接*/ if ((n=read(socketFd,buf,sizeof(buf)))==0){ close(socketFd); //將給定的套接字fd從位圖set中清除出去 FD_CLR(socketFd, &allset); //解除select對此文件描述符的監(jiān)控 client[i]=-1; }else if(n>0){ for (j = 0; j < n; ++j) buf[j] = toupper(buf[j]); write(socketFd, buf, n); write(STDOUT_FILENO, buf, n); } /*不懂:需要處理的個數(shù)減1?*/ if(--nready==0) break; //跳出for, 但還在while中 } } } close(listenFd); return 0; }
3.client.c
/* client.c */ #include <stdio.h> #include <string.h> #include <stdlib.h> #include <unistd.h> #include <arpa/inet.h> #include <netinet/in.h> #include "wrap.h" #define MAXLINE 80 #define SERV_PORT 6666 int main(int argc, char *argv[]) { struct sockaddr_in servaddr; char buf[MAXLINE]; int sockfd, n; if (argc != 2) { printf("Enter: ./client server_IP\n"); exit(1); } sockfd = Socket(AF_INET, SOCK_STREAM, 0); bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; inet_pton(AF_INET, argv[1], &servaddr.sin_addr); servaddr.sin_port = htons(SERV_PORT); Connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)); printf("------------connect ok----------------\n"); while (fgets(buf, MAXLINE, stdin) != NULL) { Write(sockfd, buf, strlen(buf)); n = Read(sockfd, buf, MAXLINE); if (n == 0) { printf("the other side has been closed.\n"); break; } else Write(STDOUT_FILENO, buf, n); } Close(sockfd); return 0; }
二:poll
這個函數(shù)是一個半成品,用的很少?
1.基礎API?
poll函數(shù)?
int poll(struct pollfd *fds, nfds_t nfds, int timeout); fds:監(jiān)聽的文件描述符,傳入傳出【數(shù)組】 struct pollfd { int fd :待監(jiān)聽的文件描述符 short events:待監(jiān)聽的文件描述符對應的監(jiān)聽事件 取值:POLLIN、POLLOUT、POLLERR short revnets: 傳入時,給0 如果滿足對應事件的話, 返回 非0 --> POLLIN、POLLOUT、POLLERR } nfds: 監(jiān)聽數(shù)組的,實際有效監(jiān)聽個數(shù) timeout: > 0:超時時長。單位:毫秒 -1:阻塞等待 0:不阻塞 返回值:返回滿足對應監(jiān)聽事件的文件描述符 總個數(shù)
poll優(yōu)缺點
優(yōu)點: 自帶數(shù)組結(jié)構(gòu)。 可以將 監(jiān)聽事件集合 和 返回事件集合 分離 拓展 監(jiān)聽上限。 超出 1024限制 缺點: 不能跨平臺。 Linux 無法直接定位滿足監(jiān)聽事件的文件描述符, 編碼難度較大
read函數(shù)返回值
> 0: 實際讀到的字節(jié)數(shù) =0: socket中,表示對端關(guān)閉。close() -1: 如果 errno == EINTR 被異常終端 需要重啟 如果 errno == EAGIN 或 EWOULDBLOCK 以非阻塞方式讀數(shù)據(jù),但是沒有數(shù)據(jù) 需要,再次讀 如果 errno == ECONNRESET 說明連接被 重置 需要 close(),移除監(jiān)聽隊列 錯誤
突破1024 文件描述符限制
cat /proc/sys/fs/file-max ——> 當前計算機所能打開的最大文件個數(shù)。 受硬件影響 ulimit -a ——> 當前用戶下的進程,默認打開文件描述符個數(shù)。 缺省為 1024 修改: 打開 sudo vi /etc/security/limits.conf, 寫入: * soft nofile 65536 --> 設置默認值, 可以直接借助命令修改。 【注銷用戶,使其生效】 * hard nofile 100000 --> 命令修改上限
2.server.c
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <netinet/in.h> #include <arpa/inet.h> #include <poll.h> #include <errno.h> #include "wrap.h" #define MAXLINE 80 #define SERV_PORT 6666 #define OPEN_MAX 1024 int main(int argc,char* argv[]){ int ret=0; /*poll函數(shù)返回值*/ int nready=0; int i,j,maxi; int connectFd,listenFd,socketFd; ssize_t n; char buf[MAXLINE]; char str[INET_ADDRSTRLEN]; socklen_t clientLen; /*創(chuàng)建結(jié)構(gòu)體數(shù)組*/ struct pollfd client[OPEN_MAX]; /*創(chuàng)建客戶端地址結(jié)構(gòu)和服務器地址結(jié)構(gòu)*/ struct sockaddr_in clientAddr,serverAddr; /*得到監(jiān)聽套接字listenFd*/ listenFd=Socket(AF_INET,SOCK_STREAM,0); /*設置地址可復用*/ int opt=0; ret=setsockopt(listenFd,SOL_SOCKET,SO_REUSEADDR,(void*)&opt,sizeof(opt)); if(ret==-1) perr_exit("setsockopt error"); /*向服務器地址結(jié)構(gòu)填入內(nèi)容*/ bzero(&serverAddr,sizeof(serverAddr)); serverAddr.sin_family=AF_INET; serverAddr.sin_addr.s_addr=htonl(INADDR_ANY); serverAddr.sin_port=htons(SERVER_PORT); /*綁定服務器地址結(jié)構(gòu)到監(jiān)聽套接字,并設置監(jiān)聽上限*/ Bind(listenFd,(const struct sockaddr*)&serverAddr,sizeof(serverAddr)); Listen(listenFd,128); /*初始化第一個pollfd為監(jiān)聽套接字*/ client[0].fd=listenFd; //listenfd監(jiān)聽普通讀事件 client[0].events=POLLIN; //事件已經(jīng)準備好被讀取或處理 /*將pollfd數(shù)組的余下內(nèi)容的fd文件描述符屬性置為-1*/ for(i=1;i<OPEN_MAX;++i) client[i].fd=-1; //用-1初始化client[]里剩下元素 maxi=0; //client[]數(shù)組有效元素中最大元素下標 while(1){ /*nready是有多少套接字有POLLIN請求*/ nready=poll(client,maxi+1,-1); //阻塞 if(nready==-1) perr_exit("poll error"); /*如果listenFd的revents有POLLIN請求,則調(diào)用Accept函數(shù)得到connectFd*/ if(client[0].revents&POLLIN){ //有客戶端鏈接請求 clientLen=sizeof(clientAddr); connectFd=Accept(listenFd,(struct sockaddr*)&clientAddr,&clientLen); /*打印客戶端地址結(jié)構(gòu)信息*/ printf("Received from %s at PORT %d\n", inet_ntop(AF_INET,&(clientAddr.sin_addr.s_addr),str,sizeof(str)), ntohs(clientAddr.sin_port)); /*將創(chuàng)建出來的connectFd加入到pollfd數(shù)組中*/ for(i=1;i<OPEN_MAX;++i) if(client[i].fd<0){ //找到client[]中空閑的位置,存放accept返回的connfd client[i].fd=connectFd; break; } if(i==OPEN_MAX) perr_exit("Too many clients,I'm going to die..."); /*當沒有錯誤時,將對應的events設置為POLLIN*/ client[i].events=POLLIN; //設置剛剛返回的connfd,監(jiān)控讀事件 if(i>maxi) maxi=i; //更新client[]中最大元素下標 if(--nready<=0) continue; //沒有更多就緒事件時,繼續(xù)回到poll阻塞 } /*開始從1遍歷pollfd數(shù)組*/ for(i=1;i<=maxi;++i){ //檢測client[] /*到結(jié)尾了或者有異常*/ if((socketFd=client[i].fd)<0) continue; /*第i個客戶端有連接請求,進行處理 read*/ if(client[i].revents&POLLIN){ if((n=read(socketFd,buf,sizeof(buf)))<0){ /*出錯時進一步判斷errno*/ if(errno=ECONNRESET){ printf("client[%d] aborted connection\n",i); close(socketFd); client[i].fd=-1; }else perr_exit("read error"); }else if(n==0){ /*read返回0,說明讀到了結(jié)尾,關(guān)閉連接*/ printf("client[%d] closed connection\n",i); close(socketFd); client[i].fd=-1; }else{ /*數(shù)據(jù)處理*/ for(j=0;j<n;++j) buf[j]=toupper(buf[j]); Writen(STDOUT_FILENO,buf,n); Writen(socketFd,buf,n); } if(--nready==0) break; } } } return 0; }
3.client.c
/* client.c */ #include <stdio.h> #include <string.h> #include <unistd.h> #include <netinet/in.h> #include "wrap.h" #define MAXLINE 80 #define SERV_PORT 6666 int main(int argc, char *argv[]) { struct sockaddr_in servaddr; char buf[MAXLINE]; int sockfd, n; sockfd = Socket(AF_INET, SOCK_STREAM, 0); bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr); servaddr.sin_port = htons(SERV_PORT); Connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)); while (fgets(buf, MAXLINE, stdin) != NULL) { Write(sockfd, buf, strlen(buf)); n = Read(sockfd, buf, MAXLINE); if (n == 0) printf("the other side has been closed.\n"); else Write(STDOUT_FILENO, buf, n); } Close(sockfd); return 0; }
三:epoll
epoll是Linux下多路復用IO接口select/poll的增強版本,它能顯著提高程序在大量并發(fā)連接中只有少量活躍的情況下的系統(tǒng)CPU利用率:都連接但不發(fā)送數(shù)據(jù)?
1.基礎API
紅黑樹
lfd數(shù)據(jù)連接 cfd數(shù)據(jù)通信
epoll_create創(chuàng)建? ?epoll_ctl操作? epoll_wait阻塞
int epoll_create(int size); 創(chuàng)建一棵監(jiān)聽紅黑樹 size:創(chuàng)建的紅黑樹的監(jiān)聽節(jié)點數(shù)量(僅供內(nèi)核參考) 返回值: 成功:指向新創(chuàng)建的紅黑樹的根節(jié)點的 fd 失?。?-1 errno int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); 操作控制監(jiān)聽紅黑樹 epfd:epoll_create 函數(shù)的返回值 epfd op :對該監(jiān)聽紅黑數(shù)所做的操作 EPOLL_CTL_ADD 添加fd到 監(jiān)聽紅黑樹 EPOLL_CTL_MOD 修改fd在 監(jiān)聽紅黑樹上的監(jiān)聽事件 EPOLL_CTL_DEL 將一個fd 從監(jiān)聽紅黑樹上摘下(取消監(jiān)聽) fd:待監(jiān)聽的fd event:本質(zhì)struct epoll_event 結(jié)構(gòu)體 地址 成員 events:EPOLLIN / EPOLLOUT / EPOLLERR EPOLLIN : 表示對應的文件描述符可以讀(包括對端SOCKET正常關(guān)閉) EPOLLOUT: 表示對應的文件描述符可以寫 EPOLLPRI: 表示對應的文件描述符有緊急的數(shù)據(jù)可讀(這里應該表示有帶外數(shù)據(jù)到來) EPOLLERR: 表示對應的文件描述符發(fā)生錯誤 EPOLLHUP: 表示對應的文件描述符被掛斷; EPOLLET: 將EPOLL設為邊緣觸發(fā)(Edge Triggered)模式,這是相對于水平觸發(fā)(Level Triggered)而言的 EPOLLONESHOT:只監(jiān)聽一次事件,當監(jiān)聽完這次事件之后,如果還需要繼續(xù)監(jiān)聽這個socket的話,需要再次把這個socket加入到EPOLL隊列里 成員 typedef union epoll_data: 聯(lián)合體(共用體) int fd; 對應監(jiān)聽事件的 fd void *ptr; uint32_t u32; uint64_t u64; 返回值:成功 0; 失敗: -1 errno int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout); 阻塞監(jiān)聽 epfd:epoll_create 函數(shù)的返回值 epfd events:傳出參數(shù),【數(shù)組】, 滿足監(jiān)聽條件的 哪些 fd 結(jié)構(gòu)體 maxevents:數(shù)組 元素的總個數(shù) 1024(不是字節(jié)數(shù)) struct epoll_event evnets[1024] timeout: -1: 阻塞————通過等待某些特定條件出現(xiàn)來實現(xiàn)的,而在等待的過程中,程序的其他部分都會被暫停執(zhí)行 0:不阻塞 >0: 超時時間 (毫秒) read返回值: > 0: 滿足監(jiān)聽的 總個數(shù),可以用作循環(huán)上限 0:沒有fd滿足監(jiān)聽事件 -1:失敗,errno
epoll實現(xiàn)多路IO轉(zhuǎn)接思路
lfd = socket(); 監(jiān)聽連接事件lfd bind(); listen(); int epfd = epoll_create(1024); epfd, 監(jiān)聽紅黑樹的樹根 struct epoll_event tep, ep[1024]; tep, 用來設置單個fd屬性, ep是epoll_wait() 傳出的滿足監(jiān)聽事件的數(shù)組 tep.events = EPOLLIN; 初始化 lfd的監(jiān)聽屬性_文件描述符可以讀 tep.data.fd = lfd epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &tep); 將 lfd 添加到監(jiān)聽紅黑樹上 while (1) { ret = epoll_wait(epfd, ep,1024, -1); 阻塞監(jiān)聽 for (i = 0; i < ret; i++) { //lfd數(shù)據(jù)連接 if (ep[i].data.fd == lfd) { lfd 滿足讀事件,有新的客戶端發(fā)起連接請求 cfd = Accept(); tep.events = EPOLLIN; 初始化 cfd的監(jiān)聽屬性_文件描述符可以讀 tep.data.fd = cfd; epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &tep); 將 cfd 添加到監(jiān)聽紅黑樹上 } //cfd數(shù)據(jù)通信 else { cfd 們 滿足讀事件, 有客戶端寫數(shù)據(jù)來 n = read(ep[i].data.fd, buf, sizeof(buf)); if ( n == 0) { close(ep[i].data.fd); epoll_ctl(epfd, EPOLL_CTL_DEL, ep[i].data.fd , NULL); 將關(guān)閉的cfd,從監(jiān)聽樹上摘下 } else if (n > 0) { 小--大 write(ep[i].data.fd, buf, n); } } } }
epoll優(yōu)缺點?
優(yōu)點: 高效。突破1024文件描述符 缺點: 不能跨平臺。 Linux
ctags使用
是vim下方便代碼閱讀的工具1 `ctags ./* -R`在項目目錄下生成ctags文件; `Ctrl+]`跳轉(zhuǎn)到函數(shù)定義的位置; `Ctrl+t`返回此前的跳轉(zhuǎn)位置; `Ctrl+o`屏幕左邊列出文件列表, 再按關(guān)閉; `F4`屏幕右邊列出函數(shù)列表, 再按關(guān)閉; (還是VSCode比較香)
2.server.c
#include "033-035_wrap.h" #define SERVER_PORT 9527 #define MAXLINE 80 #define OPEN_MAX 1024 int main(int argc,char* argv[]){ int i=0,n=0,num=0; int clientAddrLen=0; int listenFd=0,connectFd=0,socketFd=0; ssize_t nready,efd,res; char buf[MAXLINE],str[INET_ADDRSTRLEN]; struct sockaddr_in serverAddr,clientAddr; /*創(chuàng)建一個臨時節(jié)點temp和一個數(shù)組ep*/ struct epoll_event temp; struct epoll_event ep[OPEN_MAX]; /*創(chuàng)建監(jiān)聽套接字*/ listenFd=Socket(AF_INET,SOCK_STREAM,0); /*設置地址可復用*/ int opt=1; setsockopt(listenFd,SOL_SOCKET,SO_REUSEADDR,(void*)&opt,sizeof(opt)); /*初始化服務器地址結(jié)構(gòu)*/ bzero(&serverAddr,sizeof(serverAddr)); serverAddr.sin_family=AF_INET; serverAddr.sin_addr.s_addr=htonl(INADDR_ANY); serverAddr.sin_port=htons(SERVER_PORT); /*綁定服務器地址結(jié)構(gòu)*/ Bind(listenFd,(const struct sockaddr*)&serverAddr,sizeof(serverAddr)); /*設置監(jiān)聽上限*/ Listen(listenFd,128); /*創(chuàng)建監(jiān)聽紅黑樹樹根*/ efd=epoll_create(OPEN_MAX); if(efd==-1) perr_exit("epoll_create error"); /*將listenFd加入監(jiān)聽紅黑樹中*/ temp.events=EPOLLIN; temp.data.fd=listenFd; res=epoll_ctl(efd,EPOLL_CTL_ADD,listenFd,&temp); if(res==-1) perr_exit("epoll_ctl error"); while(1){ /*阻塞監(jiān)聽寫事件*/ nready=epoll_wait(efd,ep,OPEN_MAX,-1); if(nready==-1) perr_exit("epoll_wait error"); /*輪詢整個數(shù)組(紅黑樹)*/ for(i=0;i<nready;++i){ if(!(ep[i].events&EPOLLIN)) continue; /*如果是建立連接請求*/ // lfd 滿足讀事件,有新的客戶端發(fā)起連接請求 if(ep[i].data.fd==listenFd){ clientAddrLen=sizeof(clientAddr); connectFd=Accept(listenFd,(struct sockaddr*)&clientAddr,&clientAddrLen); printf("Received from %s at PORT %d\n", inet_ntop(AF_INET, &clientAddr.sin_addr.s_addr, str, sizeof(str)), ntohs(clientAddr.sin_port)); printf("connectFd=%d,client[%d]\n",connectFd,++num); /*將新創(chuàng)建的連接套接字加入紅黑樹*/ //初始化 cfd的監(jiān)聽屬性_文件描述符可以讀 temp.events=EPOLLIN; temp.data.fd=connectFd; res=epoll_ctl(efd,EPOLL_CTL_ADD,connectFd,&temp); if(res==-1) perr_exit("epoll_ctl errror"); }else{ /*不是建立連接請求,是數(shù)據(jù)處理請求*/ socketFd=ep[i].data.fd; //cfd 們 滿足讀事件, 有客戶端寫數(shù)據(jù)來 n=read(socketFd,buf,sizeof(buf)); /*讀到0說明客戶端關(guān)閉*/ //已經(jīng)讀到結(jié)尾 if(n==0){ res=epoll_ctl(efd,EPOLL_CTL_DEL,socketFd,NULL); if(res==-1) perr_exit("epoll_ctl error"); close(socketFd); printf("client[%d] closed connection\n",socketFd); //報錯 }else if(n<0){ /*n<0報錯*/ perr_exit("read n<0 error"); // 將關(guān)閉的cfd,從監(jiān)聽樹上摘下 res=epoll_ctl(efd,EPOLL_CTL_DEL,socketFd,NULL); close(socketFd); // > 0實際讀到的字節(jié)數(shù) }else{ /*數(shù)據(jù)處理*/ for(i=0;i<n;++i) buf[i]=toupper(buf[i]); write(STDOUT_FILENO,buf,n); Writen(socketFd,buf,n); } } } } close(listenFd); close(efd); return 0; }
3.client.c
/* client.c */ #include <stdio.h> #include <string.h> #include <unistd.h> #include <netinet/in.h> #include "wrap.h" #define MAXLINE 80 #define SERV_PORT 6666 int main(int argc, char *argv[]) { struct sockaddr_in servaddr; char buf[MAXLINE]; int sockfd, n; sockfd = Socket(AF_INET, SOCK_STREAM, 0); bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr); servaddr.sin_port = htons(SERV_PORT); Connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)); while (fgets(buf, MAXLINE, stdin) != NULL) { Write(sockfd, buf, strlen(buf)); n = Read(sockfd, buf, MAXLINE); if (n == 0) printf("the other side has been closed.\n"); else Write(STDOUT_FILENO, buf, n); } Close(sockfd); return 0; }
4.事件模型(epoll 事件觸發(fā)模型ET和LT)
ET工作模式:邊沿觸發(fā)————只有數(shù)據(jù)到來才觸發(fā),不管緩存區(qū)中是否還有數(shù)據(jù),緩沖區(qū)剩余未讀盡的數(shù)據(jù)不會導致 作用:當文件描述符從未就緒變?yōu)榫途w時,內(nèi)核會通過epoll告訴你一次喊你就緒,直到你做操作導致那個文件描述符不再為就緒狀態(tài) 緩沖區(qū)未讀盡的數(shù)據(jù)不會導致epoll_wait返回, 新的數(shù)據(jù)寫入才會觸發(fā)(等文件描述符不再為就緒狀態(tài)) struct epoll_event event event.events = EPOLLIN | EPOLLET LT工作模式:水平觸發(fā)————只要有數(shù)據(jù)都會觸發(fā)(默認采用模式) 作用:內(nèi)核告訴你一個文件描述符是否就緒,然后可以對這個就緒的fd進行io操作,如果你不做任何操作,內(nèi)核還會繼續(xù)通知你 緩沖區(qū)未讀盡的數(shù)據(jù)會導致epoll_wait返回(繼續(xù)通知你) 結(jié)論:epoll 的 ET模式, 高效模式,但是只支持 非阻塞模式 --- 忙輪詢:用于在計算機系統(tǒng)中處理硬件中斷 忙輪詢是一種不進入內(nèi)核的方式,它在用戶空間中輪詢檢測硬件狀態(tài) 及時響應硬件的中斷請求,避免CPU在中斷服務程序中處理完所有的中斷請求后,又再次觸發(fā)中斷 struct epoll_event event; event.events = EPOLLIN | EPOLLET; epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &event); int flg = fcntl(cfd, F_GETFL); 非阻塞 flg |= O_NONBLOCK; fcntl(cfd, F_SETFL, flg);
代碼實現(xiàn)?文章來源:http://www.zghlxwxcb.cn/news/detail-668987.html
#include <stdio.h> #include <stdlib.h> #include <sys/epoll.h> #include <errno.h> #include <unistd.h> #define MAXLINE 10 int main(int argc, char *argv[]) { int efd, i; int pfd[2]; pid_t pid; char buf[MAXLINE], ch = 'a'; pipe(pfd); pid = fork(); if (pid == 0) { //子 寫 close(pfd[0]); while (1) { //aaaa\n for (i = 0; i < MAXLINE/2; i++) buf[i] = ch; buf[i-1] = '\n'; ch++; //bbbb\n for (; i < MAXLINE; i++) buf[i] = ch; buf[i-1] = '\n'; ch++; //aaaa\nbbbb\n write(pfd[1], buf, sizeof(buf)); sleep(5); } close(pfd[1]); } else if (pid > 0) { //父 讀 struct epoll_event event; struct epoll_event resevent[10]; //epoll_wait就緒返回event int res, len; close(pfd[1]); efd = epoll_create(10); event.events = EPOLLIN | EPOLLET; // ET 邊沿觸發(fā) // event.events = EPOLLIN; // LT 水平觸發(fā) (默認) event.data.fd = pfd[0]; epoll_ctl(efd, EPOLL_CTL_ADD, pfd[0], &event); while (1) { res = epoll_wait(efd, resevent, 10, -1); printf("res %d\n", res); if (resevent[0].data.fd == pfd[0]) { len = read(pfd[0], buf, MAXLINE/2); write(STDOUT_FILENO, buf, len); } } close(pfd[0]); close(efd); } else { perror("fork"); exit(-1); } return 0; }
4.1 server.c
#include <stdio.h> #include <string.h> #include <netinet/in.h> #include <arpa/inet.h> #include <signal.h> #include <sys/wait.h> #include <sys/types.h> #include <sys/epoll.h> #include <unistd.h> #define MAXLINE 10 #define SERV_PORT 9000 int main(void) { struct sockaddr_in servaddr, cliaddr; socklen_t cliaddr_len; int listenfd, connfd; char buf[MAXLINE]; char str[INET_ADDRSTRLEN]; int efd; listenfd = socket(AF_INET, SOCK_STREAM, 0); bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl(INADDR_ANY); servaddr.sin_port = htons(SERV_PORT); bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)); listen(listenfd, 20); struct epoll_event event; struct epoll_event resevent[10]; int res, len; efd = epoll_create(10); event.events = EPOLLIN | EPOLLET; /* ET 邊沿觸發(fā) */ //event.events = EPOLLIN; /* 默認 LT 水平觸發(fā) */ printf("Accepting connections ...\n"); cliaddr_len = sizeof(cliaddr); connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len); printf("received from %s at PORT %d\n", inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)), ntohs(cliaddr.sin_port)); event.data.fd = connfd; epoll_ctl(efd, EPOLL_CTL_ADD, connfd, &event); while (1) { res = epoll_wait(efd, resevent, 10, -1); printf("res %d\n", res); if (resevent[0].data.fd == connfd) { len = read(connfd, buf, MAXLINE/2); //readn(500) write(STDOUT_FILENO, buf, len); } } return 0; }
4.2 client.c
#include <stdio.h> #include <string.h> #include <unistd.h> #include <arpa/inet.h> #include <netinet/in.h> #define MAXLINE 10 #define SERV_PORT 9000 int main(int argc, char *argv[]) { struct sockaddr_in servaddr; char buf[MAXLINE]; int sockfd, i; char ch = 'a'; sockfd = socket(AF_INET, SOCK_STREAM, 0); bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr); servaddr.sin_port = htons(SERV_PORT); connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)); while (1) { //aaaa\n for (i = 0; i < MAXLINE/2; i++) buf[i] = ch; buf[i-1] = '\n'; ch++; //bbbb\n for (; i < MAXLINE; i++) buf[i] = ch; buf[i-1] = '\n'; ch++; //aaaa\nbbbb\n write(sockfd, buf, sizeof(buf)); sleep(5); } close(sockfd); return 0; }
5.epoll 反應堆模型
作用:提高網(wǎng)絡IO處理的效率 epoll ET模式 + 非阻塞、輪詢 + void *ptr void *ptr:指向結(jié)構(gòu)體,該結(jié)構(gòu)體包含socket、地址、端口等信息 原來:epoll實現(xiàn)多路IO轉(zhuǎn)接思路 socket、bind、listen -- epoll_create 創(chuàng)建監(jiān)聽 紅黑樹 -- 返回 epfd -- epoll_ctl() 向樹上添加一個監(jiān)聽fd -- while(1)-- -- epoll_wait 監(jiān)聽 -- 對應監(jiān)聽fd有事件產(chǎn)生 -- 返回 監(jiān)聽滿足數(shù)組。 -- 判斷返回數(shù)組元素 -- lfd滿足 -- Accept -- cfd 滿足 -- read() --- 小->大 -- write回去 反應堆:不但要監(jiān)聽 cfd 的讀事件、還要監(jiān)聽cfd的寫事件 socket、bind、listen -- epoll_create 創(chuàng)建監(jiān)聽 紅黑樹 -- 返回 epfd -- epoll_ctl() 向樹上添加一個監(jiān)聽fd -- while(1)-- -- epoll_wait 監(jiān)聽 -- 對應監(jiān)聽fd有事件產(chǎn)生 -- 返回 監(jiān)聽滿足數(shù)組。 -- 判斷返回數(shù)組元素 -- lfd滿足 -- Accept -- cfd 滿足 -- read() --- 小->大 -- cfd從監(jiān)聽紅黑樹上摘下 -- EPOLLOUT -- 回調(diào)函數(shù) -- epoll_ctl() -- EPOLL_CTL_ADD 重新放到紅黑上監(jiān)聽“寫”事件-- 等待 epoll_wait 返回 -- 說明 cfd 可寫 -- write回去 -- cfd從監(jiān)聽紅黑樹上摘下 -- EPOLLIN -- epoll_ctl() -- EPOLL_CTL_ADD 重新放到紅黑上監(jiān)聽“讀”事件 -- epoll_wait 監(jiān)聽 eventset函數(shù):設置回調(diào)函數(shù) lfd --> acceptconn() cfd --> recvdata(); cfd --> senddata(); eventadd函數(shù):將一個fd, 添加到 監(jiān)聽紅黑樹 設置監(jiān)聽讀事件,還是監(jiān)聽寫事件 網(wǎng)絡編程中: read --- recv() write --- send();
epoll基于非阻塞I/O事件驅(qū)動文章來源地址http://www.zghlxwxcb.cn/news/detail-668987.html
/* *epoll基于非阻塞I/O事件驅(qū)動 */ #include <stdio.h> #include <sys/socket.h> #include <sys/epoll.h> #include <arpa/inet.h> #include <fcntl.h> #include <unistd.h> #include <errno.h> #include <string.h> #include <stdlib.h> #include <time.h> #define MAX_EVENTS 1024 //監(jiān)聽上限數(shù) #define BUFLEN 4096 #define SERV_PORT 8080 //默認端口號 void recvdata(int fd, int events, void *arg); void senddata(int fd, int events, void *arg); /* 描述就緒文件描述符相關(guān)信息 */ struct myevent_s { int fd; //要監(jiān)聽的文件描述符 int events; //對應的監(jiān)聽事件 void *arg; //泛型參數(shù) void (*call_back)(int fd, int events, void *arg); //回調(diào)函數(shù) int status; //是否在監(jiān)聽:1->在紅黑樹上(監(jiān)聽), 0->不在(不監(jiān)聽) char buf[BUFLEN]; int len; long last_active; //記錄每次加入紅黑樹 g_efd 的時間值 }; int g_efd; //全局變量, 保存epoll_create返回的文件描述符 struct myevent_s g_events[MAX_EVENTS+1]; //自定義結(jié)構(gòu)體類型數(shù)組. +1-->listen fd /*將結(jié)構(gòu)體 myevent_s 成員變量 初始化賦值*/ void eventset(struct myevent_s *ev, int fd, void (*call_back)(int, int, void *), void *arg) { ev->fd = fd; ev->call_back = call_back; //設置回調(diào)函數(shù) ev->events = 0; ev->arg = arg; ev->status = 0; memset(ev->buf, 0, sizeof(ev->buf)); ev->len = 0; ev->last_active = time(NULL); //調(diào)用eventset函數(shù)的時間 return; } /* 向 epoll監(jiān)聽的紅黑樹 添加一個 文件描述符 */ //eventadd函數(shù): 將一個fd添加到監(jiān)聽紅黑樹, 設置監(jiān)聽讀事件還是寫事件 //eventadd(efd, EPOLLIN, &g_events[MAX_EVENTS]); void eventadd(int efd, int events, struct myevent_s *ev) { struct epoll_event epv = {0, {0}}; int op; epv.data.ptr = ev; epv.events = ev->events = events; //EPOLLIN 或 EPOLLOUT if (ev->status == 0) { //已經(jīng)在紅黑樹 g_efd 里 op = EPOLL_CTL_ADD; //將其加入紅黑樹 g_efd, 并將status置1 ev->status = 1; } if (epoll_ctl(efd, op, ev->fd, &epv) < 0) //實際添加/修改 printf("event add failed [fd=%d], events[%d]\n", ev->fd, events); else printf("event add OK [fd=%d], op=%d, events[%0X]\n", ev->fd, op, events); return ; } /* 從epoll 監(jiān)聽的 紅黑樹中刪除一個 文件描述符*/ void eventdel(int efd, struct myevent_s *ev) { struct epoll_event epv = {0, {0}}; if (ev->status != 1) //不在紅黑樹上 return ; //epv.data.ptr = ev; epv.data.ptr = NULL; ev->status = 0; //修改狀態(tài) epoll_ctl(efd, EPOLL_CTL_DEL, ev->fd, &epv); //從紅黑樹 efd 上將 ev->fd 摘除 return ; } /* 當有文件描述符就緒, epoll返回, 調(diào)用該函數(shù) 與客戶端建立鏈接 */ void acceptconn(int lfd, int events, void *arg) { struct sockaddr_in cin; socklen_t len = sizeof(cin); int cfd, i; if ((cfd = accept(lfd, (struct sockaddr *)&cin, &len)) == -1) { if (errno != EAGAIN && errno != EINTR) { /* 暫時不做出錯處理 */ } printf("%s: accept, %s\n", __func__, strerror(errno)); return ; } do { for (i = 0; i < MAX_EVENTS; i++) //從全局數(shù)組g_events中找一個空閑元素 if (g_events[i].status == 0) //類似于select中找值為-1的元素 break; //跳出 for if (i == MAX_EVENTS) { printf("%s: max connect limit[%d]\n", __func__, MAX_EVENTS); break; //跳出do while(0) 不執(zhí)行后續(xù)代碼 } int flag = 0; if ((flag = fcntl(cfd, F_SETFL, O_NONBLOCK)) < 0) { //將cfd也設置為非阻塞 printf("%s: fcntl nonblocking failed, %s\n", __func__, strerror(errno)); break; } /* 給cfd設置一個 myevent_s 結(jié)構(gòu)體, 回調(diào)函數(shù) 設置為 recvdata */ eventset(&g_events[i], cfd, recvdata, &g_events[i]); eventadd(g_efd, EPOLLIN, &g_events[i]); //將cfd添加到紅黑樹g_efd中,監(jiān)聽讀事件 } while(0); printf("new connect [%s:%d][time:%ld], pos[%d]\n", inet_ntoa(cin.sin_addr), ntohs(cin.sin_port), g_events[i].last_active, i); return ; } //epoll反應堆-wait被觸發(fā)后read和write回調(diào)及監(jiān)聽 void recvdata(int fd, int events, void *arg) { struct myevent_s *ev = (struct myevent_s *)arg; int len; len = recv(fd, ev->buf, sizeof(ev->buf), 0); //讀文件描述符, 數(shù)據(jù)存入myevent_s成員buf中 eventdel(g_efd, ev); //將該節(jié)點從紅黑樹上摘除 if (len > 0) { ev->len = len; ev->buf[len] = '\0'; //手動添加字符串結(jié)束標記 printf("C[%d]:%s\n", fd, ev->buf); eventset(ev, fd, senddata, ev); //設置該 fd 對應的回調(diào)函數(shù)為 senddata eventadd(g_efd, EPOLLOUT, ev); //將fd加入紅黑樹g_efd中,監(jiān)聽其寫事件 } else if (len == 0) { close(ev->fd); /* ev-g_events 地址相減得到偏移元素位置 */ printf("[fd=%d] pos[%ld], closed\n", fd, ev-g_events); } else { close(ev->fd); printf("recv[fd=%d] error[%d]:%s\n", fd, errno, strerror(errno)); } return; } void senddata(int fd, int events, void *arg) { struct myevent_s *ev = (struct myevent_s *)arg; int len; len = send(fd, ev->buf, ev->len, 0); //直接將數(shù)據(jù) 回寫給客戶端。未作處理 eventdel(g_efd, ev); //從紅黑樹g_efd中移除 if (len > 0) { printf("send[fd=%d], [%d]%s\n", fd, len, ev->buf); eventset(ev, fd, recvdata, ev); //將該fd的 回調(diào)函數(shù)改為 recvdata eventadd(g_efd, EPOLLIN, ev); //從新添加到紅黑樹上, 設為監(jiān)聽讀事件 } else { close(ev->fd); //關(guān)閉鏈接 printf("send[fd=%d] error %s\n", fd, strerror(errno)); } return ; } /*創(chuàng)建 socket, 初始化lfd */ void initlistensocket(int efd, short port) { struct sockaddr_in sin; //將socket設為lfd非阻塞 int lfd = socket(AF_INET, SOCK_STREAM, 0); fcntl(lfd, F_SETFL, O_NONBLOCK); //設置地址結(jié)構(gòu) memset(&sin, 0, sizeof(sin)); //bzero(&sin, sizeof(sin)) sin.sin_family = AF_INET; sin.sin_addr.s_addr = INADDR_ANY; sin.sin_port = htons(port); bind(lfd, (struct sockaddr *)&sin, sizeof(sin)); listen(lfd, 20); /* void eventset(struct myevent_s *ev, int fd, void (*call_back)(int, int, void *), void *arg); */ /*把g_events數(shù)組的最后一個元素設置為lfd,回調(diào)函數(shù)設置為acceptconn*/ eventset(&g_events[MAX_EVENTS], lfd, acceptconn, &g_events[MAX_EVENTS]); /* void eventadd(int efd, int events, struct myevent_s *ev) */ /*掛上樹*/ eventadd(efd, EPOLLIN, &g_events[MAX_EVENTS]); return ; } int main(int argc, char *argv[]) { /*選擇默認端口號或指定端口號*/ unsigned short port = SERV_PORT; if (argc == 2) //使用用戶指定端口.如未指定,用默認端口 port = atoi(argv[1]); //創(chuàng)建紅黑樹,返回給全局 g_efd g_efd = epoll_create(MAX_EVENTS+1); if (g_efd <= 0) printf("create efd in %s err %s\n", __func__, strerror(errno)); //初始化監(jiān)聽socket initlistensocket(g_efd, port); //創(chuàng)建一個系統(tǒng)的epoll_event的數(shù)組,與my_events的規(guī)模相同 struct epoll_event events[MAX_EVENTS+1]; //保存已經(jīng)滿足就緒事件的文件描述符數(shù)組 printf("server running:port[%d]\n", port); int checkpos = 0, i; while (1) { /* 超時驗證,每次測試100個鏈接,不測試listenfd 當客戶端60秒內(nèi)沒有和服務器通信,則關(guān)閉此客戶端鏈接 */ long now = time(NULL); //當前時間 for (i = 0; i < 100; i++, checkpos++) { //一次循環(huán)檢測100個。 使用checkpos控制檢測對象 if (checkpos == MAX_EVENTS) checkpos = 0; if (g_events[checkpos].status != 1) //不在紅黑樹 g_efd 上 continue; long duration = now - g_events[checkpos].last_active; //時間間隔,客戶端不活躍的世間 if (duration >= 60) { close(g_events[checkpos].fd); //關(guān)閉與該客戶端鏈接 printf("[fd=%d] timeout\n", g_events[checkpos].fd); eventdel(g_efd, &g_events[checkpos]); //將該客戶端 從紅黑樹 g_efd移除 } } /*監(jiān)聽紅黑樹g_efd, 將滿足的事件的文件描述符加至events數(shù)組中, 1秒沒有事件滿足, 返回 0*/ int nfd = epoll_wait(g_efd, events, MAX_EVENTS+1, 1000); if (nfd < 0) { printf("epoll_wait error, exit\n"); break; } for (i = 0; i < nfd; i++) { /*使用自定義結(jié)構(gòu)體myevent_s類型指針, 接收 聯(lián)合體data的void *ptr成員*/ struct myevent_s *ev = (struct myevent_s *)events[i].data.ptr; //cfd從監(jiān)聽紅黑樹上摘下 if ((events[i].events & EPOLLIN) && (ev->events & EPOLLIN)) { //讀就緒事件 ev->call_back(ev->fd, events[i].events, ev->arg); //lfd EPOLLIN } if ((events[i].events & EPOLLOUT) && (ev->events & EPOLLOUT)) { //寫就緒事件 ev->call_back(ev->fd, events[i].events, ev->arg); } } } /* 退出前釋放所有資源 */ return 0; }
到了這里,關(guān)于Linux網(wǎng)絡編程:多路I/O轉(zhuǎn)接服務器(select poll epoll)的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!