一、環境說明
- 系統:Ubuntu 虛擬機(已安裝基本開發工具,如 GCC)
- 目標:通過 C 語言服務器托管 HTML 表單頁面,并實現數據提交交互
二、核心文件準備
1. 創建 HTML 表單頁面(xunfei.html
)
<!-- 保存路徑:~/Linux-HTTP/xunfei.html -->
<html lang="zh-CN">
<head><meta charset="utf-8"><title>訊飛課堂表單</title>
</head>
<body><div style="text-align:center; height:500px"><h2>歡迎來到訊飛課堂!</h2><form action="/commit" method="post">姓名:<input type="text" name="name" required><br><br>年齡:<input type="text" name="age" required><br><br><button type="submit">提交</button></form></div>
</body>
</html>
- 關鍵點:
action="/commit"
:表單數據通過 POST 請求發送到服務器?/commit
?路徑method="post"
:使用 POST 方法提交數據(適合傳輸敏感或大量數據)
2. 編寫 C 語言 HTTP 服務器代碼(server.c
)
// 保存路徑:~/Linux-HTTP/server.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/epoll.h>
#include <fcntl.h>#define PORT 8080 // 服務器端口
#define BUFFER_SIZE 4096 // 緩沖區大小
#define MAX_EVENTS 1000 // 最大事件數// 設置套接字為非阻塞模式
void set_nonblocking(int fd) {int flags = fcntl(fd, F_GETFL);fcntl(fd, F_SETFL, flags | O_NONBLOCK);
}// 發送 HTTP 響應
void send_response(int client_fd, const char *status, const char *content_type, const char *content, size_t len) {char header[BUFFER_SIZE];snprintf(header, BUFFER_SIZE,"HTTP/1.1 %s\r\n""Content-Type: %s\r\n""Content-Length: %zu\r\n""Connection: close\r\n\r\n",status, content_type, len);write(client_fd, header, strlen(header));write(client_fd, content, len);
}// 處理請求
void handle_request(int client_fd) {char buffer[BUFFER_SIZE] = {0};ssize_t bytes_read = read(client_fd, buffer, BUFFER_SIZE - 1);if (bytes_read <= 0) return;// 解析請求行(Method、Path、Protocol)char *method = strtok(buffer, " ");char *path = strtok(NULL, " ");char *protocol = strtok(NULL, "\r\n");// 處理 GET 請求(返回 HTML 頁面)if (strcmp(method, "GET") == 0) {char file_path[256] = "./xunfei.html";if (strcmp(path, "/") == 0) { // 根路徑映射到表單頁面FILE *file = fopen(file_path, "r");if (!file) {send_response(client_fd, "404 Not Found", "text/plain", "File Not Found", 14);return;}fseek(file, 0, SEEK_END);long file_size = ftell(file);fseek(file, 0, SEEK_SET);char *content = malloc(file_size);fread(content, 1, file_size, file);fclose(file);send_response(client_fd, "200 OK", "text/html", content, file_size);free(content);}}// 處理 POST 請求(接收表單數據)else if (strcmp(method, "POST") == 0 && strstr(path, "/commit")) {char *body = strstr(buffer, "\r\n\r\n") + 4; // 提取請求體printf("接收到表單數據:%s\n", body);send_response(client_fd, "200 OK", "text/plain", "提交成功", 9);}else {send_response(client_fd, "501 Not Implemented", "text/plain", "不支持的方法", 12);}close(client_fd);
}int main() {// 創建 socketint server_fd = socket(AF_INET, SOCK_STREAM, 0);if (server_fd < 0) {perror("socket創建失敗");exit(1);}// 綁定端口struct sockaddr_in addr = {.sin_family = AF_INET,.sin_port = htons(PORT),.sin_addr.s_addr = INADDR_ANY};if (bind(server_fd, (struct sockaddr*)&addr, sizeof(addr)) < 0) {perror("bind失敗");close(server_fd);exit(1);}// 監聽連接if (listen(server_fd, SOMAXCONN) < 0) {perror("listen失敗");close(server_fd);exit(1);}printf("服務器啟動,監聽端口 %d...\n", PORT);// 使用 epoll 處理并發連接int epoll_fd = epoll_create1(0);struct epoll_event event = {.events = EPOLLIN | EPOLLET, .data.fd = server_fd};epoll_ctl(epoll_fd, EPOLL_CTL_ADD, server_fd, &event);set_nonblocking(server_fd);struct epoll_event events[MAX_EVENTS];while (1) {int n = epoll_wait(epoll_fd, events, MAX_EVENTS, -1);for (int i = 0; i < n; i++) {if (events[i].data.fd == server_fd) { // 新連接struct sockaddr_in client_addr;socklen_t addr_len = sizeof(client_addr);int client_fd = accept(server_fd, (struct sockaddr*)&client_addr, &addr_len);if (client_fd < 0) continue;set_nonblocking(client_fd);epoll_ctl(epoll_fd, EPOLL_CTL_ADD, client_fd, &event);} else { // 處理請求handle_request(events[i].data.fd);epoll_ctl(epoll_fd, EPOLL_CTL_DEL, events[i].data.fd, NULL);}}}close(server_fd);return 0;
}
- 核心邏輯:
- GET 請求:訪問根路徑?
/
?時返回?xunfei.html
?頁面 - POST 請求:處理?
/commit
?路徑的表單提交,打印數據并返回成功提示
- GET 請求:訪問根路徑?
三、操作步驟(Ubuntu 虛擬機中執行)
1. 創建項目目錄
mkdir ~/Linux-HTTP && cd ~/Linux-HTTP # 創建并進入項目目錄
?2. 編寫并保存文件
- 使用?
nano
?或?vim
?分別創建?xunfei.html
?和?server.c
,粘貼上述代碼并保存。
3. 編譯服務器
gcc server.c -o server # 生成可執行文件
可能問題:若提示?gcc: command not found
,需先安裝 GCC:
sudo apt update && sudo apt install gcc -y # (首次編譯需執行,后續可忽略)
4. 運行服務器
./server # 啟動服務器
輸出提示:
服務器啟動,監聽端口 8080...
5. 訪問測試(兩種方式)
方式 1:虛擬機內直接訪問
打開終端,使用?curl
?測試:
curl http://localhost:8080 # 查看 HTML 頁面內容
方式 2:宿主機通過瀏覽器訪問
- 前提:確保虛擬機網絡設置為?橋接模式?或?NAT 模式,并開放端口。
- 在宿主機瀏覽器輸入:
http://虛擬機IP:8080 # 例如:http://192.168.1.100:8080
?輸入表單數據并點擊 “提交”,觀察虛擬機終端輸出:
接收到表單數據:name=張三&age=20 # 示例輸出
?
四、常見問題與解決
1. 服務器啟動失敗(端口被占用)
lsof -i :8080 # 查看占用端口的進程
kill -9 <PID> # 強制終止進程(PID 替換為實際進程號)
2. 無法訪問頁面(防火墻限制)
sudo ufw allow 8080/tcp # 開放 8080 端口(Ubuntu 防火墻默認關閉,若啟用需執行)
?
3. 表單提交后數據亂碼
- 確保 HTML 頭部包含?
<meta charset="utf-8">
- 服務器處理 POST 數據時,需根據編碼格式解析(示例代碼直接打印原始數據,如需處理可添加 URL 解碼邏輯)
五、擴展方向
- 優化請求處理:
- 支持更多 HTTP 方法(如 PUT、DELETE)
- 添加靜態文件緩存機制
- 數據持久化:
- 將表單數據存入文件或數據庫(如 SQLite)
- 并發優化:
- 使用線程池替代 epoll 單線程模型
- 實現長連接(Connection: keep-alive)
通過這個實例,你可以深入理解 HTTP 協議的基本交互流程,并為后續開發更復雜的 Web 服務奠定基礎。