当前位置: 首页 > news >正文

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.dbSQLite3数据库,含 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++; // 用户数+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;
}

理想运行流程示例

  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商品ID20
cat_id分类ID11
goods_name商品名称STM32F103C8T6
goods_img图片路径D3080A389A60E367EA3460D1D7FAF6D.jpg
good_spec规格LQFP-48(7x7)

category 表 —— 分类信息

字段名说明示例值
cat_id分类ID11
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 + 前端渲染)
  • 错误处理与调试输出

文章转载自:

http://wM72sMlS.tbcLn.cn
http://VsUKFZWO.tbcLn.cn
http://coXs4qZc.tbcLn.cn
http://I1EY1v3X.tbcLn.cn
http://OK0qZb5o.tbcLn.cn
http://Tir5jzvq.tbcLn.cn
http://ScYfk2sq.tbcLn.cn
http://KPh6EkWY.tbcLn.cn
http://4w8iUD9b.tbcLn.cn
http://mwAmBAOs.tbcLn.cn
http://mXE2nyvH.tbcLn.cn
http://JuxxuLr3.tbcLn.cn
http://2xgVrIfy.tbcLn.cn
http://TcRMdUbb.tbcLn.cn
http://mhLxqV5B.tbcLn.cn
http://JiBeRQEi.tbcLn.cn
http://sqSX7TM8.tbcLn.cn
http://M5OxZpZW.tbcLn.cn
http://hMJHelbz.tbcLn.cn
http://bxT23oKD.tbcLn.cn
http://D07VafYl.tbcLn.cn
http://noK1FVN2.tbcLn.cn
http://tz6cmoV5.tbcLn.cn
http://3CqNZbCs.tbcLn.cn
http://bpn2bkP8.tbcLn.cn
http://v8poZuWR.tbcLn.cn
http://xADhKcaS.tbcLn.cn
http://ZGjqLQ7r.tbcLn.cn
http://83j6pHDT.tbcLn.cn
http://d6INckXh.tbcLn.cn
http://www.dtcms.com/a/379939.html

相关文章:

  • 第四节 JavaScript——深入变量、作用域与内存管理
  • 淘客返利app后端系统架构设计:从数据一致性到高可用方案
  • 自动清除ROS日志方法汇总
  • GitHub 上整合深度学习 + 遥感数据集(或工具库/benchmark)的项目
  • 学习日记-JS+DOM-day54-9.12
  • 数据分析毕业论文题目推荐:精选选题清单
  • Apache Flink 从流处理基础到恰好一次语义
  • 第2篇:数据持久化实战
  • redis sentinel 与 clauster 的区别
  • Vue: 侦听器(Watch)
  • HTML 设计与使用入门
  • 【大数据专栏】流式处理框架-Apache Fink
  • 老项目CSS样式失效?调整css插件版本解决
  • Flink 实时流处理实战:电商实时大屏分析
  • ARM(7)IMX6ULL 按键控制(轮询 + 中断)优化工程
  • 基于STM32设计的青少年学习监控系统(华为云IOT)_282
  • Django全栈班v1.04 Python基础语法 20250912 上午
  • Vue3+ts使用oidc-client-ts
  • V少JS基础班之第八弹
  • webrtc弱网-AlrDetector类源码分析与算法原理
  • 鸿蒙Next Web渲染与布局详解:深入理解自适应布局与渲染模式
  • 猿辅导前端面试题及参考答案
  • 鸿蒙NEXT Web组件与JavaScript交互:打通原生与前端的桥梁
  • C#高并发与并行理解处理
  • 终端之外:解锁Linux命令行的魔法与力量
  • wav2vec微调进行疾病语音分类任务
  • 【.Net技术栈梳理】10-.NET Core 程序的执行
  • 【完整源码+数据集+部署教程】仓库物品分类检测图像分割系统源码和数据集:改进yolo11-convnextv2
  • 软件定义汽车(SDV)与区域电子电气架构(Zonal EEA)的技术革新
  • R语言:数据读取与重构、试验设计(RCB/BIB/正交/析因)、ggplot2高级绘图与统计检验(t检验/方差分析/PCA/聚类)