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

Linux 多執行緒 HTTP 伺服器原始碼深度解析

     編輯:關於C語言

本文剖析一個基於 Linux POSIX 執行緒(pthread)的簡易 HTTP 伺服器原始碼,涵蓋 Socket 建立、多執行緒並發處理、HTTP 請求解析、靜態檔案服務與 MIME 類型對應等核心模組。

整體架構概覽

  • 主執行緒:負責 socket()、bind()、listen()、accept(),接受連線後建立 ARG 結構傳遞 connfd 與客戶端資訊,啟動工作執行緒。
  • 工作執行緒:執行 start_routing(),呼叫 process_cli() 處理完整 HTTP 請求/回應週期,結束時釋放資源並 pthread_exit()。

關鍵程式碼逐段解析

1. 主程式流程

int main() {
    int listenfd = socket(AF_INET, SOCK_STREAM, 0);
    int opt = SO_REUSEADDR;
    setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); // 避免 TIME_WAIT 阻礙重啟

    struct sockaddr_in server = {0};
    server.sin_family = AF_INET;
    server.sin_port = htons(8848);
    server.sin_addr.s_addr = htonl(INADDR_ANY);

    bind(listenfd, (struct sockaddr*)&server, sizeof(server));
    listen(listenfd, 5);

    while (1) {
        struct sockaddr_in client;
        socklen_t sin_size = sizeof(client);
        int connectfd = accept(listenfd, (struct sockaddr*)&client, &sin_size);

        ARG* arg = new ARG;
        arg->connfd = connectfd;
        memcpy(&arg->client, &client, sizeof(client));

        pthread_t thread;
        pthread_create(&thread, NULL, start_routing, (void*)arg);
        // 主執行緒不 join,工作執行緒自行 detach/exit
    }
}

2. HTTP 請求處理

void process_cli(int connectfd, sockaddr_in client) {
    FILE* fp = fdopen(connectfd, "r"); // 將 socket 包裝為 FILE* 方便用 fgets
    char requestline[1000];
    fgets(requestline, sizeof(requestline), fp); // 讀取請求行:GET /path HTTP/1.1

    char cmd[1000], filepath[1000];
    strcpy(filepath, "./");
    sscanf(requestline, "%s%s", cmd, filepath+2); // 解析方法與路徑

    if (strcmp(cmd, "GET") == 0) {
        char* ext = gettextname(filepath); // 取得副檔名
        if (fileordirExist(filepath)) {
            if (isDIR(filepath)) {
                strcat(filepath, "index.html"); // 目錄預設首頁
                if (!fileordirExist(filepath)) { msg404(connectfd); return; }
            }
            sendobj(connectfd, filepath); // 傳送檔案
        } else {
            msg404(connectfd);
        }
    }
    fclose(fp); // 關閉連線
}

3. 檔案傳送與 HTTP 標頭產生

int sendobj(int connectfd, char* serverfilepath) {
    FILE* sfp = fopen(serverfilepath, "r");
    FILE* cfp = fdopen(connectfd, "w");
    writehead(cfp, gettextname(serverfilepath)); // 寫入 HTTP 標頭

    int c;
    while ((c = getc(sfp)) != EOF) putc(c, cfp); // 逐字元傳送檔案內容
    fflush(cfp);
    fclose(sfp);
    return 0;
}

int writehead(FILE* cfp, char* extname) {
    char* content = "text/plain";
    if (strcmp(extname, "html")==0 || strcmp(extname, "htm")==0) content = "text/html";
    else if (strcmp(extname, "css")==0) content = "text/css";
    else if (strcmp(extname, "gif")==0) content = "image/gif";
    else if (strcmp(extname, "jpeg")==0 || strcmp(extname, "jpg")==0) content = "image/jpeg";
    else if (strcmp(extname, "png")==0) content = "image/png";

    fprintf(cfp, "HTTP/1.1 200 OK\r\n");
    fprintf(cfp, "Content-Type: %s\r\n\r\n", content); // 雙 CRLF 結束標頭
    return 0;
}

4. 工具函數

  • fileordirExist():stat() 判斷路徑是否存在。
  • isDIR():S_ISDIR(stat.st_mode) 判斷是否目錄。
  • gettextname():strrchr() 取得最後一個 . 後的副檔名。
  • msg404():傳送簡易 404 回應。

編譯與執行

g++ -g -o httpd server.cpp -lpthread
./httpd
# 瀏覽器訪問 https://127.0.0.1:8848
# 需在同目錄下放置 index.html

已知問題與改進方向

  1. 中文亂碼:標頭缺少 charset=utf-8,需在 Content-Type 加入編碼宣告。
  2. 標頭格式:HTTP 標頭需以 \r\n 結尾,原碼僅用 \n,嚴格來說不符 RFC。
  3. 大檔案效能:逐字元 getc/putc 效率極低,應改用 sendfile() 或緩衝區批次讀寫。
  4. 資源洩漏:執行緒未 pthread_detach() 或 join(),長期運行會洩漏執行緒資源。
  5. 安全性:路徑解析無防禦目錄遍歷(../),生產環境必須過濾。

此程式碼適合作為 Linux 網路程式設計與多執行緒入門教學範例,實務部署需大幅強化。

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