在 Ubuntu 虚拟机中实现 HTML 表单与 C 语言 HTTP 服务器交互

一、环境说明

  • 系统:Ubuntu 虚拟机(已安装基本开发工具,如 GCC)
  • 目标:通过 C 语言服务器托管 HTML 表单页面,并实现数据提交交互

二、核心文件准备

1. 创建 HTML 表单页面(xunfei.html

cpp 复制代码
<!-- 保存路径:~/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

cpp 复制代码
// 保存路径:~/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 路径的表单提交,打印数据并返回成功提示

三、操作步骤(Ubuntu 虚拟机中执行)

1. 创建项目目录

cpp 复制代码
mkdir ~/Linux-HTTP && cd ~/Linux-HTTP  # 创建并进入项目目录

2. 编写并保存文件

  • 使用 nanovim 分别创建 xunfei.htmlserver.c,粘贴上述代码并保存。

3. 编译服务器

cpp 复制代码
gcc server.c -o server  # 生成可执行文件

可能问题 :若提示 gcc: command not found,需先安装 GCC:

cpp 复制代码
sudo apt update && sudo apt install gcc -y  # (首次编译需执行,后续可忽略)
  1. 运行服务器
cpp 复制代码
./server  # 启动服务器

输出提示

cpp 复制代码
服务器启动,监听端口 8080...

5. 访问测试(两种方式)

方式 1:虚拟机内直接访问

打开终端,使用 curl 测试:

cpp 复制代码
curl http://localhost:8080  # 查看 HTML 页面内容
方式 2:宿主机通过浏览器访问
  • 前提 :确保虚拟机网络设置为 桥接模式NAT 模式,并开放端口。
  • 在宿主机浏览器输入:
cpp 复制代码
http://虚拟机IP:8080  # 例如:http://192.168.1.100:8080

输入表单数据并点击 "提交",观察虚拟机终端输出:

cpp 复制代码
接收到表单数据:name=张三&age=20  # 示例输出

四、常见问题与解决

1. 服务器启动失败(端口被占用)

cpp 复制代码
lsof -i :8080  # 查看占用端口的进程
kill -9 <PID>   # 强制终止进程(PID 替换为实际进程号)

2. 无法访问页面(防火墙限制)

cpp 复制代码
sudo ufw allow 8080/tcp  # 开放 8080 端口(Ubuntu 防火墙默认关闭,若启用需执行)

3. 表单提交后数据乱码

  • 确保 HTML 头部包含 <meta charset="utf-8">
  • 服务器处理 POST 数据时,需根据编码格式解析(示例代码直接打印原始数据,如需处理可添加 URL 解码逻辑)

五、扩展方向

  1. 优化请求处理
    • 支持更多 HTTP 方法(如 PUT、DELETE)
    • 添加静态文件缓存机制
  2. 数据持久化
    • 将表单数据存入文件或数据库(如 SQLite)
  3. 并发优化
    • 使用线程池替代 epoll 单线程模型
    • 实现长连接(Connection: keep-alive)

通过这个实例,你可以深入理解 HTTP 协议的基本交互流程,并为后续开发更复杂的 Web 服务奠定基础。

相关推荐
\光辉岁月/40 分钟前
记录一次请求数据很慢的灾难
运维·服务器
jogpoxi41 分钟前
文件目录名称无效?数据恢复全流程与常见问题解析
服务器·网络·数据库
今天阳光明媚吗1 小时前
Ubuntu 上进行树莓派交叉编译
linux·ubuntu·树莓派·交叉编译
运维老曾1 小时前
在ubuntu 24安装 postgresql 17 (源码安装)
linux·ubuntu·postgresql
GoWjw1 小时前
虚拟机下ubuntu分区挂载实验
linux·运维·ubuntu
上海云盾第一敬业销售1 小时前
免费的DDOS防护对网站业务有用吗?
运维·服务器·网络
张小九992 小时前
服务器修改/home的挂载路径
linux·运维·服务器
华纳云IDC服务商2 小时前
如何提高服务器的QPS来应对618活动的并发流量
java·运维·服务器
爱跨境的笑笑5 小时前
IP大科普:住宅IP、机房IP、原生IP、双ISP
服务器·tcp/ip·接口隔离原则