一、环境说明
- 系统: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() {
// 创建 socket
int 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 服务奠定基础。