系统编程—在线商城信息查询系统

一、项目概述

本文将介绍如何基于 C 语言实现一个简易的在线商城 Web 服务器,该服务器具备用户注册、登录、商品搜索、商品详情展示等核心功能,底层采用 SQLite3 数据库存储数据,同时解决了 SQL 注入、URL 解码等常见 Web 开发问题,可作为轻量级在线商城的基础框架。

技术栈

  • 编程语言:C 语言
  • 网络编程:Socket(TCP/IP)
  • 数据库:SQLite3
  • Web 协议:HTTP/1.1
  • 其他:文件 I/O、URL 解码、SQL 预处理语句

二、核心功能实现

1. 项目整体架构

服务器采用经典的 TCP Socket 监听 - 连接模型,端口绑定 80(HTTP 默认端口),主要处理 GET 请求,核心流程:

  1. 创建监听 Socket 并设置端口复用
  2. 循环接收客户端连接
  3. 解析 HTTP 请求行,提取请求方法和 URL
  4. 根据 URL 路由到不同业务逻辑(注册 / 登录 / 商品搜索 / 详情)
  5. 处理完成后返回 HTML / 图片等资源,关闭连接

2. 数据库基础操作

SQLite3 是轻量级嵌入式数据库,本项目使用预处理语句(sqlite3_prepare_v2 + 绑定参数)解决 SQL 注入问题,核心封装了注册和登录两个数据库操作函数。

2.1 用户注册功能
复制代码
int write_register(char* name, char* pass)
{
    sqlite3* db = NULL;
    int ret = sqlite3_open("123.db", &db);
    if (ret != SQLITE_OK) {
        fprintf(stderr, "sqlite3_open %s\n", sqlite3_errmsg(db));
        sqlite3_close(db);
        return 0;
    }

    sqlite3_stmt* stmt;
    // 使用占位符?防止SQL注入
    const char* sql = "INSERT INTO user (name, pass) VALUES (?, ?);";
    ret = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
    if (ret != SQLITE_OK) {
        fprintf(stderr, "sqlite3_prepare_v2 INSERT failed: %s\n", sqlite3_errmsg(db));
        sqlite3_close(db);
        return 0;
    }

    // 绑定参数到占位符
    sqlite3_bind_text(stmt, 1, name, -1, SQLITE_STATIC);
    sqlite3_bind_text(stmt, 2, pass, -1, SQLITE_STATIC);

    ret = sqlite3_step(stmt);
    if (ret != SQLITE_DONE) {
        fprintf(stderr, "sqlite3_step INSERT failed: %s\n", sqlite3_errmsg(db));
        sqlite3_finalize(stmt);
        sqlite3_close(db);
        return 0;
    }

    printf("Register success for user: %s\n", name);

    sqlite3_finalize(stmt);
    sqlite3_close(db);
    return 1;
}
2.2 用户登录验证
复制代码
int is_denglu_right(char* name, char* pass)
{
    sqlite3* db = NULL;
    int ret = sqlite3_open("123.db", &db);
    if (ret != SQLITE_OK) {
        fprintf(stderr, "sqlite3_open %s\n", sqlite3_errmsg(db));
        sqlite3_close(db);
        return 0;
    }

    int flag = 0;
    sqlite3_stmt* stmt;
    const char* sql = "SELECT * FROM user WHERE name = ? AND pass = ?;";
    ret = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
    if (ret != SQLITE_OK) {
        fprintf(stderr, "sqlite3_prepare_v2 SELECT failed: %s\n", sqlite3_errmsg(db));
        sqlite3_close(db);
        return 0;
    }

    sqlite3_bind_text(stmt, 1, name, -1, SQLITE_STATIC);
    sqlite3_bind_text(stmt, 2, pass, -1, SQLITE_STATIC);

    ret = sqlite3_step(stmt);
    if (ret == SQLITE_ROW) { // 查询到数据则登录成功
        flag = 1;
    }

    sqlite3_finalize(stmt);
    sqlite3_close(db);
    return flag;
}

3. URL 解码功能

Web 请求中 URL 参数会被编码(如空格转为 +、特殊字符转为 % XX),需解码后才能正确处理:

复制代码
void url_decode(char* dest, const char* src) {
    char c;
    while (*src) {
        if (*src == '%') {
            src++;
            if (*src && *(src+1)) {
                // 解析十六进制编码
                c = ( ( (*src >= 'A') ? (toupper(*src) - 'A' + 10) : (*src - '0') ) << 4 )
                  + ( (*(src+1) >= 'A') ? (toupper(*(src+1)) - 'A' + 10) : (*(src+1) - '0') );
                *dest++ = c;
                src += 2;
            }
        } else if (*src == '+') {
            *dest++ = ' '; // +号还原为空格
            src++;
        } else {
            *dest++ = *src++;
        }
    }
    *dest = '\0';
}

4. 商品搜索功能

解析 /search 请求中的关键词,解码后查询商品表,动态生成搜索结果 HTML:

复制代码
else if (strncmp(url, "/search?", 8) == 0) {
    char* name_ptr = strstr(url, "username=");
    if (name_ptr) {
        char encoded_name[128] = {0};
        char decoded_name[128] = {0};
        sscanf(name_ptr, "username=%s", encoded_name);
        
        url_decode(decoded_name, encoded_name); // 解码搜索关键词

        FILE* fp = fopen("05.html", "w");
        if (NULL == fp) { perror("fopen 05.html"); close(conn); continue; }

        // 生成搜索结果HTML头部
        fprintf(fp, "<!DOCTYPE html>\n<html>\n<head>\n<meta charset='utf-8'>\n<title>Search Results</title>\n</head>\n<body>\n");
        fprintf(fp, "<h1>Search results for: %s</h1>\n", decoded_name);

        sqlite3* db = NULL;
        if (sqlite3_open("123.db", &db) == SQLITE_OK) {
            char* errmsg = NULL;
            char sql_cmd[512] = {0};
            // 模糊查询商品名称
            sprintf(sql_cmd, "select goods_id,goods_name,goods_img from goods where goods_name like '%s%%';", decoded_name);
            ret = sqlite3_exec(db, sql_cmd, detail3, fp, &errmsg);
            if (ret != SQLITE_OK) {
                fprintf(stderr, "exec search [%s] msg:%s\n", sql_cmd, errmsg);
                sqlite3_free(errmsg);
            }
            sqlite3_close(db);
        }
        fprintf(fp, "</body>\n</html>");
        fclose(fp);
        send_file(conn, "./05.html", FILE_HTML);
    } else {
        send_file(conn, "./03.html", FILE_HTML);
    }
}

5. HTTP 响应封装

封装了文件大小计算、响应头发送、文件发送函数,支持 HTML/PNG/JPG/ICO 等类型文件:

复制代码
// 计算文件大小
long file_size(char* file)
{
    int fd = open(file, O_RDONLY);
    if (-1 == fd) {
        perror("file size open error");
        fprintf(stderr, "filename is %s\n", file);
        return -1;
    }
    long size = lseek(fd, 0, SEEK_END);
    close(fd);
    return size;
}

// 发送HTTP响应头
int send_head(int conn, char* file, FILE_TYPE type)
{
    char buf[256] = {0};
    long size = file_size(file);
    if (size < 0) return -1;

    char* http_cmd[7] = {NULL};
    http_cmd[0] = "HTTP/1.1 200 OK\r\n";
    http_cmd[1] = "Date: Tue, 15 Nov 1994 08:12:31 GMT\r\n";
    switch (type) {
        case FILE_HTML: http_cmd[2] = "Content-Type: text/html;charset=utf-8\r\n"; break;
        case FILE_PNG:  http_cmd[2] = "Content-Type: image/png\r\n"; break;
        case FILE_JPG:  http_cmd[2] = "Content-Type: image/jpeg\r\n"; break;
        case FILE_ICO:  http_cmd[2] = "Content-Type: image/x-icon\r\n"; break;
        default:        http_cmd[2] = "Content-Type: text/html;charset=utf-8\r\n";
    }
    http_cmd[3] = buf;
    sprintf(buf, "Content-Length: %ld\r\n", size);
    http_cmd[4] = "Connection: close\r\n";
    http_cmd[5] = "Server: MYWEBSer\r\n";
    http_cmd[6] = "Content-Language: zh-CN\r\n\r\n";

    for (int i = 0; i < 7; i++) {
        send(conn, http_cmd[i], strlen(http_cmd[i]), 0);
    }
    return 0;
}

// 发送文件内容
int send_file(int conn, char* filename, FILE_TYPE type)
{
    if (send_head(conn, filename, type) != 0) {
        dprintf(conn, "HTTP/1.1 404 Not Found\r\nConnection: close\r\n\r\n");
        return 1;
    }
    int fd = open(filename, O_RDONLY);
    if (-1 == fd) {
        perror("open file to send");
        return 1;
    }
    char buf[4096] = {0};
    int rd_ret;
    while ((rd_ret = read(fd, buf, sizeof(buf))) > 0) {
        send(conn, buf, rd_ret, 0);
    }
    close(fd);
    return 0;
}

三、服务器主流程

复制代码
int main(int argc, char** argv)
{
    int listfd = socket(AF_INET, SOCK_STREAM, 0);
    if (-1 == listfd) { perror("socket"); return 1; }

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

    struct sockaddr_in ser, cli;
    bzero(&ser, sizeof(ser));
    ser.sin_family = AF_INET;
    ser.sin_port = htons(80);
    ser.sin_addr.s_addr = INADDR_ANY;
    if (bind(listfd, (SA)&ser, sizeof(ser)) == -1) { perror("bind"); return 1; }
    if (listen(listfd, 128) == -1) { perror("listen"); return 1; }

    printf("Server is running on port 80...\n");

    socklen_t len = sizeof(cli);
    while (1) {
        int conn = accept(listfd, (SA)&cli, &len);
        if (-1 == conn) {
            if (errno == EINTR) continue;
            perror("accept");
            continue;
        }

        char buf[1024] = {0};
        int ret = recv(conn, buf, sizeof(buf) - 1, 0);
        if (ret <= 0) { close(conn); continue; }

        char method[16], url[512], ver[16];
        sscanf(buf, "%s %s %s", method, url, ver);

        // 路由分发
        if (strcmp(method, "GET") != 0) {
            dprintf(conn, "HTTP/1.1 501 Not Implemented\r\nConnection: close\r\n\r\n");
        } else if (strcmp(url, "/") == 0) {
            send_file(conn, "./03.html", FILE_HTML);
        } else if (strncmp(url, "/register?", 10) == 0) {
            // 注册逻辑
        } else if (strncmp(url, "/login?", 7) == 0) {
            // 登录逻辑
        } else if (strncmp(url, "/search?", 8) == 0) {
            // 搜索逻辑
        } else if (strncmp(url, "/detail_", 8) == 0) {
            // 详情逻辑
        } else if (strstr(url, ".html")) {
            send_file(conn, url + 1, FILE_HTML);
        } else {
            dprintf(conn, "HTTP/1.1 404 Not Found\r\nConnection: close\r\n\r\n");
        }

        close(conn);
    }
    close(listfd);
    return 0;
}

四、编译与运行

  1. 编译命令

需链接 SQLite3 库,编译命令如下:

复制代码
gcc 04ser.c -o web_shop -lsqlite3 -Wall
  1. 运行前准备

创建 SQLite3 数据库文件 123.db,并初始化表结构:

复制代码
-- 用户表
CREATE TABLE user (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    name TEXT UNIQUE NOT NULL,
    pass TEXT NOT NULL
);

-- 商品表
CREATE TABLE goods (
    goods_id INTEGER PRIMARY KEY,
    goods_name TEXT NOT NULL,
    goods_img TEXT,
    goods_desc TEXT
);

准备前端 HTML 文件(03.html/04.html/06.html/07.html)及商品图片资源。

  1. 启动服务器

    ./web_shop

浏览器访问 http://127.0.0.1 即可进入商城首页。

五、核心优化点

  1. 防 SQL 注入:全程使用 SQLite3 预处理语句(sqlite3_prepare_v2 + 绑定参数),避免拼接 SQL 字符串。
  2. URL 解码:处理前端传递的编码参数,保证中文 / 特殊字符正常显示。
  3. 资源类型适配:支持 HTML、PNG、JPG、ICO 等多种文件类型的 HTTP 响应。
  4. 错误处理:完善的文件操作、数据库操作错误处理,提升稳定性。
  5. 端口复用:设置SO_REUSEADDR选项,避免服务器重启时端口占用问题。
相关推荐
郝学胜-神的一滴17 小时前
深入理解Linux中的Try锁机制
linux·服务器·开发语言·c++·程序人生
散峰而望18 小时前
【算法竞赛】顺序表和vector
c语言·开发语言·数据结构·c++·人工智能·算法·github
cpp_250118 小时前
B3927 [GESP202312 四级] 小杨的字典
数据结构·c++·算法·题解·洛谷
Cx330❀18 小时前
《C++ 递归、搜索与回溯》第2-3题:合并两个有序链表,反转链表
开发语言·数据结构·c++·算法·链表·面试
小六子成长记18 小时前
【C++】:多态的实现
开发语言·c++
chen_22718 小时前
动态桌面方案
c++·qt·ffmpeg·kanzi
liulilittle18 小时前
OPENPPP2 Code Analysis Three
网络·c++·网络协议·信息与通信·通信
꧁Q༒ོγ꧂18 小时前
算法详解(一)--算法系列开篇:什么是算法?
开发语言·c++·算法
橘颂TA18 小时前
【剑斩OFFER】算法的暴力美学——力扣:1047 题:删除字符串中的所有相邻重复项
c++·算法·leetcode·职场和发展·结构于算法