在 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 服务奠定基础。

相关推荐
ulias2123 小时前
Linux系统中的权限问题
linux·运维·服务器
青花瓷4 小时前
Ubuntu下OpenClaw的安装(豆包火山API版)
运维·服务器·ubuntu
Dream of maid5 小时前
Linux(下)
linux·运维·服务器
齐鲁大虾5 小时前
统信系统UOS常用命令集
linux·运维·服务器
一叶知秋yyds7 小时前
Ubuntu 虚拟机安装 OpenClaw 完整流程
linux·运维·ubuntu·openclaw
专吃海绵宝宝菠萝屋的派大星7 小时前
使用Dify对接自己开发的mcp
java·服务器·前端
大数据新鸟8 小时前
操作系统之虚拟内存
java·服务器·网络
楠奕9 小时前
CentOS7安装GoldenDB单机搭建及常见报错解决方案
linux·运维·服务器
GCTTTTTT10 小时前
远程服务器走本地代理
运维·服务器
剑锋所指,所向披靡!10 小时前
Linux常用指令(2)
linux·运维·服务器