本文章综合使用
1.基于 BSD Socket 原生实现 TCP 服务端监听与连接处理,采用fork()多进程并发模型响应多用户请求,配合SIGCHLD信号机制自动回收子进程、避免僵尸进程;实现端口复用、异常容错与资源自动释放【tcp+多进程】
2.手动解析 HTTP 请求行、请求方法(GET/POST)、URL 路径与查询参数;实现 POST 表单数据提取、URL 解码(兼容中文与特殊字符);自主构造标准 HTTP 响应头,【http】
3.基于 SQLite 嵌入式数据库,通过原生 C 接口封装独立的数据访问层;实现用户表自动初始化、账号注册 / 登录校验;【sqlite3】
4.手写 HTML+CSS 实现登录、注册、商品列表、商品详情全套前端页面,采用卡片式布局与基础交互优化;
覆盖了系统编程、网络编程、数据库开发、前端页面、工程构建全链路技术栈,没有依赖任何重型 Web 框架
1.服务器的初始化

cs
#include "shop.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <signal.h>
#include <sys/wait.h>
// 子进程回收,避免僵尸进程
void sig_child(int signo) {
while (waitpid(-1, NULL, WNOHANG) > 0);
}
int main() {
// 1. 初始化数据库
if (db_init(DB_PATH) != 0) {
printf("数据库打开失败,请检查 shop.db 文件路径\n");
return 1;
}
// 2. 创建Socket
int server_fd = socket(AF_INET, SOCK_STREAM, 0);
if (server_fd < 0) {
perror("socket 创建失败");
return 1;
}
// 端口复用
int opt = 1;
setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
// 绑定地址端口
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(PORT);
addr.sin_addr.s_addr = INADDR_ANY;
if (bind(server_fd, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
perror("端口绑定失败");
return 1;
}
// 开始监听
listen(server_fd, 128);
printf("===== 商品查询服务启动 =====\n");
printf("端口号: %d\n", PORT);
printf("浏览器访问: http://localhost:%d\n", PORT);
printf("============================\n");
// 注册信号处理
signal(SIGCHLD, sig_child);
// 3. 循环接收客户端请求
while (1) {
int client_fd = accept(server_fd, NULL, NULL);
if (client_fd < 0) {
perror("accept 出错");
continue;
}
// 多进程并发:每个请求fork一个子进程处理
pid_t pid = fork();
if (pid == 0) {
close(server_fd); // 子进程关闭监听套接字
handle_request(client_fd); // 处理请求
exit(0);
} else if (pid > 0) {
close(client_fd); // 父进程关闭客户端套接字
}
}
db_close();
close(server_fd);
return 0;
}
主要main函数的调

cs
#include "shop.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sqlite3.h>
static sqlite3 *db = NULL;
/************************ 工具函数 ************************/
static void send_header(int fd, int code, const char *type, int len) {
char buf[512];
sprintf(buf, "HTTP/1.1 %d OK\r\nContent-Type: %s\r\nContent-Length: %d\r\n\r\n",
code, type, len);
send(fd, buf, strlen(buf), 0);
}
static void send_file(int fd, const char *path, const char *type) {
int file_fd = open(path, O_RDONLY);
if (file_fd < 0) {
const char *err = "404 Not Found";
send_header(fd, 404, "text/plain", strlen(err));
send(fd, err, strlen(err), 0);
return;
}
struct stat st;
fstat(file_fd, &st);
send_header(fd, 200, type, st.st_size);
char buf[BUF_SIZE];
int n;
while ((n = read(file_fd, buf, BUF_SIZE)) > 0) {
send(fd, buf, n, 0);
}
close(file_fd);
}
static void url_decode(char *dst, const char *src) {
char *p = dst;
while (*src) {
if (*src == '%' && src[1] && src[2]) {
char hex[3] = {src[1], src[2], 0};
*p++ = strtol(hex, NULL, 16);
src += 3;
} else if (*src == '+') {
*p++ = ' ';
src++;
} else {
*p++ = *src++;
}
}
*p = 0;
}
static void parse_form(char *body, char *user, char *pass) {
char *p = strstr(body, "user=");
if (p) {
p += 5;
char *end = strchr(p, '&');
if (!end) end = p + strlen(p);
strncpy(user, p, end - p);
user[end - p] = 0;
url_decode(user, user);
}
p = strstr(body, "passwd=");
if (p) {
p += 7;
char *end = strchr(p, '&');
if (!end) end = p + strlen(p);
strncpy(pass, p, end - p);
pass[end - p] = 0;
url_decode(pass, pass);
}
}
/************************ 数据库操作 ************************/
int db_init(const char *db_path) {
if (sqlite3_open(db_path, &db) != SQLITE_OK) {
return -1;
}
// 程序启动自动创建用户表,无需手动建
const char *create_sql =
"CREATE TABLE IF NOT EXISTS user ("
"id INTEGER PRIMARY KEY AUTOINCREMENT,"
"username TEXT UNIQUE NOT NULL,"
"password TEXT NOT NULL);";
char *err = NULL;
sqlite3_exec(db, create_sql, NULL, NULL, &err);
if (err) sqlite3_free(err);
return 0;
}
void db_close() {
if (db) sqlite3_close(db);
}
int db_register(const char *username, const char *password) {
char sql[256];
sprintf(sql, "INSERT INTO user(username,password) VALUES('%s','%s')",
username, password);
char *err = NULL;
int rc = sqlite3_exec(db, sql, NULL, NULL, &err);
if (rc != SQLITE_OK) {
sqlite3_free(err);
return -1;
}
return 0;
}
static int login_callback(void *arg, int col, char **val, char **name) {
int *flag = (int*)arg;
*flag = 1;
return 0;
}
int db_login(const char *username, const char *password) {
char sql[256];
int found = 0;
sprintf(sql, "SELECT id FROM user WHERE username='%s' AND password='%s'",
username, password);
sqlite3_exec(db, sql, login_callback, &found, NULL);
return found ? 0 : -1;
}
typedef struct {
Goods *list;
int idx;
} GoodsListArg;
static int goods_list_callback(void *arg, int col, char **val, char **name) {
GoodsListArg *argp = (GoodsListArg*)arg;
int i = argp->idx;
argp->list[i].goods_id = atoi(val[0]);
argp->list[i].cat_id = atoi(val[1]);
strncpy(argp->list[i].goods_name, val[2] ? val[2] : "", 127);
strncpy(argp->list[i].good_spec, val[3] ? val[3] : "", 127);
argp->list[i].shop_price = atof(val[4]);
strncpy(argp->list[i].goods_img, val[5] ? val[5] : "", 127);
argp->idx++;
return 0;
}
Goods* db_search_goods(const char *keyword, int *count) {
char sql[512];
sqlite3_stmt *stmt;
*count = 0;
sprintf(sql, "SELECT COUNT(*) FROM goods WHERE goods_name LIKE '%%%s%%' OR keywords LIKE '%%%s%%'",
keyword, keyword);
sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
if (sqlite3_step(stmt) == SQLITE_ROW) {
*count = sqlite3_column_int(stmt, 0);
}
sqlite3_finalize(stmt);
if (*count == 0) return NULL;
Goods *list = malloc(sizeof(Goods) * (*count));
GoodsListArg arg = {list, 0};
sprintf(sql, "SELECT goods_id,cat_id,goods_name,good_spec,shop_price,goods_img "
"FROM goods WHERE goods_name LIKE '%%%s%%' OR keywords LIKE '%%%s%%'",
keyword, keyword);
sqlite3_exec(db, sql, goods_list_callback, &arg, NULL);
return list;
}
Goods* db_get_goods_by_id(int goods_id) {
char sql[256];
sprintf(sql, "SELECT goods_id,cat_id,goods_name,good_spec,shop_price,goods_img,"
"goods_desc,goods_number,keywords FROM goods WHERE goods_id=%d", goods_id);
sqlite3_stmt *stmt;
Goods *g = malloc(sizeof(Goods));
memset(g, 0, sizeof(Goods));
if (sqlite3_prepare_v2(db, sql, -1, &stmt, NULL) != SQLITE_OK) {
free(g);
return NULL;
}
if (sqlite3_step(stmt) == SQLITE_ROW) {
g->goods_id = sqlite3_column_int(stmt, 0);
g->cat_id = sqlite3_column_int(stmt, 1);
strncpy(g->goods_name, (char*)sqlite3_column_text(stmt, 2), 127);
strncpy(g->good_spec, (char*)sqlite3_column_text(stmt, 3), 127);
g->shop_price = sqlite3_column_double(stmt, 4);
strncpy(g->goods_img, (char*)sqlite3_column_text(stmt, 5), 127);
strncpy(g->goods_desc, (char*)sqlite3_column_text(stmt, 6), 255);
g->goods_number = sqlite3_column_int(stmt, 7);
strncpy(g->keywords, (char*)sqlite3_column_text(stmt, 8), 127);
sqlite3_finalize(stmt);
return g;
}
sqlite3_finalize(stmt);
free(g);
return NULL;
}
/************************ 页面渲染 ************************/
static void render_goods_list(int fd, Goods *list, int count) {
char html[8192];
int offset = 0;
offset += sprintf(html + offset,
"<!DOCTYPE html><html><head><meta charset='utf-8'>"
"<title>商品列表</title><style>"
"*{margin:0;padding:0;box-sizing:border-box;}"
".header{padding:20px;background:#f5f5f5;border-bottom:1px solid #eee;}"
".search-form{width:600px;margin:0 auto;}"
".search-form input[type=text]{width:400px;padding:8px 12px;border:1px solid #ddd;border-radius:4px;}"
".search-form input[type=submit]{padding:8px 20px;background:#007bff;color:white;border:none;border-radius:4px;cursor:pointer;}"
".goods-list{width:1100px;margin:30px auto;overflow:hidden;}"
".goods-item{float:left;width:200px;margin:10px;border:1px solid #eee;border-radius:6px;padding:10px;text-align:center;transition:0.3s;}"
".goods-item:hover{box-shadow:0 2px 10px rgba(0,0,0,0.1);}"
".goods-item img{width:150px;height:150px;object-fit:contain;margin-bottom:8px;}"
".goods-name{font-size:14px;color:#333;height:40px;overflow:hidden;}"
".goods-price{color:#e4393c;font-size:16px;font-weight:bold;margin-top:8px;}"
"a{text-decoration:none;color:inherit;}"
"</style></head><body>");
offset += sprintf(html + offset,
"<div class='header'>"
"<form class='search-form' method='get' action='/search'>"
"<input type='text' name='keyword' placeholder='输入商品名称或关键词搜索...'>"
"<input type='submit' value='搜索'>"
"</form></div><div class='goods-list'>");
if (count == 0 || !list) {
offset += sprintf(html + offset, "<p style='text-align:center;width:100%%;padding:50px;color:#999;'>未找到相关商品</p>");
} else {
for (int i = 0; i < count; i++) {
offset += sprintf(html + offset,
"<div class='goods-item'>"
"<a href='/detail?goods_id=%d'>"
"<img src='/jpg/%s' alt='%s'>"
"<div class='goods-name'>%s</div>"
"<div class='goods-price'>¥%.2f</div>"
"</a></div>",
list[i].goods_id, list[i].goods_img, list[i].goods_name,
list[i].goods_name, list[i].shop_price);
}
}
offset += sprintf(html + offset, "</div></body></html>");
send_header(fd, 200, "text/html; charset=utf-8", offset);
send(fd, html, offset, 0);
}
static void render_detail(int fd, int goods_id) {
Goods *g = db_get_goods_by_id(goods_id);
if (!g) {
const char *err = "商品不存在";
send_header(fd, 404, "text/plain", strlen(err));
send(fd, err, strlen(err), 0);
return;
}
char html[4096];
int len = sprintf(html,
"<!DOCTYPE html><html><head><meta charset='utf-8'>"
"<title>%s - 商品详情</title><style>"
"*{margin:0;padding:0;box-sizing:border-box;}"
".container{width:900px;margin:50px auto;overflow:hidden;}"
".img-box{float:left;width:350px;padding:20px;border:1px solid #eee;border-radius:6px;}"
".img-box img{width:100%%;display:block;}"
".info-box{float:right;width:500px;padding:20px;}"
".info-box h2{font-size:22px;margin-bottom:20px;color:#333;}"
".info-item{margin:15px 0;font-size:15px;color:#666;}"
".price{color:#e4393c;font-size:28px;font-weight:bold;}"
".back-btn{display:inline-block;margin-top:30px;padding:10px 25px;background:#007bff;color:white;border-radius:4px;text-decoration:none;}"
"</style></head><body>"
"<div class='container'>"
"<div class='img-box'><img src='/jpg/%s'></div>"
"<div class='info-box'>"
"<h2>%s</h2>"
"<div class='info-item price'>¥%.2f</div>"
"<div class='info-item'>规格:%s</div>"
"<div class='info-item'>库存:%d 件</div>"
"<div class='info-item'>关键词:%s</div>"
"<div class='info-item'>商品描述:%s</div>"
"<a href='/index.html' class='back-btn'>返回商品列表</a>"
"</div></div></body></html>",
g->goods_name, g->goods_img, g->goods_name,
g->shop_price, g->good_spec, g->goods_number,
g->keywords, g->goods_desc);
send_header(fd, 200, "text/html; charset=utf-8", len);
send(fd, html, len, 0);
free(g);
}
/************************ 请求路由处理 ************************/
void handle_request(int client_fd) {
char buf[BUF_SIZE];
int n = recv(client_fd, buf, BUF_SIZE - 1, 0);
if (n <= 0) {
close(client_fd);
return;
}
buf[n] = 0;
char method[16], path[256];
sscanf(buf, "%s %s", method, path);
// 1. 静态页面
if (strcmp(path, "/") == 0 || strcmp(path, "/login.html") == 0) {
send_file(client_fd, STATIC_PATH"/login.html", "text/html; charset=utf-8");
}
else if (strcmp(path, "/register.html") == 0) {
send_file(client_fd, STATIC_PATH"/register.html", "text/html; charset=utf-8");
}
// 2. 商品图片
else if (strncmp(path, "/jpg/", 5) == 0) {
char file_path[256];
sprintf(file_path, "%s/%s", IMG_PATH, path + 5);
send_file(client_fd, file_path, "image/jpeg");
}
// 3. 首页商品列表
else if (strcmp(path, "/index.html") == 0) {
int count = 0;
Goods *list = db_search_goods("", &count);
if (count > 20) count = 20;
render_goods_list(client_fd, list, count);
free(list);
}
// 4. 登录处理
else if (strcmp(path, "/login") == 0) {
char user[64] = {0}, pass[64] = {0};
char *body = strstr(buf, "\r\n\r\n");
if (body) parse_form(body + 4, user, pass);
if (db_login(user, pass) == 0) {
const char *redirect = "HTTP/1.1 302 Found\r\nLocation: /index.html\r\n\r\n";
send(client_fd, redirect, strlen(redirect), 0);
} else {
const char *err =
"<!DOCTYPE html><html><head><meta charset='utf-8'></head><body>"
"<p style='text-align:center;margin-top:100px;'>用户名或密码错误</p>"
"<p style='text-align:center;'><a href='/login.html'>返回登录</a></p>"
"</body></html>";
send_header(client_fd, 200, "text/html; charset=utf-8", strlen(err));
send(client_fd, err, strlen(err), 0);
}
}
// 5. 注册处理
else if (strcmp(path, "/register") == 0) {
char user[64] = {0}, pass[64] = {0};
char *body = strstr(buf, "\r\n\r\n");
if (body) parse_form(body + 4, user, pass);
if (db_register(user, pass) == 0) {
const char *ok =
"<!DOCTYPE html><html><head><meta charset='utf-8'></head><body>"
"<p style='text-align:center;margin-top:100px;'>注册成功!</p>"
"<p style='text-align:center;'><a href='/login.html'>去登录</a></p>"
"</body></html>";
send_header(client_fd, 200, "text/html; charset=utf-8", strlen(ok));
send(client_fd, ok, strlen(ok), 0);
} else {
const char *err =
"<!DOCTYPE html><html><head><meta charset='utf-8'></head><body>"
"<p style='text-align:center;margin-top:100px;'>注册失败(用户名可能已存在)</p>"
"<p style='text-align:center;'><a href='/register.html'>返回注册</a></p>"
"</body></html>";
send_header(client_fd, 200, "text/html; charset=utf-8", strlen(err));
send(client_fd, err, strlen(err), 0);
}
}
// 6. 搜索功能
else if (strncmp(path, "/search?", 8) == 0) {
char keyword[128] = {0};
char *p = strstr(path, "keyword=");
if (p) {
p += 8;
char *end = strchr(p, '&');
if (!end) end = p + strlen(p);
strncpy(keyword, p, end - p);
keyword[end - p] = 0;
url_decode(keyword, keyword);
}
int count = 0;
Goods *list = db_search_goods(keyword, &count);
render_goods_list(client_fd, list, count);
free(list);
}
// 7. 商品详情
else if (strncmp(path, "/detail?", 8) == 0) {
int goods_id = 0;
char *p = strstr(path, "goods_id=");
if (p) goods_id = atoi(p + 9);
render_detail(client_fd, goods_id);
}
// 8. 404
else {
const char *err = "404 Not Found";
send_header(client_fd, 404, "text/plain", strlen(err));
send(client_fd, err, strlen(err), 0);
}
close(client_fd);
}
由此可以实现一个全栈的socket以及多进程的实现,商品的查找与html