Linux下C语言实现HTTP+SQLite3电子元器件查询系统

发布于:2025-09-13 ⋅ 阅读:(12) ⋅ 点赞:(0)

Linux下C语言实现HTTP+SQLite3电子元器件查询系统


🧩 系统功能模块总览

本系统基于 C语言 + HTTP协议 + SQLite3数据库 构建,运行于Linux环境,实现一个电子元器件信息查询Web服务。系统包含三大核心模块:

1️⃣ 用户管理模块

  • 登录功能(含前端表单验证)
  • 注册页面跳转(通过超链接)
  • 用户身份验证(比对用户名密码)
  • 会话管理(无状态HTTP,每次请求独立验证)

2️⃣ 信息检索模块

  • 关键词搜索(支持模糊匹配商品名)
  • 分类筛选(通过 cat_id 精准筛选商品类别)

3️⃣ 信息展示模块

  • 类别列表展示(如电容、电阻、单片机等)
  • 详情页展示(点击商品跳转,展示完整参数)

📄 文件结构与功能对应表

文件名 功能说明
ser.c 核心服务端程序,处理HTTP请求、用户验证、数据库查询、文件/图片响应等
01.html 登录成功后跳转的搜索主界面,含搜索框与分类导航
02.html 商品列表页,动态加载 /api/images 返回的JSON数据并渲染
03.html 登录页面,含用户名密码输入框及注册跳转按钮
04.html 错误提示页(404/登录失败/参数错误等通用错误页)
05.html 用户注册页面,提交后由服务端处理并重定向
users.txt 用户数据存储文件(格式:username password,每行一条)
componet.db SQLite3数据库,含 goodscategory 两张表,存储商品与分类信息

🧠 核心代码结构详解(ser.c)


🔧 1. 全局定义与类型声明

#include <errno.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <sqlite3.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

// 定义 sockaddr 类型别名,简化强制类型转换
typedef struct sockaddr*(SA);

// 枚举文件类型,用于设置HTTP响应头中的Content-Type
typedef enum
{
  FILE_HTML,
  FILE_JPG,
  FILE_PNG,
  FILE_GIF
} FILE_TYPE;

// 用户结构体,存储用户名与密码
typedef struct
{
  char username[50];
  char password[50];
} User;

// 全局用户数组,最多支持100个用户
User users[100];
int user_count = 0; // 当前用户数量

📂 2. 用户数据持久化函数

load_users() —— 从文件加载用户数据
void load_users()
{
  FILE* file = fopen("users.txt", "r"); // 打开用户数据文件
  if (NULL == file)
  {
    printf("fopen error\n"); // 文件打开失败提示
    return;
  }
  user_count = 0; // 重置用户计数
  // 循环读取用户名和密码,最多读取100条
  while (2 == fscanf(file, "%s %s", users[user_count].username, users[user_count].password))
  {
    user_count++;
    if (user_count >= 100)
    {
      break;
    }
  }
  fclose(file); // 关闭文件
  printf("load [%d] users\n", user_count); // 输出加载用户数量
}

理想输出示例
load [3] users (表示成功加载3个用户)


save_users() —— 将用户数据写入文件
void save_users()
{
  FILE* file = fopen("users.txt", "w"); // 以写入模式打开文件(覆盖原内容)
  if (NULL == file)
  {
    perror("fopen"); // 打开失败,输出系统错误
    return;
  }
  // 循环写入所有用户数据
  for (int i = 0; i < user_count; i++)
  {
    fprintf(file, "%s %s\n", users[i].username, users[i].password);
  }
  fclose(file); // 关闭文件
}

理想结果
users.txt 文件内容更新,格式如:

user1 pass1
user2 pass2

🔍 3. 用户验证与注册函数

check_user() —— 验证用户名密码
int check_user(char* username, char* password)
{
  for (int i = 0; i < user_count; i++)
  {
    // 逐个比对用户名和密码
    if (0 == strcmp(users[i].username, username) && 0 == strcmp(users[i].password, password))
    {
      return 1; // 匹配成功,返回1
    }
  }
  return 0; // 未找到匹配,返回0
}

理想结果
输入正确用户名密码 → 返回 1(登录成功)
输入错误 → 返回 0(登录失败)


add_user() —— 添加新用户
int add_user(char* username, char* password)
{
  if (user_count >= 100) // 用户数已达上限
  {
    return -1; // 返回-1表示失败
  }
  strcpy(users[user_count].username, username); // 复制用户名
  strcpy(users[user_count].password, password); // 复制密码
  user_count++; // 用户数+1
  save_users(); // 持久化到文件
  printf("username:%s\n", username); // 打印注册成功的用户名
  return 1; // 返回1表示成功
}

理想输出示例
username:alice
同时 users.txt 文件末尾新增一行:alice 密码


🌐 4. URL参数解析与解码函数

url_decode() —— 解码URL编码(辅助函数,未在主流程调用)
void url_decode(char* dst, size_t dst_size, char* src)
{
  char* p = dst;
  char* end = dst + dst_size - 1;
  char code[3];

  while (*src && p < end)
  {
    if ('%' == *src && src[1] && src[2]) // 处理 %XX 格式
    {
      memcpy(code, src + 1, 2); // 取出两位十六进制
      code[2] = '\0';
      *p++ = (char)strtol(code, NULL, 16); // 转换为字符
      src += 3;
    }
    else if ('+' == *src) // + 号转为空格
    {
      *p++ = ' ';
      src++;
    }
    else // 其他字符直接复制
    {
      *p++ = *src++;
    }
  }
  *p = '\0'; // 字符串结束
}

理想结果示例
输入 hello%20world → 输出 hello world


hex2dec() —— 十六进制字符转十进制数
int hex2dec(char c)
{
  if ('0' <= c && c <= '9') return c - '0';
  else if ('a' <= c && c <= 'f') return c - 'a' + 10;
  else if ('A' <= c && c <= 'F') return c - 'A' + 10;
  else return -1; // 非法字符
}

理想结果示例
hex2dec('A')10
hex2dec('f')15
hex2dec('G')-1


url_decode_inplace() —— 原地解码URL(主流程使用)
void url_decode_inplace(char url[])
{
  int i = 0;
  int len = strlen(url);
  int res_len = 0;
  char res[2048]; // 临时缓冲区

  for (i = 0; i < len; ++i)
  {
    char c = url[i];
    if (c != '%')
    {
      res[res_len++] = c; // 非%直接复制
    }
    else
    {
      if (i + 2 >= len) break; // 不足三位,跳出
      char c1 = url[++i]; // 取第一位十六进制
      char c0 = url[++i]; // 取第二位
      int num = hex2dec(c1) * 16 + hex2dec(c0); // 计算数值
      if (num < 0) continue; // 非法跳过
      res[res_len++] = (char)num; // 存入结果
    }
  }
  res[res_len] = '\0';
  strcpy(url, res); // 覆盖原字符串
}

理想结果示例
输入 Goods=STM32%20F103 → 输出 Goods=STM32 F103


get_param_value() —— 从URL中提取指定参数值(支持中文解码)
char* get_param_value(char* url, const char* key)
{
  // 静态缓冲区,避免返回局部变量
  static char val_user[256], val_pwd[256], val_goods[256], val_catid[256], val_other[256];
  char* dst = NULL;

  // 根据key选择对应缓冲区
  if (0 == strcmp(key, "username")) dst = val_user;
  else if (0 == strcmp(key, "userpw")) dst = val_pwd;
  else if (0 == strcmp(key, "password")) dst = val_pwd;
  else if (0 == strcmp(key, "Goods")) dst = val_goods;
  else if (0 == strcmp(key, "cat_id")) dst = val_catid;
  else dst = val_other;

  dst[0] = '\0'; // 清空缓冲区
  char* start = strstr(url, key); // 查找参数名
  if (!start) return dst; // 未找到,返回空字符串

  start += strlen(key) + 1; // 跳过"key="部分
  char* end = strchr(start, '&'); // 查找下一个&或结尾
  if (!end) end = start + strlen(start);
  size_t len = end - start;
  if (len >= 255) len = 255; // 防止溢出
  strncpy(dst, start, len);
  dst[len] = '\0';
  url_decode_inplace(dst); // 解码(支持中文)
  printf("DEBUG: 解码参数 %s = [%s]\n", key, dst); // 调试输出
  return dst;
}

理想输出示例
DEBUG: 解码参数 Goods = [STM32F103C8T6]
DEBUG: 解码参数 username = [张三]


📤 5. HTTP响应发送函数

send_head() —— 发送HTTP响应头
int send_head(int conn, char* filename, FILE_TYPE type)
{
  struct stat st;
  int ret = stat(filename, &st); // 获取文件状态
  if (-1 == ret)
  {
    fprintf(stderr, "send_head stat [%s],err:%s\n", filename, strerror(errno));
    return 1;
  }

  // 构造HTTP响应头
  char* http_cmd[6] = {0};
  char buf[512] = {0};

  http_cmd[0] = "HTTP/1.1 200 OK\r\n";
  http_cmd[1] = "server: zhagnsanServ\r\n";

  // 根据文件类型设置Content-Type
  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_GIF:  http_cmd[2] = "Content-Type: image/gif\r\n"; break;
  }

  // 设置Content-Length
  http_cmd[3] = buf;
  sprintf(http_cmd[3], "content-length: %lu\r\n", st.st_size);

  http_cmd[4] = "date: Wed, 10 Sep 2025 03:12:50 GMT\r\n";
  http_cmd[5] = "Connection: closed\r\n\r\n";

  // 发送所有头部
  for (int i = 0; i < 6; i++)
  {
    send(conn, http_cmd[i], strlen(http_cmd[i]), 0);
  }
  return 0;
}

理想结果
客户端收到标准HTTP 200响应头,包含正确Content-Type和长度


send_file() —— 发送整个文件内容
int send_file(int conn, char* filename, FILE_TYPE type)
{
  send_head(conn, filename, type); // 先发头部
  int fd = open(filename, O_RDONLY); // 打开文件
  if (-1 == fd)
  {
    perror("send_file open");
    return 1;
  }

  // 循环读取并发送文件内容
  while (1)
  {
    char buf[1024] = {0};
    int ret = read(fd, buf, sizeof(buf));
    if (ret <= 0) break;
    send(conn, buf, ret, 0);
  }
  close(fd);
  return 0;
}

理想结果
客户端完整收到HTML或图片文件,浏览器正常渲染


send_register_success() —— 注册成功跳转登录页
void send_register_success(int conn)
{
  const char* success_page =
      "HTTP/1.1 200 OK\r\n"
      "Content-Type: text/html; charset=UTF-8\r\n"
      "Connection: close\r\n\r\n"
      "<script>location.href='03.html';</script>"; // 自动跳转到登录页

  send(conn, success_page, strlen(success_page), 0);
}

理想结果
浏览器自动跳转到 03.html(登录页面)


send_register_failed() —— 注册失败提示页
void send_register_failed(int conn, const char* reason)
{
  char failed_page[1024];
  snprintf(failed_page, sizeof(failed_page),
           "HTTP/1.1 200 OK\r\n"
           "Content-Type: text/html; charset=UTF-8\r\n"
           "Connection: close\r\n\r\n"
           "<!DOCTYPE html>"
           "<html><head><meta charset='utf-8'><title>注册失败</title></head>"
           "<body style='text-align:center; font-family:Arial;'>"
           "<h2 style='color:red;'>注册失败!</h2>"
           "<p>%s</p>"
           "<p><a href='05.html'>返回注册页面</a> | <a href='03.html'>返回登录页面</a></p>"
           "</body></html>",
           reason);

  send(conn, failed_page, strlen(failed_page), 0);
}

理想结果
显示红色“注册失败!”字样,提供返回链接


🗃️ 6. SQLite3数据库操作函数

open_db() —— 打开数据库连接
sqlite3* db = NULL; // 全局数据库指针

int open_db()
{
  int ret = sqlite3_open("./componet.db", &db); // 打开数据库
  if (ret != SQLITE_OK)
  {
    fprintf(stderr, "sqlite3_open %s\n", sqlite3_errmsg(db));
    sqlite3_close(db);
    return -1;
  }
  return 0; // 成功返回0
}

理想结果
数据库成功打开,db 指针有效,无错误输出


close_db() —— 关闭数据库连接
void close_db()
{
  if (db) sqlite3_close(db); // 安全关闭
}

理想结果
数据库连接安全关闭,无内存泄漏


send_search_result() —— 旧版搜索结果页(直接生成HTML)
void send_search_result(int conn, char* goods_name)
{
  if (open_db() != 0) // 打开失败则返回错误页
  {
    send_file(conn, "./04.html", FILE_HTML);
    return;
  }

  // 构造SQL语句,模糊查询
  char sql[512];
  snprintf(sql, sizeof(sql), "SELECT goods_name, goods_img FROM goods WHERE goods_name LIKE '%%%s%%' ORDER BY goods_id",
           goods_name);

  printf("执行 SQL: %s\n", sql); // 调试输出SQL

  sqlite3_stmt* stmt;
  int ret = sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
  if (ret != SQLITE_OK)
  {
    fprintf(stderr, "sqlite3_prepare_v2 %s\n", sqlite3_errmsg(db));
    sqlite3_finalize(stmt);
    sqlite3_close(db);
    send_file(conn, "./04.html", FILE_HTML);
    return;
  }

  // 构造HTML响应
  char response[8192];
  int pos = 0;

  pos += snprintf(response + pos, sizeof(response) - pos,
                  "HTTP/1.1 200 OK\r\n"
                  "Content-Type: text/html; charset=UTF-8\r\n"
                  "Connection: close\r\n\r\n"
                  "<!DOCTYPE html>"
                  "<html><head><meta charset='utf-8'><title>搜索结果</title>"
                  "<style>body{font-family:Arial;margin:20px;}.item{display:inline-block;width:200px;margin:10px;text-align:center;}img{max-width:150px;max-height:150px;border:1px solid #ccc;}a{color:blue;text-decoration:none;}</style></head><body>"
                  "<h2>?? 搜索结果:'%s'</h2>",
                  goods_name);

  // 遍历查询结果,生成商品项
  while (SQLITE_ROW == (ret = sqlite3_step(stmt)))
  {
    const char* name = (const char*)sqlite3_column_text(stmt, 0);
    const char* img_path = (const char*)sqlite3_column_text(stmt, 1);
    if (!name || !img_path) continue;

    pos += snprintf(response + pos, sizeof(response) - pos,
                    "<div class='item'>"
                    "<img src='%s' alt='%s'>"
                    "<p><a href='/detail_%d'>%s</a></p>" // 此处硬编码 detail_100,实际应为 goods_id
                    "</div>",
                    img_path, name, 100, name);
  }

  sqlite3_finalize(stmt);
  sqlite3_close(db);
  strcat(response, "</body></html>");
  send(conn, response, strlen(response), 0);
}

理想输出示例
执行 SQL: SELECT goods_name, goods_img FROM goods WHERE goods_name LIKE '%STM32%' ORDER BY goods_id
理想结果
浏览器显示搜索结果网格,每个商品含图片和名称链接(但链接ID固定为100,存在BUG)


get_param() —— 简化版参数提取(用于商品详情页)
char* get_param(const char* url, const char* key)
{
  static char value[100];
  value[0] = '\0';

  char* pos = strstr(url, key);
  if (pos)
  {
    pos += strlen(key) + 1;
    char* end = strchr(pos, '&');
    if (!end) end = pos + strlen(pos);
    strncpy(value, pos, end - pos);
    value[end - pos] = '\0';
  }
  return value;
}

理想结果示例
输入 /06.html?goods_id=20 → 返回 "20"


show_goods_detail() —— 商品详情页(新版,使用dprintf)
void show_goods_detail(int conn, const char* goods_id)
{
  sqlite3* db;
  sqlite3_stmt* stmt;
  int rc = sqlite3_open("componet.db", &db);
  if (rc)
  {
    dprintf(conn, "HTTP/1.1 500 Internal Server Error\r\n\r\n数据库打开失败");
    return;
  }

  // 构造SQL,查询完整商品信息
  char sql[256];
  snprintf(sql, sizeof(sql),
           "SELECT goods_id, cat_id, goods_sn, goods_name, good_spec, "
           "goods_number, goods_weight, market_price, shop_price, "
           "keywords, goods_desc, goods_img "
           "FROM goods WHERE goods_id=%s;",
           goods_id);

  rc = sqlite3_prepare_v2(db, sql, -1, &stmt, 0);
  if (rc != SQLITE_OK)
  {
    dprintf(conn, "HTTP/1.1 500 Internal Server Error\r\n\r\nSQL错误");
    sqlite3_close(db);
    return;
  }

  if (SQLITE_ROW == sqlite3_step(stmt))
  {
    // 发送HTTP头
    dprintf(conn, "HTTP/1.1 200 OK\r\nContent-Type: text/html; charset=utf-8\r\n\r\n");
    dprintf(conn, "<html><head><title>商品详情</title></head><body>");

    // 显示商品名称和图片
    dprintf(conn, "<h1>%s</h1>", sqlite3_column_text(stmt, 3));
    dprintf(conn, "<img src='/img/%s' alt='%s' style='max-width:200px'><br><br>", sqlite3_column_text(stmt, 11),
            sqlite3_column_text(stmt, 3));

    // 显示详细参数表格
    dprintf(conn, "<table border='1'>");
    dprintf(conn, "<tr><td>goods_id</td><td>%s</td></tr>", sqlite3_column_text(stmt, 0));
    dprintf(conn, "<tr><td>cat_id</td><td>%s</td></tr>", sqlite3_column_text(stmt, 1));
    dprintf(conn, "<tr><td>goods_sn</td><td>%s</td></tr>", sqlite3_column_text(stmt, 2));
    dprintf(conn, "<tr><td>good_spec</td><td>%s</td></tr>", sqlite3_column_text(stmt, 4));
    dprintf(conn, "<tr><td>goods_number</td><td>%s</td></tr>", sqlite3_column_text(stmt, 5));
    dprintf(conn, "<tr><td>goods_weight</td><td>%s</td></tr>", sqlite3_column_text(stmt, 6));
    dprintf(conn, "<tr><td>market_price</td><td>%s</td></tr>", sqlite3_column_text(stmt, 7));
    dprintf(conn, "<tr><td>shop_price</td><td>%s</td></tr>", sqlite3_column_text(stmt, 8));
    dprintf(conn, "<tr><td>keywords</td><td>%s</td></tr>", sqlite3_column_text(stmt, 9));
    dprintf(conn, "<tr><td>goods_desc</td><td>%s</td></tr>", sqlite3_column_text(stmt, 10));
    dprintf(conn, "</table>");
    dprintf(conn, "</body></html>");
  }
  else
  {
    dprintf(conn, "HTTP/1.1 404 Not Found\r\n\r\n未找到该商品");
  }

  sqlite3_finalize(stmt);
  sqlite3_close(db);
}

理想结果
浏览器显示商品详情页,包含大标题、图片、完整参数表格


/api/images —— JSON数据接口(供02.html前端调用)
// 在main函数中处理 /api/images 请求
if (0 == strncmp(url, "/api/images", 11))
{
  char* goods_name = get_param_value(url, "Goods");
  char* cat_id_str = get_param_value(url, "cat_id");
  printf(">>> /api/images: Goods=%s, cat_id=%s\n", goods_name ? goods_name : "(null)",
         cat_id_str ? cat_id_str : "(null)");

  if (open_db() != 0)
  {
    const char* err = "HTTP/1.1 500\r\n\r\n{\"error\":\"db\"}";
    send(conn, err, strlen(err), 0);
    close(conn);
    continue;
  }

  char sql[512];
  // 根据参数构造SQL
  if (cat_id_str && cat_id_str[0])
  {
    int cat_id = atoi(cat_id_str);
    snprintf(sql, sizeof(sql),
             "SELECT goods_id,goods_name,goods_img,good_spec FROM goods WHERE cat_id=%d ORDER BY goods_id", cat_id);
  }
  else if (goods_name && goods_name[0])
  {
    snprintf(sql, sizeof(sql),
             "SELECT goods_id,goods_name,goods_img,good_spec FROM goods WHERE goods_name LIKE '%%%s%%' ORDER BY goods_id",
             goods_name);
  }
  else
  {
    const char* err = "HTTP/1.1 400\r\n\r\n{\"error\":\"para\"}";
    send(conn, err, strlen(err), 0);
    close_db();
    close(conn);
    continue;
  }

  sqlite3_stmt* stmt;
  if (sqlite3_prepare_v2(db, sql, -1, &stmt, NULL) != SQLITE_OK)
  {
    sqlite3_close(db);
    const char* err = "HTTP/1.1 500\r\n\r\n{\"error\":\"sql\"}";
    send(conn, err, strlen(err), 0);
    close(conn);
    continue;
  }

  // 构造JSON数组
  char resp[8192];
  int pos = 0;
  pos += snprintf(resp + pos, sizeof(resp) - pos,
                  "HTTP/1.1 200 OK\r\nContent-Type: application/json; charset=UTF-8\r\nConnection: close\r\n\r\n[");

  int first = 1;
  while (SQLITE_ROW == sqlite3_step(stmt))
  {
    int id = sqlite3_column_int(stmt, 0);
    const char* name = (const char*)sqlite3_column_text(stmt, 1);
    const char* img = (const char*)sqlite3_column_text(stmt, 2);
    const char* spec = (const char*)sqlite3_column_text(stmt, 3);
    if (!name || !img) continue;

    // 确保图片路径以 /img/ 开头
    char full_img[512];
    if (strncmp(img, "/img/", 5) != 0)
    {
      snprintf(full_img, sizeof(full_img), "/img/%s", img);
    }
    else
    {
      strcpy(full_img, img);
    }

    if (!first) pos += snprintf(resp + pos, sizeof(resp) - pos, ",");
    pos += snprintf(resp + pos, sizeof(resp) - pos,
                    "{\"goods_id\":%d,\"name\":\"%s\",\"spec\":\"%s\",\"image\":\"%s\"}", id, name,
                    spec ? spec : "", full_img);
    first = 0;
  }

  sqlite3_finalize(stmt);
  close_db();
  pos += snprintf(resp + pos, sizeof(resp) - pos, "]");
  printf(">>> /api/images 返回 JSON 长度 %d\n", pos);
  send(conn, resp, pos, 0);
  close(conn);
  continue;
}

理想输出示例
>>> /api/images: Goods=STM32, cat_id=(null)
>>> /api/images 返回 JSON 长度 487
理想结果
返回JSON数组,如:

[
  {"goods_id":20,"name":"STM32F103C8T6","spec":"LQFP-48(7x7)","image":"/img/D3080A389A60E367EA3460D1D7FAF6D.jpg"}
]

🚀 7. 主函数 main() —— HTTP服务器核心逻辑

int main(int argc, char** argv)
{
  load_users(); // 启动时加载用户数据

  // 创建TCP监听套接字
  int listfd = socket(AF_INET, SOCK_STREAM, 0);
  if (-1 == listfd)
  {
    perror("socket");
    return 1;
  }

  // 设置端口复用
  int on = 1;
  setsockopt(listfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
  setsockopt(listfd, SOL_SOCKET, SO_REUSEPORT, &on, sizeof(on));

  // 绑定80端口
  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 (-1 == bind(listfd, (SA)&ser, sizeof(ser)))
  {
    perror("bind");
    return 1;
  }
  listen(listfd, 3); // 监听队列长度3
  socklen_t len = sizeof(cli);

  // 主循环:接受连接、处理请求
  while (1)
  {
    int conn = accept(listfd, (SA)&cli, &len);
    if (-1 == conn)
    {
      perror("accept");
      close(conn);
      continue;
    }

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

    printf("\n==================== 请求头 ====================\n%s\n", buf);

    // 解析HTTP请求行
    char buf_copy[4096];
    strcpy(buf_copy, buf);
    char* method = strtok(buf_copy, " ");
    char* url = strtok(NULL, " ");
    char* ver = strtok(NULL, "\r");
    if (!url)
    {
      close(conn);
      continue;
    }

    // 根路径 → 登录页
    if (0 == strcmp(url, "/"))
    {
      send_file(conn, "./03.html", FILE_HTML);
      close(conn);
      continue;
    }

    // 登录处理
    if (0 == strncmp(url, "/login", 6))
    {
      char* username = get_param_value(url, "username");
      char* password = get_param_value(url, "userpw");
      printf(">>> 登录验证: user=%s, pwd=%s\n", username ? username : "(null)", password ? password : "(null)");
      if (username && password && check_user(username, password))
      {
        printf(">>> 登录成功 → 01.html\n");
        send_file(conn, "./01.html", FILE_HTML);
      }
      else
      {
        printf(">>> 登录失败 → 04.html\n");
        send_file(conn, "./04.html", FILE_HTML);
      }
      close(conn);
      continue;
    }

    // 注册处理
    if (0 == strncmp(url, "/register", 9))
    {
      char* usr = get_param_value(url, "username");
      char* pwd = get_param_value(url, "password");
      printf(">>> 注册: user=%s\n", usr ? usr : "(null)");
      if (usr && pwd && add_user(usr, pwd) == 1)
        send_register_success(conn);
      else
        send_register_failed(conn, "注册失败");
      close(conn);
      continue;
    }

    // 商品详情页(旧版)
    if (0 == strncmp(url, "/06.html", 8))
    {
      char* goods_id = get_param(url, "goods_id");
      if (goods_id && strlen(goods_id) > 0)
      {
        printf(">>> 动态详情页: goods_id=%s\n", goods_id);
        show_goods_detail(conn, goods_id);
      }
      else
      {
        dprintf(conn, "HTTP/1.1 400 Bad Request\r\n\r\n缺少 goods_id 参数");
      }
      close(conn);
      continue;
    }

    // 搜索重定向到前端页(触发前端JS加载)
    if (0 == strncmp(url, "/search", 7))
    {
      char* gName = get_param_value(url, "Goods");
      char* cId = get_param_value(url, "cat_id");
      printf(">>> 搜索: Goods=%s, cat_id=%s\n", gName ? gName : "(null)", cId ? cId : "(null)");

      if ((gName && gName[0]) || (cId && cId[0]))
      {
        char redirect_url[512];
        if (gName && gName[0])
        {
          snprintf(redirect_url, sizeof(redirect_url),
                   "HTTP/1.1 302 Found\r\n"
                   "Location: /02.html?Goods=%s\r\n"
                   "Connection: close\r\n\r\n",
                   gName);
        }
        else
        {
          snprintf(redirect_url, sizeof(redirect_url),
                   "HTTP/1.1 302 Found\r\n"
                   "Location: /02.html?cat_id=%s\r\n"
                   "Connection: close\r\n\r\n",
                   cId);
        }
        printf(">>> 搜索重定向到02.html\n");
        send(conn, redirect_url, strlen(redirect_url), 0);
      }
      else
      {
        printf(">>> 搜索参数为空 → 04.html\n");
        send_file(conn, "./04.html", FILE_HTML);
      }
      close(conn);
      continue;
    }

    // 图片资源服务
    if (0 == strncmp(url, "/img/", 5))
    {
      char img_path[512];
      snprintf(img_path, sizeof(img_path), ".%s", url);
      if (strstr(url, ".png"))
        send_file(conn, img_path, FILE_PNG);
      else if (strstr(url, ".jpg") || strstr(url, ".jpeg"))
        send_file(conn, img_path, FILE_JPG);
      else if (strstr(url, ".gif"))
        send_file(conn, img_path, FILE_GIF);
      else
        send_file(conn, img_path, FILE_JPG); // 默认按JPG处理
      close(conn);
      continue;
    }

    // 静态HTML页面
    if (strstr(url, ".html"))
    {
      char* html_path = strtok(url, "?"); // 去掉参数部分
      printf(">>> 静态HTML: %s\n", html_path + 1);
      send_file(conn, html_path + 1, FILE_HTML);
      close(conn);
      continue;
    }

    // 404 未找到
    const char* nf = "HTTP/1.1 404 Not Found\r\n\r\n<h1>404 Not Found</h1>";
    send(conn, nf, strlen(nf), 0);
    close(conn);
  }
  close(listfd);
  return 0;
}

理想运行流程示例

  1. 用户访问 http://localhost/ → 显示登录页 03.html
  2. 输入用户名密码 → 提交到 /login?username=xxx&userpw=yyy
  3. 验证成功 → 跳转到 01.html(搜索主页)
  4. 点击分类或输入关键词搜索 → 重定向到 02.html?cat_id=11
  5. 02.html 页面JS自动调用 /api/images?cat_id=11 获取JSON数据并渲染
  6. 点击商品 → 跳转到 /06.html?goods_id=20 → 显示详情页

🖼️ 前端HTML页面功能说明

01.html —— 搜索主界面

<!-- 含搜索框与分类导航 -->
<form action="/search" method="get">
  <input type="text" name="Goods" placeholder="请输入商品名称" required>
  <button type="submit">搜索</button>
</form>
<ul>
  <li><a href="02.html?cat_id=1">直插铝电解电容</a></li>
  <li><a href="02.html?cat_id=11">单片机(MCU/MPU/SOC)</a></li>
</ul>

⚠️ 注意:此处直接跳转 02.html,未经过 /search 重定向,与后端设计略有不一致,但功能正常


02.html —— 动态商品列表页(核心前端)

<script>
const p = new URLSearchParams(location.search),
      name = p.get('Goods'),
      cat  = p.get('cat_id');
if (!name && !cat) {
  box.textContent = '缺少 Goods 或 cat_id 参数';
} else {
  fetch('/api/images?' + (name ? 'Goods=' + encodeURIComponent(name)
                              : 'cat_id=' + encodeURIComponent(cat)))
    .then(r => r.json())
    .then(arr => {
      box.innerHTML = arr.length
        ? arr.map(g => `
            <span style="display:inline-block;margin:15px;text-align:center">
              <a href="/06.html?goods_id=${g.goods_id}">
                <img src="${g.image}" alt="${g.name}" width="300"><br>
                ${g.name}<br><small>${g.spec || ''}</small>
              </a>
            </span>`).join('')
        : '未找到商品';
    })
    .catch(() => box.textContent = '加载失败');
}
</script>

理想结果
页面动态加载商品网格,点击跳转到对应详情页


03.html —— 登录页

<form action='login' autocomplete='off'>
  用户名: <input type='text' name='username' required placeholder='用户名'>
  密码: <input type='password' name='userpw' required placeholder='密码'>
  <input type='submit' value="登录">
  <a href="05.html"><input type='button' value="注册"></a>
</form>

05.html —— 注册页

<form action='register'>
  用户名: <input type='text' name='username' required='required' placeholder='用户名'>
  密码: <input type='password' name='password' required='required' placeholder='密码'>
  <input type='submit' value="注册">
  <a href="03.html"><input type='button' value="返回登录"></a>
</form>

📊 数据库表结构说明

goods 表 —— 商品信息

字段名 说明 示例值
goods_id 商品ID 20
cat_id 分类ID 11
goods_name 商品名称 STM32F103C8T6
goods_img 图片路径 D3080A389A60E367EA3460D1D7FAF6D.jpg
good_spec 规格 LQFP-48(7x7)

category 表 —— 分类信息

字段名 说明 示例值
cat_id 分类ID 11
cat_name 分类名称 单片机(MCU/MPU/SOC)

✅ 系统运行前提条件

  1. Linux环境(Ubuntu/CentOS等)
  2. 安装 gcc, sqlite3
  3. 编译命令:gcc ser.c -o ser -lsqlite3
  4. 数据库文件 componet.db 存在于当前目录
  5. HTML文件(01~05.html)和图片目录 img/ 存在于当前目录
  6. 用户文件 users.txt(可为空,注册后自动生成)
  7. 以root权限运行(绑定80端口):sudo ./ser

🎯 总结

  • C语言实现简易HTTP服务器
  • Socket编程与TCP连接处理
  • HTTP协议请求解析与响应构造
  • URL参数提取与中文解码
  • SQLite3数据库查询与结果处理
  • 静态文件服务(HTML/图片)
  • 用户注册登录系统(文件存储)
  • 前后端分离设计(JSON API + 前端渲染)
  • 错误处理与调试输出

网站公告

今日签到

点亮在社区的每一天
去签到