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数据库,含 goods 和 category 两张表,存储商品与分类信息 |
🧠 核心代码结构详解(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;
}
✅ 理想运行流程示例:
- 用户访问
http://localhost/
→ 显示登录页03.html
- 输入用户名密码 → 提交到
/login?username=xxx&userpw=yyy
- 验证成功 → 跳转到
01.html
(搜索主页)- 点击分类或输入关键词搜索 → 重定向到
02.html?cat_id=11
02.html
页面JS自动调用/api/images?cat_id=11
获取JSON数据并渲染- 点击商品 → 跳转到
/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) |
✅ 系统运行前提条件
- Linux环境(Ubuntu/CentOS等)
- 安装
gcc
,sqlite3
库 - 编译命令:
gcc ser.c -o ser -lsqlite3
- 数据库文件
componet.db
存在于当前目录 - HTML文件(01~05.html)和图片目录
img/
存在于当前目录 - 用户文件
users.txt
(可为空,注册后自动生成) - 以root权限运行(绑定80端口):
sudo ./ser
🎯 总结
- C语言实现简易HTTP服务器
- Socket编程与TCP连接处理
- HTTP协议请求解析与响应构造
- URL参数提取与中文解码
- SQLite3数据库查询与结果处理
- 静态文件服务(HTML/图片)
- 用户注册登录系统(文件存储)
- 前后端分离设计(JSON API + 前端渲染)
- 错误处理与调试输出