首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> C++ >> 關於C++

C/C++ 學習筆記:xinetd 服務部署與簡易 HTTP 伺服器原始碼解析

     編輯:關於C++

本筆記記錄 xinetd (Extended Internet Daemon) 超級伺服器的安裝配置流程,並深度解析一個基於 xinetd 啟動的簡易 HTTP 伺服器原始碼 (myhttpd.c),涵蓋請求解析、靜態檔案服務、目錄列表、MIME 類型判斷、URL 編解碼與 HTTP 協定頭構建等核心網路程式設計技術。

一、xinetd 核心概念與優勢

  • 問題:傳統獨立守護進程各佔一埠長駐記憶體,資源浪費。
  • 方案:xinetd 單一進程 select/poll/epoll 同時監聽多埠,收到請求依 /etc/xinetd.d/ 設定 fork() + exec() 啟動對應服務程式。
  • 優勢:節省記憶體、統一存取控制 (TCP Wrappers)、日誌集中、支援 IPv4/IPv6、連線數限制、重導向等進階功能。

二、xinetd 安裝與配置實戰

1. 安裝

sudo apt-get update && sudo apt-get install xinetd

2. 服務定義檔 /etc/xinetd.d/myhttpd

service myhttpd
{
    socket_type     = stream      # TCP 串流
    protocol        = tcp
    wait            = no          # 多執行緒/多進程並發 (每請求 fork)
    user            = nobody      # 權限降級
    server          = /home/user/HttpTest/myhttpd  # 實際服務程式完整路徑
    server_args     = /home/user/HttpTest/dir      # 傳給 server argv[1] (文檔根目錄)
    disable         = no          # 啟用
    flags           = IPv4
}

關鍵參數:wait=no 表示 xinetd 接受連線後立即啟動服務處理,適用高併發;server_args 會作為 argv[1] 傳遞給 main(int argc, char** argv)。

3. 註冊埠號 /etc/services

myhttpd    2222/tcp   # 自訂服務名對應埠號

4. 重啟生效

sudo service xinetd restart

三、簡易 HTTP 伺服器 myhttpd.c 核心邏輯解析

程式以 stdin/stdout 與 xinetd 通訊 (xinetd 將 socket fd dup2 至 0/1),單進程處理單一 HTTP 請求,處理完即退出。

1. 啟動與環境初始化

if (argc != 2) send_error(500, "Internal Error", NULL, "Config error - no dir specified.");
if (chdir(argv[1]) < 0) send_error(500, "Internal Error", NULL, "Config error - couldn't chdir.");
  • 檢查 server_args 傳入的文檔根目錄並 chdir,後續路徑皆為相對路徑,簡化邏輯。

2. HTTP 請求行解析

fgets(line, sizeof(line), stdin); // 讀請求行
sscanf(line, "%[^ ] %[^ ] %[^ ]", method, path, protocol); // 切割 METHOD PATH VERSION
while (fgets(line, sizeof(line), stdin) != NULL) { // 讀並丟棄請求頭
    if (strcmp(line, "\n") == 0 || strcmp(line, "\r\n") == 0) break;
}
  • 僅支援 GET,其他回 501 Not Implemented。
  • 路徑必須以 / 開頭,否則 400 Bad Request。

3. 路徑清理與安全檢查

file = &path[1]; // 去掉開頭 '/'
strdecode(file, file); // URL 解碼 (%20 → space 等)
if (file[0] == '\0') file = "./";
// 防止目錄穿越攻擊
if (file[0]=='/' || strcmp(file,"..")==0 || strncmp(file,"../",3)==0 ||
    strstr(file,"/../") != NULL || strcmp(&file[len-3],"/..")==0)
    send_error(400, "Bad Request", NULL, "Illegal filename.");

4. 檔案/目錄判斷與回應

struct stat sb;
if (stat(file, &sb) < 0) send_error(404, "Not Found", NULL, "File not found.");

if (S_ISDIR(sb.st_mode)) { // 目錄
    if (file[len-1] != '/') { // 強制尾部加 '/'
        snprintf(location, sizeof(location), "Location: %s/", path);
        send_error(302, "Found", location, "Directories must end with a slash.");
    }
    // 優先尋找 index.html
    snprintf(idx, sizeof(idx), "%sindex.html", file);
    if (stat(idx, &sb) >= 0) { file = idx; goto do_file; }
    // 產生目錄列表
    send_headers(200, "Ok", NULL, "text/html", -1, sb.st_mtime);
    printf("...

Index of %s

\n", file);
    n = scandir(file, &dl, NULL, alphasort); // 掃描目錄
    for (i=0; id_name);
    printf("

...
\n"); } else { // 一般檔案 do_file: fp = fopen(file, "r"); if (!fp) send_error(403, "Forbidden", NULL, "File is protected."); send_headers(200, "Ok", NULL, get_mime_type(file), sb.st_size, sb.st_mtime); while ((ich = getc(fp)) != EOF) putchar(ich); }

5. HTTP 回應頭構建 send_headers()

void send_headers(int status, char* title, char* extra_header,
                  char* mime_type, off_t length, time_t mod) {
    printf("%s %d %s\r\n", PROTOCOL, status, title); // HTTP/1.1 200 OK
    printf("Server: %s\r\n", SERVER_NAME);
    printf("Date: %s\r\n", gmt_time_str);
    if (extra_header) printf("%s\r\n", extra_header); // 如 Location
    if (mime_type) printf("Content-Type: %s\r\n", mime_type);
    if (length >= 0) printf("Content-Length: %lld\r\n", length);
    if (mod != -1) printf("Last-Modified: %s\r\n", gmt_mod_str);
    printf("Connection: close\r\n\r\n"); // 結束頭部
}

6. MIME 類型判斷 get_mime_type()

依副檔名映射:.html/.htm → text/html, .jpg/.jpeg → image/jpeg, .png → image/png, .css → text/css, .js → application/javascript 等,預設 text/plain。

7. URL 編解碼核心

// 解碼: %XX → 字元
void strdecode(char* to, char* from) {
    for (; *from; ++to, ++from) {
        if (from[0]=='%' && isxdigit(from[1]) && isxdigit(from[2])) {
            *to = hexit(from[1])*16 + hexit(from[2]);
            from += 2;
        } else *to = *from;
    }
    *to = '\0';
}
// 編碼: 非字母數字/_.-~ → %XX
void strencode(char* to, size_t tosize, const char* from) {
    for (int tolen=0; *from && tolen+4 < tosize; ++from) {
        if (isalnum(*from) || strchr("/_.-~", *from)) { *to++ = *from; ++tolen; }
        else { sprintf(to, "%%%02x", (int)*from & 0xff); to+=3; tolen+=3; }
    }
    *to = '\0';
}

四、關鍵技術點總結

技術點實作細節
I/O 模型xinetd + fork/exec,單進程處理單請求,stdin/stdout 直連 socket
安全chroot 風格 chdir + 路徑穿越檢查 (.., /../ 等)
目錄索引scandir + alphasort 自動排序,動態產生 HTML
MIME副檔名映射表,支援常見靜態資源類型
URL 編碼RFC 3986 子集,解碼用於路徑,編碼用於目錄列表連結生成
錯誤處理統一 send_error → send_headers + HTML 錯誤頁 + exit(1)
時間格式strftime + gmtime 產生 RFC 1123 格式 (GMT)

五、擴充方向

  1. 支援 HEAD/POST 方法與 CGI 執行。
  2. 加入 Range 請求支援斷點續傳/視頻拖曳。
  3. 引入 epoll + 非阻塞 I/O 改為高併發單進程架構 (移除 xinetd 依賴)。
  4. HTTPS 支援 (OpenSSL/mbedTLS)。
  5. 配置檔解析 (埠、根目錄、預設首頁、MIME 對照表外部化)。

這個微型伺服器雖簡單,卻完整展示了 HTTP/1.1 靜態資源服務的核心流程,是理解 Web 伺服器內部運作、網路程式設計實戰與 xinetd 部署模式的優秀教學案例。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved