Linux C編程第十八章 高并發服務器(三)

      網友投稿 613 2025-04-01

      epoll工作原理:

      通過下面的偽代碼有助于上面的理解:

      epoll偽代碼

      1 int main() 2 { 3 // 創建監聽的套接字 4 int lfd = socket(); 5 // 綁定 6 bind(); 7 // 監聽 8 listen(); 9 10 // epoll樹根節點 11 int epfd = epoll_create(3000); 12 // 存儲發送變化的fd對應信息 13 struct epoll_event all[3000]; 14 // init 15 // 監聽的lfd掛到epoll樹上 16 struct epoll_event ev; 17 // 在ev中init lfd信息 18 ev.events = EPOLLIN ; 19 ev.data.fd = lfd; 20 epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &ev); 21 while(1) 22 { 23 // 委托內核檢測事件 24 int ret = epoll_wait(epfd, all, 3000, -1); 25 // 根據ret遍歷all數組 26 for(int i=0; i

      示例:

      epoll.c

      1 #include 2 #include 3 #include 4 #include 5 #include 6 #include 7 #include 8 #include 9 #include 10 11 12 int main(int argc, const char* argv[]) 13 { 14 if(argc < 2) 15 { 16 printf("eg: ./a.out port\n"); 17 exit(1); 18 } 19 struct sockaddr_in serv_addr; 20 socklen_t serv_len = sizeof(serv_addr); 21 int port = atoi(argv[1]); 22 23 // 創建套接字 24 int lfd = socket(AF_INET, SOCK_STREAM, 0); 25 // 初始化服務器 sockaddr_in 26 memset(&serv_addr, 0, serv_len); 27 serv_addr.sin_family = AF_INET; // 地址族 28 serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); // 監聽本機所有的IP 29 serv_addr.sin_port = htons(port); // 設置端口 30 // 綁定IP和端口 31 bind(lfd, (struct sockaddr*)&serv_addr, serv_len); 32 33 // 設置同時監聽的最大個數 34 listen(lfd, 36); 35 printf("Start accept ......\n"); 36 37 struct sockaddr_in client_addr; 38 socklen_t cli_len = sizeof(client_addr); 39 40 // 創建epoll樹根節點 41 int epfd = epoll_create(2000); 42 // 初始化epoll樹 43 struct epoll_event ev; 44 ev.events = EPOLLIN; 45 ev.data.fd = lfd; 46 epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &ev); 47 48 struct epoll_event all[2000]; 49 while(1) 50 { 51 // 使用epoll通知內核fd 文件IO檢測 sizeof(all)/sizeof(all[0]) --> sizeof(struct epoll_event) 52 int ret = epoll_wait(epfd, all, sizeof(all)/sizeof(all[0]), -1); 53 54 // 遍歷all數組中的前ret個元素 55 for(int i=0; i

      client.c

      1 /* client.c */ 2 #include 3 #include 4 #include 5 #include 6 #include "wrap.h" 7 8 #define MAXLINE 80 9 #define SERV_PORT 6666 10 11 int main(int argc, char *argv[]) 12 { 13 struct sockaddr_in servaddr; 14 char buf[MAXLINE]; 15 int sockfd, n; 16 17 sockfd = Socket(AF_INET, SOCK_STREAM, 0); 18 19 bzero(&servaddr, sizeof(servaddr)); 20 servaddr.sin_family = AF_INET; 21 inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr); 22 servaddr.sin_port = htons(SERV_PORT); 23 24 Connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)); 25 26 while (fgets(buf, MAXLINE, stdin) != NULL) { 27 Write(sockfd, buf, strlen(buf)); 28 n = Read(sockfd, buf, MAXLINE); 29 if (n == 0) 30 printf("the other side has been closed.\n"); 31 else 32 Write(STDOUT_FILENO, buf, n); 33 } 34 35 Close(sockfd); 36 return 0; 37 }

      注意:epoll_wait 調用次數越多, 系統的開銷越大

      四、epoll進階

      1.?事件模型

      EPOLL事件有兩種模型:

      Edge Triggered (ET) 邊緣觸發只有數據到來才觸發,不管緩存區中是否還有數據。

      Level Triggered (LT) 水平觸發只要有數據都會觸發。

      思考如下步驟:

      1)假定我們已經把一個用來從管道中讀取數據的文件描述符(RFD)添加到epoll描述符。

      2)管道的另一端寫入了2KB的數據

      3)調用epoll_wait,并且它會返回RFD,說明它已經準備好讀取操作

      4)讀取1KB的數據

      5)調用epoll_wait……

      在這個過程中,有兩種工作模式:

      (1)ET模式

      ET模式即Edge Triggered工作模式。

      如果我們在第1步將RFD添加到epoll描述符的時候使用了EPOLLET標志,那么在第5步調用epoll_wait之后將有可能會掛起,因為剩余的數據還存在于文件的輸入緩沖區內,而且數據發出端還在

      等待一個針對已經發出數據的反饋信息。只有在監視的文件句柄上發生了某個事件的時候 ET 工作模式才會匯報事件。因此在第5步的時候,調用者可能會放棄等待仍在存在于文件輸入緩沖區內的剩

      余數據。epoll工作在ET模式的時候,必須使用非阻塞套接口,以避免由于一個文件句柄的阻塞讀/阻塞寫操作把處理多個文件描述符的任務餓死。最好以下面的方式調用ET模式的epoll接口,在后面

      會介紹避免可能的缺陷。

      基于非阻塞文件句柄

      只有當read或者write返回EAGAIN(非阻塞讀,暫時無數據)時才需要掛起、等待。但這并不是說每次read時都需要循環讀,直到讀到產生一個EAGAIN才認為此次事件處理完成,當read返回的

      讀到的數據長度小于請求的數據長度時,就可以確定此時緩沖中已沒有數據了,也就可以認為此事讀事件已處理完成。

      (2)LT模式

      LT模式即Level Triggered工作模式。

      與ET模式不同的是,以LT方式調用epoll接口的時候,它就相當于一個速度比較快的poll,無論后面的數據是否被使用。

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

      何操作,內核還是會繼續通知你的,所以,這種模式編程出錯誤可能性要小一點。傳統的select/poll都是這種模型的代表。

      ET(edge-triggered):ET是高速工作方式,只支持no-block socket。在這種模式下,當描述符從未就緒變為就緒時,內核通過epoll告訴你。然后它會假設你知道文件描述符已經就緒,并且不會再

      為那個文件描述符發送更多的就緒通知。請注意,如果一直不對這個fd作IO操作(從而導致它再次變成未就緒),內核不會發送更多的通知(only once)。

      2.??實例一

      基于管道epoll ET觸發模式

      et_epoll.c

      1 #include 2 #include 3 #include 4 #include 5 #include 6 7 #define MAXLINE 10 8 9 int main(int argc, char *argv[]) 10 { 11 int efd, i; 12 int pfd[2]; 13 pid_t pid; 14 char buf[MAXLINE], ch = 'a'; 15 16 pipe(pfd); 17 pid = fork(); 18 if (pid == 0) { 19 close(pfd[0]); 20 while (1) { 21 for (i = 0; i < MAXLINE/2; i++) 22 buf[i] = ch; 23 buf[i-1] = '\n'; 24 ch++; 25 26 for (; i < MAXLINE; i++) 27 buf[i] = ch; 28 buf[i-1] = '\n'; 29 ch++; 30 31 write(pfd[1], buf, sizeof(buf)); 32 sleep(2); 33 } 34 close(pfd[1]); 35 } else if (pid > 0) { 36 struct epoll_event event; 37 struct epoll_event resevent[10]; 38 int res, len; 39 close(pfd[1]); 40 41 efd = epoll_create(10); 42 /* event.events = EPOLLIN; */ 43 event.events = EPOLLIN | EPOLLET; /* ET 邊沿觸發 ,默認是水平觸發 */ 44 event.data.fd = pfd[0]; 45 epoll_ctl(efd, EPOLL_CTL_ADD, pfd[0], &event); 46 47 while (1) { 48 res = epoll_wait(efd, resevent, 10, -1); 49 printf("res %d\n", res); 50 if (resevent[0].data.fd == pfd[0]) { 51 len = read(pfd[0], buf, MAXLINE/2); 52 write(STDOUT_FILENO, buf, len); 53 } 54 } 55 close(pfd[0]); 56 close(efd); 57 } else { 58 perror("fork"); 59 exit(-1); 60 } 61 return 0; 62 }

      3.?實例二

      基于網絡C/S模型的epoll ET觸發模式

      et_epoll.c

      1 #include 2 #include 3 #include 4 #include 5 #include 6 #include 7 #include 8 #include 9 #include 10 11 12 int main(int argc, const char* argv[]) 13 { 14 if(argc < 2) 15 { 16 printf("eg: ./a.out port\n"); 17 exit(1); 18 } 19 struct sockaddr_in serv_addr; 20 socklen_t serv_len = sizeof(serv_addr); 21 int port = atoi(argv[1]); 22 23 // 創建套接字 24 int lfd = socket(AF_INET, SOCK_STREAM, 0); 25 // 初始化服務器 sockaddr_in 26 memset(&serv_addr, 0, serv_len); 27 serv_addr.sin_family = AF_INET; // 地址族 28 serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); // 監聽本機所有的IP 29 serv_addr.sin_port = htons(port); // 設置端口 30 // 綁定IP和端口 31 bind(lfd, (struct sockaddr*)&serv_addr, serv_len); 32 33 // 設置同時監聽的最大個數 34 listen(lfd, 36); 35 printf("Start accept ......\n"); 36 37 struct sockaddr_in client_addr; 38 socklen_t cli_len = sizeof(client_addr); 39 40 // 創建epoll樹根節點 41 int epfd = epoll_create(2000); 42 // 初始化epoll樹 43 struct epoll_event ev; 44 45 // 設置邊沿觸發 46 ev.events = EPOLLIN | EPOLLET; 47 ev.data.fd = lfd; 48 epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &ev); 49 50 struct epoll_event all[2000]; 51 while(1) 52 { 53 // 使用epoll通知內核fd 文件IO檢測 54 int ret = epoll_wait(epfd, all, sizeof(all)/sizeof(all[0]), -1); 55 printf("================== epoll_wait =============\n"); 56 57 // 遍歷all數組中的前ret個元素 58 for(int i=0; i

      tcp_client.c

      1 #include 2 #include 3 #include 4 #include 5 #include 6 #include 7 #include 8 #include 9 10 // tcp client 11 int main(int argc, const char* argv[]) 12 { 13 if(argc < 2) 14 { 15 printf("eg: ./a.out port\n"); 16 exit(1); 17 } 18 // 創建套接字 19 int fd = socket(AF_INET, SOCK_STREAM, 0); 20 if(fd == -1) 21 { 22 perror("socket error"); 23 exit(1); 24 } 25 int port = atoi(argv[1]); 26 // 連接服務器 27 struct sockaddr_in serv_addr; 28 memset(&serv_addr, 0, sizeof(serv_addr)); 29 serv_addr.sin_family = AF_INET; 30 serv_addr.sin_port = htons(port); 31 inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr.s_addr); 32 int ret = connect(fd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)); 33 if(ret == -1) 34 { 35 perror("connect error"); 36 exit(1); 37 } 38 39 // 通信 40 while(1) 41 { 42 // 寫數據 43 // 接收鍵盤輸入 44 char buf[512]; 45 fgets(buf, sizeof(buf), stdin); 46 // 發送給服務器 47 write(fd, buf, strlen(buf)+1); 48 49 // 接收服務器端的數據 50 int len = read(fd, buf, sizeof(buf)); 51 printf("read buf = %s, len = %d\n", buf, len); 52 } 53 return 0; 54 }

      執行結果:

      client端:

      [root@centos epoll]# ./client 6666 000001111122222 read buf = 000001111122222 , len = 5 read buf = 11111, len = 5 read buf = 22222, len = 5

      server端:

      【Linux C編程】第十八章 高并發服務器(三)

      [root@centos epoll]# ./et_epoll 6666 Start accept ...... ================== epoll_wait ============= New Client IP: 127.0.0.1, Port: 54080 ================== epoll_wait ============= 00000================== epoll_wait ============= 11111================== epoll_wait ============= 22222

      執行結果分析:可以看出,當客戶端發送數據(000001111122222)到server端(接收數據緩沖區內),但是由于server端一次只接受5個字節(00000),因此在接受完5個字節之后,將接收的5個字節數據發回給客戶端,程序又會在epoll_wait處阻塞等待。當有新數據再次發送過來,則會將上一次緩沖區中剩余的數據(11111)讀取并發送給客戶端,如此最后將(22222)發送給客戶端。

      【Linux C編程】第十八章 高并發服務器(四)

      Linux 數據結構

      版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。

      版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。

      上一篇:WPS表格怎么選定區域生成圖片(wps根據圖片生成表格)
      下一篇:同時打開兩個WPS表絡,為什么點擊這個表格,別一個表格,會關掉呢
      相關文章
      亚洲精品国产精品乱码不卞 | 亚洲高清在线视频| 亚洲国产成人久久一区久久| 亚洲国产精品自在自线观看| 亚洲中文字幕无码中文| 亚洲 日韩经典 中文字幕| 亚洲国产精品久久丫| 亚洲图片校园春色| 亚洲一级片在线播放| 亚洲冬月枫中文字幕在线看| 亚洲一级毛片免费观看| 国产99在线|亚洲| 亚洲熟女综合色一区二区三区| 亚洲看片无码在线视频| 久久久国产亚洲精品| 亚洲色无码国产精品网站可下载| 亚洲中文字幕久久无码| 亚洲第一成年网站视频| 国产精品亚洲色婷婷99久久精品| 爱情岛论坛亚洲品质自拍视频网站| 精品国产_亚洲人成在线| 亚洲 自拍 另类小说综合图区| mm1313亚洲国产精品美女| 亚洲欧洲一区二区三区| 最新精品亚洲成a人在线观看| 亚洲情综合五月天| 亚洲日本一区二区三区| 亚洲人成网站在线观看播放青青| 亚洲乱码一二三四区乱码| 亚洲精品又粗又大又爽A片| 国产成人亚洲精品电影| 亚洲综合色区在线观看| 亚洲AV无码久久精品成人 | 亚洲人成网站免费播放| 亚洲Aⅴ在线无码播放毛片一线天| heyzo亚洲精品日韩| 久久亚洲国产午夜精品理论片| 亚洲制服中文字幕第一区| 亚洲国产成人超福利久久精品| 亚洲色欲色欱wwW在线| 亚洲精品和日本精品|