Linux下C语言实现HTTP+SQLite3电子元器件查询系统
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++; // 用户数+1save_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-Typeswitch (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-Lengthhttp_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); // 调试输出SQLsqlite3_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];// 根据参数构造SQLif (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); // 监听队列长度3socklen_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);elsesend_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);elsesend_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 + 前端渲染)
- 错误处理与调试输出