本文剖析一個基於 Linux POSIX 執行緒(pthread)的簡易 HTTP 伺服器原始碼,涵蓋 Socket 建立、多執行緒並發處理、HTTP 請求解析、靜態檔案服務與 MIME 類型對應等核心模組。
socket()、bind()、listen()、accept(),接受連線後建立 ARG 結構傳遞 connfd 與客戶端資訊,啟動工作執行緒。start_routing(),呼叫 process_cli() 處理完整 HTTP 請求/回應週期,結束時釋放資源並 pthread_exit()。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
}
}
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); // 關閉連線
}
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;
}
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
charset=utf-8,需在 Content-Type 加入編碼宣告。\r\n 結尾,原碼僅用 \n,嚴格來說不符 RFC。getc/putc 效率極低,應改用 sendfile() 或緩衝區批次讀寫。pthread_detach() 或 join(),長期運行會洩漏執行緒資源。../),生產環境必須過濾。此程式碼適合作為 Linux 網路程式設計與多執行緒入門教學範例,實務部署需大幅強化。