/*
* @copyright: copyright (c) 2025 Chengdu TP-Link Technologies Co.Ltd.
* @fileName: server.c
* @description: 服务器功能实现文件
* @Author: Wang Zhiheng
* @email: wangzhiheng@tp-link.com.hk
* @version: 1.0.0
* @Date: 2025-08-11
* @history: \arg 1.0.0, 25Aug11, Wang Zhiheng, 创建文件
*/
/**************************************************************************************************/
/* INCLUDE FILES */
/**************************************************************************************************/
#include "server.h"
#include <signal.h>
#include <sys/wait.h>
/**************************************************************************************************/
/* LOCAL_FUNCTIONS */
/**************************************************************************************************/
/**
* @function: void recv_http_request(int client_fd)
* @description: 接收HTTP请求并处理
* @param client_fd: 客户端连接socket文件描述符
*/
void recv_http_request(int client_fd)
{
char buf[4096] = {0}; /* 接收客户端请求数据的缓冲区 */
int len = recv(client_fd, buf, sizeof(buf), 0); /* 接收数据 */
if (len <= 0)
{
printf("客户端断开连接\n");
close(client_fd);
return;
}
/* 解析并处理HTTP请求 */
parse_request(buf, client_fd);
}
/**
* @function: int parse_request(const char *message, int conn_fd)
* @description: 解析HTTP请求并路由处理
* @param message: HTTP请求消息
* @param conn_fd: 客户端连接socket文件描述符
* @return: 0成功, -1失败
*/
int parse_request(const char *message, int conn_fd)
{
char method[12], path[1024], version[1024]; /* 存储解析结果 */
sscanf(message, "%s %s %s", method, path, version); /* 解析请求行 */
/* 根据请求方法路由处理 */
if (strcasecmp(method, "GET") == 0)
{
return handle_get_request(conn_fd, path);
}
else if (strcasecmp(method, "POST") == 0)
{
return handle_post_request(conn_fd, path, message);
}
return -1; /* 不支持的请求方法 */
}
/**
* @function: int handle_get_request(int conn_fd, const char *path)
* @description: 处理GET请求
* @param conn_fd: 客户端连接socket文件描述符
* @param path: 请求路径
* @return: 0成功, -1失败
*/
int handle_get_request(int conn_fd, const char *path)
{
char *filename = NULL; /* 要发送的文件名 */
/* 处理根路径请求 */
if (strcmp(path, "/") == 0)
{
filename = "About.html";
}
/* 处理文件资源请求 */
else
{
filename = (char *)(path + 1); /* 跳过路径开头的'/' */
}
/* 发送HTTP响应 */
send_http_response(conn_fd, filename);
return 0;
}
/**
* @function: int handle_post_request(int conn_fd, const char *path, const char *message)
* @description: 处理POST请求
* @param conn_fd: 客户端连接socket文件描述符
* @param path: 请求路径
* @param message: 完整HTTP请求消息
* @return: 0成功, -1失败
*/
int handle_post_request(int conn_fd, const char *path, const char *message)
{
/* 处理联系表单提交 */
if (strcmp(path, "/data/contact.json") == 0)
{
const char *body_start = strstr(message, "\r\n\r\n"); /* 定位请求体开始位置 */
if (!body_start)
{
send_error_response(conn_fd, 400, "请求体缺失");
return -1;
}
body_start += 4; /* 跳过空行 */
process_contact_form(conn_fd, body_start); /* 处理表单数据 */
return 0;
}
/* 其他POST路径返回404 */
send_error_response(conn_fd, 404, "不支持的路径");
return -1;
}
/**
* @function: void process_contact_form(int conn_fd, const char *body)
* @description: 处理联系表单数据
* @param conn_fd: 客户端连接socket文件描述符
* @param body: 请求体内容
*/
void process_contact_form(int conn_fd, const char *body)
{
char *name = NULL, *email = NULL, *message_content = NULL; /* 表单字段 */
char *token = strtok((char *)body, "&"); /* 分割表单数据 */
/* 解析表单字段 */
while (token)
{
if (strstr(token, "name=") == token)
{
name = url_decode(token + 5); /* 解码姓名字段 */
}
else if (strstr(token, "email=") == token)
{
email = url_decode(token + 6); /* 解码邮箱字段 */
}
else if (strstr(token, "message=") == token)
{
message_content = url_decode(token + 8); /* 解码留言字段 */
}
token = strtok(NULL, "&"); /* 继续分割 */
}
/* 打印表单数据 */
printf("收到联系表单数据:\n姓名: %s\n邮箱: %s\n留言: %s\n",
name, email, message_content);
/* 释放解码内存 */
if (name) free(name);
if (email) free(email);
if (message_content) free(message_content);
/* 发送成功响应 */
const char *response =
"HTTP/1.1 200 OK\r\n"
"Content-Type: application/json\r\n"
"Content-Length: 27\r\n"
"Connection: close\r\n"
"\r\n"
"{\"callback\":\"提交成功\"}";
send(conn_fd, response, strlen(response), 0);
}
/**
* @function: void send_http_response(int sockfd, const char* file_path)
* @description: 发送HTTP响应报文
* @param sockfd: 客户端连接socket文件描述符
* @param file_path: 需要发送的文件路径
*/
void send_http_response(int sockfd, const char *file_path)
{
struct stat file_stat; /* 文件状态结构 */
char buffer[(int)pow(2, 17)]; /* 文件读写缓冲区 */
ssize_t bytes_read; /* 读取字节数 */
/* 获取文件状态 */
if (stat(file_path, &file_stat) != 0)
{
/* 文件不存在或无法获取状态 */
const char *response_404 =
"HTTP/1.1 404 Not Found\r\n"
"Content-Type: text/html\r\n"
"Content-Length: 133\r\n"
"Connection: close\r\n"
"\r\n"
"<html><body><h1>404 Not Found</h1><p>The requested file was not found on this server.</p></body></html>";
send(sockfd, response_404, strlen(response_404), 0);
close(sockfd);
return;
}
/* 打开文件 */
int fd = open(file_path, O_RDONLY);
if (fd == -1)
{
/* 打开文件失败 */
const char *response_500 =
"HTTP/1.1 500 Internal Server Error\r\n"
"Content-Type: text/html\r\n"
"Content-Length: 151\r\n"
"Connection: close\r\n"
"\r\n"
"<html><body><h1>500 Internal Server Error</h1><p>Failed to open the requested file.</p></body></html>";
send(sockfd, response_500, strlen(response_500), 0);
close(sockfd);
return;
}
printf("打开文件%s\n", file_path);
const char *mime_type = get_mime_type(file_path); /* 获取MIME类型 */
/* 发送HTTP响应头 */
char response_header[2048]; /* 响应头缓冲区 */
snprintf(response_header, sizeof(response_header),
"HTTP/1.1 200 OK\r\n"
"Content-Type: %s\r\n"
"Content-Length: %ld\r\n"
"Connection: keep-alive\r\n"
"\r\n",
mime_type, file_stat.st_size);
send(sockfd, response_header, strlen(response_header), 0);
/* 循环读取并发送文件内容 */
while ((bytes_read = read(fd, buffer, sizeof(buffer))) > 0)
{
ssize_t sent = send(sockfd, buffer, bytes_read, 0);
if (sent < 0)
{
perror("发送失败");
break;
}
}
close(fd); /* 关闭文件描述符 */
}
/**
* @function: void send_error_response(int conn_fd, int code, const char *msg)
* @description: 发送错误响应
* @param conn_fd: 客户端连接socket文件描述符
* @param code: HTTP状态码
* @param msg: 错误信息
*/
void send_error_response(int conn_fd, int code, const char *msg)
{
char response[512]; /* 响应缓冲区 */
const char *status = ""; /* 状态描述 */
/* 设置状态码描述 */
switch(code)
{
case 400: status = "400 Bad Request"; break;
case 404: status = "404 Not Found"; break;
case 500: status = "500 Internal Server Error"; break;
}
/* 构造JSON错误响应 */
snprintf(response, sizeof(response),
"HTTP/1.1 %s\r\n"
"Content-Type: application/json\r\n"
"Content-Length: %d\r\n"
"Connection: close\r\n"
"\r\n"
"{\"error\":\"%s\"}",
status, (int)strlen(msg) + 10, msg);
send(conn_fd, response, strlen(response), 0);
}
/**
* @function: char *url_decode(const char *src)
* @description: URL解码
* @param src: 编码字符串
* @return: 解码后字符串(需调用者释放)
*/
char *url_decode(const char *src)
{
if (!src) return NULL;
size_t src_len = strlen(src);
char *decoded = malloc(src_len + 1); /* 分配解码缓冲区 */
char *dst = decoded;
while (*src)
{
if (*src == '+')
{
*dst++ = ' '; /* 将+转换为空格 */
src++;
}
else if (*src == '%' && isxdigit(src[1]) && isxdigit(src[2]))
{
char hex[3] = {src[1], src[2], '\0'}; /* 提取十六进制值 */
*dst++ = (char)strtol(hex, NULL, 16); /* 转换为字符 */
src += 3;
}
else
{
*dst++ = *src++; /* 直接复制字符 */
}
}
*dst = '\0'; /* 字符串结束符 */
return decoded;
}
/**
* @function: const char* get_mime_type(const char* filename)
* @description: 获取文件MIME类型
* @param filename: 文件名
* @return: MIME类型字符串
*/
const char *get_mime_type(const char *filename)
{
const char *dot = NULL; /* 文件扩展名位置 */
dot = strrchr(filename, '.'); /* 查找最后一个点 */
/* 根据文件后缀名返回对应MIME类型 */
if (dot == NULL)
return "application/octet-stream";
if (strcmp(dot, ".html") == 0 || strcmp(dot, ".htm") == 0)
return "text/html";
else if (strcmp(dot, ".css") == 0)
return "text/css";
else if (strcmp(dot, ".png") == 0 || strcmp(dot, ".jpg") == 0 || strcmp(dot, ".jpeg") == 0)
return "image/jpeg";
return "application/octet-stream"; /* 默认类型 */
}
/**************************************************************************************************/
/* PUBLIC_FUNCTIONS */
/**************************************************************************************************/
/**
* @function: int init_listen_fd(unsigned short port)
* @description: 创建并初始化监听socket
* @param port: 监听端口号
* @return: 监听socket文件描述符
*/
int init_listen_fd(unsigned short port)
{
int listen_sockfd; /* 监听socket */
struct sockaddr_in listen_addr; /* 监听地址 */
memset(&listen_addr, 0, sizeof(listen_addr)); /* 初始化地址结构 */
int temp_result; /* 临时结果变量 */
/* 创建监听socket */
listen_sockfd = socket(AF_INET, SOCK_STREAM, 0);
handle_error("socket", listen_sockfd);
/* 设置端口复用 */
int opt = 1; /* SO_REUSEADDR选项值 */
int ret = setsockopt(listen_sockfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
handle_error("setsockopt", ret);
/* 绑定端口和IP */
listen_addr.sin_family = AF_INET;
listen_addr.sin_addr.s_addr = INADDR_ANY;
listen_addr.sin_port = htons(port);
/* 绑定地址 */
temp_result = bind(listen_sockfd, (struct sockaddr *)&listen_addr, sizeof(listen_addr));
handle_error("bind", temp_result);
printf("绑定成功\n");
/* 进入监听模式 */
if (listen(listen_sockfd, 128) < 0)
{
perror("监听失败");
close(listen_sockfd);
exit(EXIT_FAILURE);
}
printf("监听成功\n");
return listen_sockfd;
}
/**
* @function: int processes_run(int listen_sockfd)
* @description: 启动多进程处理模型
* @param listen_sockfd:监听socket文件描述符
* @return: 0成功, -1失败
*/
int processes_run(int listen_sockfd)
{
struct sockaddr_in client_addr; /* 客户端地址 */
memset(&client_addr, 0, sizeof(client_addr)); /* 初始化地址结构 */
socklen_t client_addr_len = sizeof(client_addr); /* 地址长度 */
/* 忽略SIGCHLD信号,自动回收子进程 */
signal(SIGCHLD, SIG_IGN);
while (1)
{
/* 接受客户端连接 */
int client_fd = accept(listen_sockfd, (struct sockaddr *)&client_addr, &client_addr_len);
if (client_fd < 0)
{
perror("接受连接失败");
continue;
}
printf("客户端连接: IP:%s 端口:%d socket:%d\n",
inet_ntoa(client_addr.sin_addr),
ntohs(client_addr.sin_port),
client_fd);
/* 创建子进程处理请求 */
pid_t pid = fork();
if (pid == -1)
{
perror("创建进程失败");
close(client_fd);
continue;
}
/* 子进程处理请求 */
if (pid == 0)
{
close(listen_sockfd); /* 子进程关闭监听socket */
recv_http_request(client_fd); /* 处理HTTP请求 */
close(client_fd); /* 关闭客户端socket */
exit(EXIT_SUCCESS); /* 子进程退出 */
}
/* 父进程继续监听 */
else
{
close(client_fd); /* 父进程关闭客户端socket */
}
}
close(listen_sockfd);
return 0;
}
/*
* @copyright: copyright (c) 2025 Chengdu TP-Link Technologies Co.Ltd.
* @fileName: server.h
* @description: function declaration file of server.c
* @Author: Wang Zhiheng
* @email: wangzhiheng@tp-link.com.hk
* @version: 1.0.0
* @Date: 2025-08-11
* @history: \arg 1.0.0, 25Aug11, Wang Zhiheng, Create the file
*/
/**************************************************************************************************/
/* INCLUDE FILES */
/**************************************************************************************************/
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include "server.h"
/**************************************************************************************************/
/* LOCAL_FUNCTIONS */
/**************************************************************************************************/
int main(int argc, char const *argv[])
{
/* 参数检查 */
if(argc < 3)
{
printf("用法: %s 端口 工作目录\n", argv[0]);
return -1;
}
/* 解析端口号 */
unsigned short port = atoi(argv[1]); /* 字符串转整数 */
/* 切换到工作目录 */
if (chdir(argv[2]) != 0)
{
perror("切换工作目录失败");
return -1;
}
printf("端口号: %d\n工作目录: %s\n", port, argv[2]);
/* 初始化监听socket */
int listen_fd = init_listen_fd(port);
printf("监听文件描述符: %d\n", listen_fd);
/* 启动多进程服务器 */
processes_run(listen_fd);
return 0;
}
以上是我的webserver实现,请帮我撰写一个简单报告,包括其主要功能,主要函数设计
最新发布