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

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

基于epoll实现简单的web服务器

發布時間:2025/3/21 编程问答 25 豆豆
生活随笔 收集整理的這篇文章主要介紹了 基于epoll实现简单的web服务器 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

1. 簡介

epoll 是 Linux 平臺下特有的一種 I/O 復用模型實現,于 2002 年在 Linux kernel 2.5.44 中被引入。在 epoll 之前,Unix/Linux 平臺下的 I/O 復用模型包含 select 和 poll 兩個系統調用。隨著因特網的發展,因特網的用戶量越來越大,C10K 問題出現。基于 select 和 poll 編寫的網絡服務已經不能滿足不能滿足用戶的需求了,業界迫切希望更高效的系統調用出現。在此背景下,FreeBSD 的 kqueue 和 Linux 的 epoll 被研發了出來。kqueue 和 epoll 的出現,終結了 C10K 問題,C10K 問題就此作古。

因為 Linux 系統的廣泛應用,所以大家在說 I/O 復用時,更多的是想到了 epoll,而不是 kqueue,本文也不例外。本篇文章不會涉及 kqueue,大家有興趣可以自己看看。

?2. 基于 epoll 實現 web 服務器

在 Linux 中,epoll 并不是一個系統調用,而是 epoll_create、epoll_ctl 和 epoll_wait 三個系統調用的統稱。關于這三個系統調用的細節,這里就不說明了,大家可以自己去查 man-page。接下來,我們來直接看一個例子,這個例子基于 epoll 和?TinyHttpd?實現了一個 I/O 復用版的 HTTP Server。在上代碼前,我們先來演示這個玩具版 HTTP Server 的效果。

上面就是玩具版 HTTP Server 的運行效果了,看起來還行。在我第一次把它成功跑起來的時候,感覺很奇妙。好了,看完效果,接下來看代碼吧,如下:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <sys/sysinfo.h> #include <sys/epoll.h> #include <signal.h> #include <fcntl.h> #include <sys/wait.h> #include <sys/types.h> #include "httpd.h"#define DEFAULT_PORT 8080 #define MAX_EVENT_NUM 1024 #define INFTIM -1void process(int);void handle_subprocess_exit(int);int main(int argc, char *argv[]) {struct sockaddr_in server_addr;int listen_fd;int cpu_core_num;int on = 1;listen_fd = socket(AF_INET, SOCK_STREAM, 0);fcntl(listen_fd, F_SETFL, O_NONBLOCK); // 設置 listen_fd 為非阻塞setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));bzero(&server_addr, sizeof(server_addr));server_addr.sin_family = AF_INET;server_addr.sin_addr.s_addr = htonl(INADDR_ANY);server_addr.sin_port = htons(DEFAULT_PORT);if (bind(listen_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) {perror("bind error, message: ");exit(1);}if (listen(listen_fd, 5) == -1) {perror("listen error, message: ");exit(1);}printf("listening 8080\n");signal(SIGCHLD, handle_subprocess_exit);cpu_core_num = get_nprocs();printf("cpu core num: %d\n", cpu_core_num);// 根據 CPU 數量創建子進程,為了演示“驚群現象”,這里多創建一些子進程for (int i = 0; i < cpu_core_num * 2; i++) {pid_t pid = fork();if (pid == 0) { // 子進程執行此條件分支process(listen_fd);exit(0);}}while (1) {sleep(1);}return 0; }void process(int listen_fd) {int conn_fd;int ready_fd_num;struct sockaddr_in client_addr;int client_addr_size = sizeof(client_addr);char buf[128];struct epoll_event ev, events[MAX_EVENT_NUM];// 創建 epoll 實例,并返回 epoll 文件描述符int epoll_fd = epoll_create(MAX_EVENT_NUM);ev.data.fd = listen_fd;ev.events = EPOLLIN;// 將 listen_fd 注冊到剛剛創建的 epoll 中if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, listen_fd, &ev) == -1) {perror("epoll_ctl error, message: ");exit(1);}while(1) {// 等待事件發生ready_fd_num = epoll_wait(epoll_fd, events, MAX_EVENT_NUM, INFTIM);printf("[pid %d] ? 震驚!我又被喚醒了...\n", getpid());if (ready_fd_num == -1) {perror("epoll_wait error, message: ");continue;}for(int i = 0; i < ready_fd_num; i++) {if (events[i].data.fd == listen_fd) { // 有新的連接conn_fd = accept(listen_fd, (struct sockaddr *)&client_addr, &client_addr_size);if (conn_fd == -1) {sprintf(buf, "[pid %d] ? accept 出錯了: ", getpid());perror(buf);continue;}// 設置 conn_fd 為非阻塞if (fcntl(conn_fd, F_SETFL, fcntl(conn_fd, F_GETFD, 0) | O_NONBLOCK) == -1) {continue;}ev.data.fd = conn_fd;ev.events = EPOLLIN;if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, conn_fd, &ev) == -1) {perror("epoll_ctl error, message: ");close(conn_fd);}printf("[pid %d] ? 收到來自 %s:%d 的請求\n", getpid(), inet_ntoa(client_addr.sin_addr), client_addr.sin_port);} else if (events[i].events & EPOLLIN) { // 某個 socket 數據已準備好,可以讀取了printf("[pid %d] ? 處理來自 %s:%d 的請求\n", getpid(), inet_ntoa(client_addr.sin_addr), client_addr.sin_port);conn_fd = events[i].data.fd;// 調用 TinyHttpd 的 accept_request 函數處理請求accept_request(conn_fd, &client_addr);close(conn_fd);} else if (events[i].events & EPOLLERR) {fprintf(stderr, "epoll error\n");close(conn_fd);}}} }void handle_subprocess_exit(int signo) {printf("clean subprocess.\n");int status; while(waitpid(-1, &status, WNOHANG) > 0); }

上面的代碼有點長,不過還好,基本上都是模板代碼,沒什么特別復雜的邏輯。希望大家耐心看一下。

上面的代碼基于epoll + 多進程的方式實現,開始,主進程會通過系統調用獲取 CPU 核心數,然后根據核心數創建子進程。為了演示“驚群現象”,這里多創建了一倍的子進程。關于驚群現象,下一章會講到,大家先別急哈。創建好子進程后,主進程不需再做什么事了,核心邏輯都會在子線程中執行。首先,每個子進程都會調用 epoll_create 在內核創建 epoll 實例,然后再通過 epoll_ctl 將 listen_fd 注冊到 epoll 實例中,由內核進行監控。最后,再調用 epoll_wait 等待感興趣的事件發生。當 listen_fd 中有新的連接時,epoll_wait 會返回。此時子進程調用 accept 接受連接,并把客戶端 socket 注冊到 epoll 實例中,等待 EPOLLIN 事件發生。當該事件發生后,即可接受數據,并根據 HTTP 請求信息返回相應的頁面了。

這里說明一下,上面代碼中處理 HTTP 請求的邏輯是寫在?TinyHttpd?項目中的,TinyHttpd 是一個只有 500 行左右的超輕量型Http Server,很適合學習使用。為了適應需求,我對其源碼進行了一定的修改,并添加了一些注釋。本章的測試代碼已經放到了 github 上,需要的同學自取,傳送門 ->?epoll_multiprocess_server.c。

?3. 驚群及演示

“驚群現象”是指并發環境下,多線程或多進程等待同一個 socket 事件,當這個事件發生時,多線程/多進程被同時喚醒,這就是“驚群現象”。對應上面的代碼,多個子進程通過調用 epoll_wait 等待 listen_fd 上某個事件發生。當有新連接進來時,多個進程會被同時喚醒去處理這個事件。但最終只有一個進程可以去處理事件,其他進程重新進入等待狀態。使用上面的代碼可以演示驚群現象,如下:

從上圖可以看出,當 listen_fd 上有新連接事件發生時,進程19571和19573被喚醒。但最終進程19573成功處理了新連接事件,進程19571則失敗了。

驚群現象會影響服務器性能,因為多個進程被喚醒,但最終只有一個進程可以成功處理事件。而 CPU 需要為一個事件的發生調度數個進程,因此會浪費 CPU 資源。

對于驚群現象,處理的思路一般有兩種。一種是像 Lighttpd 那樣,無視驚群。另一種是像 Nginx 那樣,使用全局鎖避免驚群。簡單起見,本文測試代碼采用的是 Lighttpd 的處理方式,即無視驚群。對于這兩種思路的細節,由于本人未讀過兩個開源軟件的代碼,這里就不多說了。如果大家有興趣,可以參考網上的一些博文。

?4. 總結

epoll 是 I/O 復用模型重要的一個實現,性能優異,應用廣泛。像 Linux 平臺下的 JVM,NIO 部分就是基于 epoll 實現的。再如大名鼎鼎 Nginx 也是使用了 epoll。由此可以看出 epoll 的重要性,因此我們有很有必要去了解 epoll。本文通過一個測試程序簡單演示了一個基于 epoll 的 HTTP Server,總體上也達到了學習 epoll 的目的。大家如果有興趣,可以下載源碼看看。當然,紙上學來終覺淺,還是要自己動手寫才行。本文的測試代碼是本人現學現賣寫的,僅測試使用,寫的不好的地方望諒解。

好了,本文到此結束,謝謝閱讀!

?參考

  • 關于多進程epoll與“驚群”問題 - CSDN
  • “驚群”,看看nginx是怎么解決它的 - CSDN
  • 高性能網絡編程(二):上一個10年,著名的C10K并發連接問題
  • 本文鏈接:?https://www.tianxiaobo.com/2018/03/02/基于epoll實現簡單的web服務器/

from:?http://www.tianxiaobo.com/2018/03/02/%E5%9F%BA%E4%BA%8Eepoll%E5%AE%9E%E7%8E%B0%E7%AE%80%E5%8D%95%E7%9A%84web%E6%9C%8D%E5%8A%A1%E5%99%A8/?

總結

以上是生活随笔為你收集整理的基于epoll实现简单的web服务器的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。