从零手写一个 C 语言 HTTP 商品查询服务器:全链路技术实践

本文章综合使用

1.基于 BSD Socket 原生实现 TCP 服务端监听与连接处理,采用fork()多进程并发模型响应多用户请求,配合SIGCHLD信号机制自动回收子进程、避免僵尸进程;实现端口复用、异常容错与资源自动释放【tcp+多进程】

2.手动解析 HTTP 请求行、请求方法(GET/POST)、URL 路径与查询参数;实现 POST 表单数据提取、URL 解码(兼容中文与特殊字符);自主构造标准 HTTP 响应头,【http】

3.基于 SQLite 嵌入式数据库,通过原生 C 接口封装独立的数据访问层;实现用户表自动初始化、账号注册 / 登录校验;【sqlite3】

4.手写 HTML+CSS 实现登录、注册、商品列表、商品详情全套前端页面,采用卡片式布局与基础交互优化;

覆盖了系统编程、网络编程、数据库开发、前端页面、工程构建全链路技术栈,没有依赖任何重型 Web 框架

1.服务器的初始化

cs 复制代码
#include "shop.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <signal.h>
#include <sys/wait.h>

// 子进程回收,避免僵尸进程
void sig_child(int signo) {
    while (waitpid(-1, NULL, WNOHANG) > 0);
}

int main() {
    // 1. 初始化数据库
    if (db_init(DB_PATH) != 0) {
        printf("数据库打开失败,请检查 shop.db 文件路径\n");
        return 1;
    }

    // 2. 创建Socket
    int server_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (server_fd < 0) {
        perror("socket 创建失败");
        return 1;
    }

    // 端口复用
    int opt = 1;
    setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));

    // 绑定地址端口
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(PORT);
    addr.sin_addr.s_addr = INADDR_ANY;

    if (bind(server_fd, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
        perror("端口绑定失败");
        return 1;
    }

    // 开始监听
    listen(server_fd, 128);
    printf("===== 商品查询服务启动 =====\n");
    printf("端口号: %d\n", PORT);
    printf("浏览器访问: http://localhost:%d\n", PORT);
    printf("============================\n");

    // 注册信号处理
    signal(SIGCHLD, sig_child);

    // 3. 循环接收客户端请求
    while (1) {
        int client_fd = accept(server_fd, NULL, NULL);
        if (client_fd < 0) {
            perror("accept 出错");
            continue;
        }

        // 多进程并发:每个请求fork一个子进程处理
        pid_t pid = fork();
        if (pid == 0) {
            close(server_fd);       // 子进程关闭监听套接字
            handle_request(client_fd); // 处理请求
            exit(0);
        } else if (pid > 0) {
            close(client_fd);       // 父进程关闭客户端套接字
        }
    }

    db_close();
    close(server_fd);
    return 0;
}

主要main函数的调

cs 复制代码
#include "shop.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sqlite3.h>

static sqlite3 *db = NULL;

/************************ 工具函数 ************************/
static void send_header(int fd, int code, const char *type, int len) {
    char buf[512];
    sprintf(buf, "HTTP/1.1 %d OK\r\nContent-Type: %s\r\nContent-Length: %d\r\n\r\n",
            code, type, len);
    send(fd, buf, strlen(buf), 0);
}

static void send_file(int fd, const char *path, const char *type) {
    int file_fd = open(path, O_RDONLY);
    if (file_fd < 0) {
        const char *err = "404 Not Found";
        send_header(fd, 404, "text/plain", strlen(err));
        send(fd, err, strlen(err), 0);
        return;
    }

    struct stat st;
    fstat(file_fd, &st);
    send_header(fd, 200, type, st.st_size);

    char buf[BUF_SIZE];
    int n;
    while ((n = read(file_fd, buf, BUF_SIZE)) > 0) {
        send(fd, buf, n, 0);
    }
    close(file_fd);
}

static void url_decode(char *dst, const char *src) {
    char *p = dst;
    while (*src) {
        if (*src == '%' && src[1] && src[2]) {
            char hex[3] = {src[1], src[2], 0};
            *p++ = strtol(hex, NULL, 16);
            src += 3;
        } else if (*src == '+') {
            *p++ = ' ';
            src++;
        } else {
            *p++ = *src++;
        }
    }
    *p = 0;
}

static void parse_form(char *body, char *user, char *pass) {
    char *p = strstr(body, "user=");
    if (p) {
        p += 5;
        char *end = strchr(p, '&');
        if (!end) end = p + strlen(p);
        strncpy(user, p, end - p);
        user[end - p] = 0;
        url_decode(user, user);
    }

    p = strstr(body, "passwd=");
    if (p) {
        p += 7;
        char *end = strchr(p, '&');
        if (!end) end = p + strlen(p);
        strncpy(pass, p, end - p);
        pass[end - p] = 0;
        url_decode(pass, pass);
    }
}

/************************ 数据库操作 ************************/
int db_init(const char *db_path) {
    if (sqlite3_open(db_path, &db) != SQLITE_OK) {
        return -1;
    }
    // 程序启动自动创建用户表,无需手动建
    const char *create_sql = 
        "CREATE TABLE IF NOT EXISTS user ("
        "id INTEGER PRIMARY KEY AUTOINCREMENT,"
        "username TEXT UNIQUE NOT NULL,"
        "password TEXT NOT NULL);";
    char *err = NULL;
    sqlite3_exec(db, create_sql, NULL, NULL, &err);
    if (err) sqlite3_free(err);
    return 0;
}

void db_close() {
    if (db) sqlite3_close(db);
}

int db_register(const char *username, const char *password) {
    char sql[256];
    sprintf(sql, "INSERT INTO user(username,password) VALUES('%s','%s')",
            username, password);
    char *err = NULL;
    int rc = sqlite3_exec(db, sql, NULL, NULL, &err);
    if (rc != SQLITE_OK) {
        sqlite3_free(err);
        return -1;
    }
    return 0;
}

static int login_callback(void *arg, int col, char **val, char **name) {
    int *flag = (int*)arg;
    *flag = 1;
    return 0;
}

int db_login(const char *username, const char *password) {
    char sql[256];
    int found = 0;
    sprintf(sql, "SELECT id FROM user WHERE username='%s' AND password='%s'",
            username, password);
    sqlite3_exec(db, sql, login_callback, &found, NULL);
    return found ? 0 : -1;
}

typedef struct {
    Goods *list;
    int idx;
} GoodsListArg;

static int goods_list_callback(void *arg, int col, char **val, char **name) {
    GoodsListArg *argp = (GoodsListArg*)arg;
    int i = argp->idx;

    argp->list[i].goods_id = atoi(val[0]);
    argp->list[i].cat_id = atoi(val[1]);
    strncpy(argp->list[i].goods_name, val[2] ? val[2] : "", 127);
    strncpy(argp->list[i].good_spec, val[3] ? val[3] : "", 127);
    argp->list[i].shop_price = atof(val[4]);
    strncpy(argp->list[i].goods_img, val[5] ? val[5] : "", 127);
    argp->idx++;
    return 0;
}

Goods* db_search_goods(const char *keyword, int *count) {
    char sql[512];
    sqlite3_stmt *stmt;
    *count = 0;

    sprintf(sql, "SELECT COUNT(*) FROM goods WHERE goods_name LIKE '%%%s%%' OR keywords LIKE '%%%s%%'",
            keyword, keyword);
    sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
    if (sqlite3_step(stmt) == SQLITE_ROW) {
        *count = sqlite3_column_int(stmt, 0);
    }
    sqlite3_finalize(stmt);

    if (*count == 0) return NULL;

    Goods *list = malloc(sizeof(Goods) * (*count));
    GoodsListArg arg = {list, 0};

    sprintf(sql, "SELECT goods_id,cat_id,goods_name,good_spec,shop_price,goods_img "
                 "FROM goods WHERE goods_name LIKE '%%%s%%' OR keywords LIKE '%%%s%%'",
            keyword, keyword);
    sqlite3_exec(db, sql, goods_list_callback, &arg, NULL);
    return list;
}

Goods* db_get_goods_by_id(int goods_id) {
    char sql[256];
    sprintf(sql, "SELECT goods_id,cat_id,goods_name,good_spec,shop_price,goods_img,"
                 "goods_desc,goods_number,keywords FROM goods WHERE goods_id=%d", goods_id);

    sqlite3_stmt *stmt;
    Goods *g = malloc(sizeof(Goods));
    memset(g, 0, sizeof(Goods));

    if (sqlite3_prepare_v2(db, sql, -1, &stmt, NULL) != SQLITE_OK) {
        free(g);
        return NULL;
    }

    if (sqlite3_step(stmt) == SQLITE_ROW) {
        g->goods_id = sqlite3_column_int(stmt, 0);
        g->cat_id = sqlite3_column_int(stmt, 1);
        strncpy(g->goods_name, (char*)sqlite3_column_text(stmt, 2), 127);
        strncpy(g->good_spec, (char*)sqlite3_column_text(stmt, 3), 127);
        g->shop_price = sqlite3_column_double(stmt, 4);
        strncpy(g->goods_img, (char*)sqlite3_column_text(stmt, 5), 127);
        strncpy(g->goods_desc, (char*)sqlite3_column_text(stmt, 6), 255);
        g->goods_number = sqlite3_column_int(stmt, 7);
        strncpy(g->keywords, (char*)sqlite3_column_text(stmt, 8), 127);
        sqlite3_finalize(stmt);
        return g;
    }

    sqlite3_finalize(stmt);
    free(g);
    return NULL;
}

/************************ 页面渲染 ************************/
static void render_goods_list(int fd, Goods *list, int count) {
    char html[8192];
    int offset = 0;

    offset += sprintf(html + offset,
        "<!DOCTYPE html><html><head><meta charset='utf-8'>"
        "<title>商品列表</title><style>"
        "*{margin:0;padding:0;box-sizing:border-box;}"
        ".header{padding:20px;background:#f5f5f5;border-bottom:1px solid #eee;}"
        ".search-form{width:600px;margin:0 auto;}"
        ".search-form input[type=text]{width:400px;padding:8px 12px;border:1px solid #ddd;border-radius:4px;}"
        ".search-form input[type=submit]{padding:8px 20px;background:#007bff;color:white;border:none;border-radius:4px;cursor:pointer;}"
        ".goods-list{width:1100px;margin:30px auto;overflow:hidden;}"
        ".goods-item{float:left;width:200px;margin:10px;border:1px solid #eee;border-radius:6px;padding:10px;text-align:center;transition:0.3s;}"
        ".goods-item:hover{box-shadow:0 2px 10px rgba(0,0,0,0.1);}"
        ".goods-item img{width:150px;height:150px;object-fit:contain;margin-bottom:8px;}"
        ".goods-name{font-size:14px;color:#333;height:40px;overflow:hidden;}"
        ".goods-price{color:#e4393c;font-size:16px;font-weight:bold;margin-top:8px;}"
        "a{text-decoration:none;color:inherit;}"
        "</style></head><body>");

    offset += sprintf(html + offset,
        "<div class='header'>"
        "<form class='search-form' method='get' action='/search'>"
        "<input type='text' name='keyword' placeholder='输入商品名称或关键词搜索...'>"
        "<input type='submit' value='搜索'>"
        "</form></div><div class='goods-list'>");

    if (count == 0 || !list) {
        offset += sprintf(html + offset, "<p style='text-align:center;width:100%%;padding:50px;color:#999;'>未找到相关商品</p>");
    } else {
        for (int i = 0; i < count; i++) {
            offset += sprintf(html + offset,
                "<div class='goods-item'>"
                "<a href='/detail?goods_id=%d'>"
                "<img src='/jpg/%s' alt='%s'>"
                "<div class='goods-name'>%s</div>"
                "<div class='goods-price'>¥%.2f</div>"
                "</a></div>",
                list[i].goods_id, list[i].goods_img, list[i].goods_name,
                list[i].goods_name, list[i].shop_price);
        }
    }

    offset += sprintf(html + offset, "</div></body></html>");

    send_header(fd, 200, "text/html; charset=utf-8", offset);
    send(fd, html, offset, 0);
}

static void render_detail(int fd, int goods_id) {
    Goods *g = db_get_goods_by_id(goods_id);
    if (!g) {
        const char *err = "商品不存在";
        send_header(fd, 404, "text/plain", strlen(err));
        send(fd, err, strlen(err), 0);
        return;
    }

    char html[4096];
    int len = sprintf(html,
        "<!DOCTYPE html><html><head><meta charset='utf-8'>"
        "<title>%s - 商品详情</title><style>"
        "*{margin:0;padding:0;box-sizing:border-box;}"
        ".container{width:900px;margin:50px auto;overflow:hidden;}"
        ".img-box{float:left;width:350px;padding:20px;border:1px solid #eee;border-radius:6px;}"
        ".img-box img{width:100%%;display:block;}"
        ".info-box{float:right;width:500px;padding:20px;}"
        ".info-box h2{font-size:22px;margin-bottom:20px;color:#333;}"
        ".info-item{margin:15px 0;font-size:15px;color:#666;}"
        ".price{color:#e4393c;font-size:28px;font-weight:bold;}"
        ".back-btn{display:inline-block;margin-top:30px;padding:10px 25px;background:#007bff;color:white;border-radius:4px;text-decoration:none;}"
        "</style></head><body>"
        "<div class='container'>"
        "<div class='img-box'><img src='/jpg/%s'></div>"
        "<div class='info-box'>"
        "<h2>%s</h2>"
        "<div class='info-item price'>¥%.2f</div>"
        "<div class='info-item'>规格:%s</div>"
        "<div class='info-item'>库存:%d 件</div>"
        "<div class='info-item'>关键词:%s</div>"
        "<div class='info-item'>商品描述:%s</div>"
        "<a href='/index.html' class='back-btn'>返回商品列表</a>"
        "</div></div></body></html>",
        g->goods_name, g->goods_img, g->goods_name,
        g->shop_price, g->good_spec, g->goods_number,
        g->keywords, g->goods_desc);

    send_header(fd, 200, "text/html; charset=utf-8", len);
    send(fd, html, len, 0);
    free(g);
}

/************************ 请求路由处理 ************************/
void handle_request(int client_fd) {
    char buf[BUF_SIZE];
    int n = recv(client_fd, buf, BUF_SIZE - 1, 0);
    if (n <= 0) {
        close(client_fd);
        return;
    }
    buf[n] = 0;

    char method[16], path[256];
    sscanf(buf, "%s %s", method, path);

    // 1. 静态页面
    if (strcmp(path, "/") == 0 || strcmp(path, "/login.html") == 0) {
        send_file(client_fd, STATIC_PATH"/login.html", "text/html; charset=utf-8");
    }
    else if (strcmp(path, "/register.html") == 0) {
        send_file(client_fd, STATIC_PATH"/register.html", "text/html; charset=utf-8");
    }

    // 2. 商品图片
    else if (strncmp(path, "/jpg/", 5) == 0) {
        char file_path[256];
        sprintf(file_path, "%s/%s", IMG_PATH, path + 5);
        send_file(client_fd, file_path, "image/jpeg");
    }

    // 3. 首页商品列表
    else if (strcmp(path, "/index.html") == 0) {
        int count = 0;
        Goods *list = db_search_goods("", &count);
        if (count > 20) count = 20;
        render_goods_list(client_fd, list, count);
        free(list);
    }

    // 4. 登录处理
    else if (strcmp(path, "/login") == 0) {
        char user[64] = {0}, pass[64] = {0};
        char *body = strstr(buf, "\r\n\r\n");
        if (body) parse_form(body + 4, user, pass);

        if (db_login(user, pass) == 0) {
            const char *redirect = "HTTP/1.1 302 Found\r\nLocation: /index.html\r\n\r\n";
            send(client_fd, redirect, strlen(redirect), 0);
        } else {
            const char *err = 
                "<!DOCTYPE html><html><head><meta charset='utf-8'></head><body>"
                "<p style='text-align:center;margin-top:100px;'>用户名或密码错误</p>"
                "<p style='text-align:center;'><a href='/login.html'>返回登录</a></p>"
                "</body></html>";
            send_header(client_fd, 200, "text/html; charset=utf-8", strlen(err));
            send(client_fd, err, strlen(err), 0);
        }
    }

    // 5. 注册处理
    else if (strcmp(path, "/register") == 0) {
        char user[64] = {0}, pass[64] = {0};
        char *body = strstr(buf, "\r\n\r\n");
        if (body) parse_form(body + 4, user, pass);

        if (db_register(user, pass) == 0) {
            const char *ok = 
                "<!DOCTYPE html><html><head><meta charset='utf-8'></head><body>"
                "<p style='text-align:center;margin-top:100px;'>注册成功!</p>"
                "<p style='text-align:center;'><a href='/login.html'>去登录</a></p>"
                "</body></html>";
            send_header(client_fd, 200, "text/html; charset=utf-8", strlen(ok));
            send(client_fd, ok, strlen(ok), 0);
        } else {
            const char *err = 
                "<!DOCTYPE html><html><head><meta charset='utf-8'></head><body>"
                "<p style='text-align:center;margin-top:100px;'>注册失败(用户名可能已存在)</p>"
                "<p style='text-align:center;'><a href='/register.html'>返回注册</a></p>"
                "</body></html>";
            send_header(client_fd, 200, "text/html; charset=utf-8", strlen(err));
            send(client_fd, err, strlen(err), 0);
        }
    }

    // 6. 搜索功能
    else if (strncmp(path, "/search?", 8) == 0) {
        char keyword[128] = {0};
        char *p = strstr(path, "keyword=");
        if (p) {
            p += 8;
            char *end = strchr(p, '&');
            if (!end) end = p + strlen(p);
            strncpy(keyword, p, end - p);
            keyword[end - p] = 0;
            url_decode(keyword, keyword);
        }

        int count = 0;
        Goods *list = db_search_goods(keyword, &count);
        render_goods_list(client_fd, list, count);
        free(list);
    }

    // 7. 商品详情
    else if (strncmp(path, "/detail?", 8) == 0) {
        int goods_id = 0;
        char *p = strstr(path, "goods_id=");
        if (p) goods_id = atoi(p + 9);
        render_detail(client_fd, goods_id);
    }

    // 8. 404
    else {
        const char *err = "404 Not Found";
        send_header(client_fd, 404, "text/plain", strlen(err));
        send(client_fd, err, strlen(err), 0);
    }

    close(client_fd);
}

由此可以实现一个全栈的socket以及多进程的实现,商品的查找与html

相关推荐
mengge.cloud1 小时前
0909华为云网络类服务实践小白实验教程
linux·运维·服务器·网络·华为云·php
Escalating_xu1 小时前
【Linux mmap 文件映射】从虚拟地址到页缓存:读写文件、匿名映射与极简 malloc
linux·缓存
智恒百亿1 小时前
CSDN 文章|八卡 RTX5090 服务器深度实测:256GB 显存池,能否流畅跑通 70B 大模型微调?
运维·服务器
Zixhy1 小时前
Boost 库实现异步高并发服务器
linux·服务器·c++
阿明61 小时前
基础编辑工具使用【Linux】
linux·运维·服务器
Tinyundg1 小时前
关于Termux的基本使用——安卓手机爆改Linux服务器?
linux·运维·服务器·智能手机·个人开发
g10565591391 小时前
华为 OceanStor 基础使用入门指南
服务器·数据库·性能优化
比兔代理1 小时前
游戏加速、直播拉流、视频下载场景下的代理IP技术方案
网络·http·游戏
captain3761 小时前
网络编程(1)
java·网络·ide·java-ee