#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "utils.h"
// 创建监听 socket
int create_listen_socket(int port)
{
int sockfd;
struct sockaddr_in addr;
int opt = 1;
if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
{
perror("socket");
exit(EXIT_FAILURE);
}
setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = INADDR_ANY;
addr.sin_port = htons(port);
if (bind(sockfd, (struct sockaddr *)&addr, sizeof(addr)) < 0)
{
perror("bind");
close(sockfd);
exit(EXIT_FAILURE);
}
if (listen(sockfd, SOMAXCONN) < 0)
{
perror("listen");
close(sockfd);
exit(EXIT_FAILURE);
}
return sockfd;
}
void log_debug(const char *message)
{
printf("[DEBUG] %s\n", message);
}
void log_error(const char *message)
{
fprintf(stderr, "[ERROR] %s\n", message);
}第一个文件,#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/sendfile.h>
#include <libgen.h>
#include <sys/stat.h>
#include <sys/select.h>
#include <stdlib.h>
#include <ctype.h>
#include "http_parser.h"
// 获取 MIME 类型
const char *get_mime_type(const char *path)
{
const char *ext = strrchr(path, '.');
if (!ext)
return "text/plain";
ext++;
if (strcasecmp(ext, "html") == 0)
return "text/html";
if (strcasecmp(ext, "css") == 0)
return "text/css";
if (strcasecmp(ext, "js") == 0)
return "application/javascript";
if (strcasecmp(ext, "jpg") == 0 || strcasecmp(ext, "jpeg") == 0)
return "image/jpeg";
if (strcasecmp(ext, "png") == 0)
return "image/png";
if (strcasecmp(ext, "gif") == 0)
return "image/gif";
if (strcasecmp(ext, "ico") == 0)
return "image/x-icon";
return "application/octet-stream";
}
// 路径安全检查
int is_valid_path(const char *path)
{
if (strstr(path, "../") || strstr(path, "..\\"))
return 0;
return 1;
}
// 新增函数:读取完整的 HTTP 请求
int read_http_request(int fd, char *buffer, size_t size)
{
ssize_t total = 0;
ssize_t bytes_read;
while ((bytes_read = read(fd, buffer + total, size - total - 1)) > 0)
{
total += bytes_read;
buffer[total] = '\0';
if (strstr(buffer, "\r\n\r\n"))
{
// 请求头结束,检查是否还有 POST 数据
const char *content_length = strstr(buffer, "Content-Length:");
if (content_length)
{
int len = 0;
sscanf(content_length, "Content-Length: %d", &len);
size_t headers_end = strstr(buffer, "\r\n\r\n") - buffer + 4;
size_t remaining = headers_end + len - total;
if (remaining > 0)
{
while (remaining > 0 && (bytes_read = read(fd, buffer + total, remaining)) > 0)
{
total += bytes_read;
remaining -= bytes_read;
}
}
}
break;
}
ssize_t max = (ssize_t)size - 1;
if (total >= max)
{
return -1; // Buffer overflow
}
}
buffer[total] = '\0';
return total > 0 ? 0 : -1;
}
// 解析 HTTP 请求
int parse_http_request(const char *raw, http_request *req)
{
sscanf(raw, "%15s %255s %15s", req->method, req->path, req->protocol);
const char *header_start = strstr(raw, "\r\n") + 2;
const char *body_start = strstr(header_start, "\r\n\r\n");
if (body_start)
{
size_t header_len = body_start - header_start;
strncpy(req->headers, header_start, header_len < sizeof(req->headers) - 1 ? header_len : sizeof(req->headers) - 1);
body_start += 4;
strncpy(req->body, body_start, sizeof(req->body) - 1);
}
return 0;
}
// 处理请求
void process_request(http_request *req, int client_fd)
{
char file_path[1024];
if (!is_valid_path(req->path))
{
dprintf(client_fd, "HTTP/1.1 403 Forbidden\r\n\r\n");
return;
}
if (strcmp(req->path, "/") == 0)
{
snprintf(file_path, sizeof(file_path), "./static/Index.html");
}
else
{
snprintf(file_path, sizeof(file_path), "./static%s", req->path);
}
if (access(file_path, F_OK) == -1)
{
dprintf(client_fd, "HTTP/1.1 404 Not Found\r\n"
"Content-Type: text/html\r\n\r\n"
"<html><body><h1>404 Not Found</h1></body></html>");
return;
}
int fd = open(file_path, O_RDONLY);
if (fd < 0)
{
char err[256];
snprintf(err, sizeof(err), "HTTP/1.1 500 Internal Server Error\r\n"
"Content-Type: text/html\r\n\r\n"
"<html><body><h1>500 Internal Server Error</h1>"
"<p>Failed to open file: %s</p></body></html>",
strerror(errno));
ssize_t bytes_written = write(client_fd, err, strlen(err));
if (bytes_written < 0)
{
perror("write failed");
}
return;
}
struct stat st;
if (fstat(fd, &st) < 0)
{
close(fd);
dprintf(client_fd, "HTTP/1.1 500 Internal Server Error\r\n"
"Content-Type: text/html\r\n\r\n"
"<html><body><h1>500 Internal Server Error</h1>"
"<p>Failed to get file size</p></body></html>");
return;
}
const char *mime_type = get_mime_type(file_path);
dprintf(client_fd, "HTTP/1.1 200 OK\r\n"
"Content-Type: %s\r\n"
"Content-Length: %ld\r\n\r\n",
mime_type, st.st_size);
off_t offset = 0;
ssize_t sent = sendfile(client_fd, fd, &offset, st.st_size);
if (sent < 0 || sent != st.st_size)
{
char err[256];
snprintf(err, sizeof(err), "HTTP/1.1 500 Internal Server Error\r\n"
"Content-Type: text/html\r\n\r\n"
"<html><body><h1>500 Internal Server Error</h1>"
"<p>File send error: %s</p></body></html>",
strerror(errno));
ssize_t bytes_written = write(client_fd, err, strlen(err));
if (bytes_written < 0)
{
perror("write failed");
}
}
close(fd);
}
// 解析表单数据
// 解码 URL 编码的字符(如 %20 -> ' ')
void url_decode(char *src, char *dest, size_t dest_size)
{
char *d = dest;
char *end = dest + dest_size - 1;
while (*src && d < end)
{
if (*src == '+')
{
*d++ = ' ';
src++;
}
else if (*src == '%' && isxdigit(src[1]) && isxdigit(src[2]))
{
// 解码 %xx
char hex[3] = {src[1], src[2], '\0'};
*d++ = (char)strtol(hex, NULL, 16);
src += 3;
}
else
{
*d++ = *src++;
}
}
*d = '\0';
}
void parse_form_data(const char *body, char *name, char *email, char *message)
{
char *data = strdup(body);
if (!data)
return;
char *saveptr;
char *pair = strtok_r(data, "&", &saveptr);
while (pair)
{
char *key = strtok(pair, "=");
char *value = strtok(NULL, "=");
if (key && value)
{
char decoded[2048];
url_decode(value, decoded, sizeof(decoded));
if (strcmp(key, "name") == 0)
snprintf(name, 256, "%s", decoded);
else if (strcmp(key, "email") == 0)
snprintf(email, 256, "%s", decoded);
else if (strcmp(key, "message") == 0)
snprintf(message, 1024, "%s", decoded);
}
pair = strtok_r(NULL, "&", &saveptr);
}
free(data);
}
// 模拟处理联系表单并返回 JSON
void handle_contact_form(http_request *req, int client_fd)
{
char name[256] = {0};
char email[256] = {0};
char message[1024] = {0};
parse_form_data(req->body, name, email, message);
// 打印接收到的数据(可替换为写入文件或数据库)
printf("Name: %s\n", name);
printf("Email: %s\n", email);
printf("Message: %s\n", message);
// 模拟保存成功,返回 JSON 响应
dprintf(client_fd,
"HTTP/1.1 200 OK\r\n"
"Content-Type: application/json\r\n\r\n"
"{ \"callback\": \"Thank you for contacting us!\" }");
}
第二个文件,#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <semaphore.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/sendfile.h>
#include "utils.h"
#include "http_parser.h"
#define MAX_THREADS 100 /* 最大并发线程数 */
sem_t thread_semaphore; /* 用于限制最大线程数 */
/* 传递给线程函数的参数结构体 */
typedef struct
{
int client_fd; /* 客户端的socket描述符 */
struct sockaddr_in client_addr; /* 客户端地址信息 */
} thread_args;
/* 线程处理函数:处理客户端连接 */
static void *handle_client(void *arg)
{
thread_args *args = (thread_args *)arg;
char buffer[BUFFER_SIZE];
/* 从客户端socket中读取数据 */
ssize_t bytes_read = read(args->client_fd, buffer, BUFFER_SIZE - 1);
if (bytes_read <= 0)
{
if (bytes_read == 0)
{
printf("Client disconnected: %d\n", args->client_fd);
}
else
{
perror("read");
}
close(args->client_fd);
free(args);
sem_post(&thread_semaphore); // 释放信号量
return NULL;
}
buffer[bytes_read] = '\0'; // 添加字符串结束符
// 检查是否超过缓冲区
if (bytes_read >= BUFFER_SIZE - 1)
{
dprintf(args->client_fd, "HTTP/1.1 413 Payload Too Large\r\n\r\n");
close(args->client_fd);
free(args);
sem_post(&thread_semaphore);
return NULL;
}
http_request req; /* 定义http请求结构体 */
if (parse_http_request(buffer, &req) < 0)
{
dprintf(args->client_fd, "HTTP/1.1 400 Bad Request\r\n\r\n");
close(args->client_fd);
}
if (strcasecmp(req.method, "GET") == 0)
{
process_request(&req, args->client_fd);
}
else if (strcasecmp(req.method, "POST") == 0)
{
if (strcmp(req.path, "/data/contact.json") == 0)
{
handle_contact_form(&req, args->client_fd);
}
else
{
dprintf(args->client_fd, "HTTP/1.1 405 Method Not Allowed\r\n\r\n");
}
}
else
{
dprintf(args->client_fd, "HTTP/1.1 405 Method Not Allowed\r\n\r\n");
}
close(args->client_fd);
free(args);
sem_post(&thread_semaphore); // 释放信号量
return NULL;
}
int main(int argc, char *argv[])
{
int port = 80; // 默认端口
// 处理命令行参数
if (argc >= 2)
{
char *endptr;
long input_port = strtol(argv[1], &endptr, 10); // 安全转换
// 验证端口格式和范围
if (*endptr != '\0' || input_port < 1 || input_port > 65535)
{
fprintf(stderr, "错误:无效端口号 '%s',使用默认端口80\n", argv[1]);
}
else
{
port = (int)input_port;
}
}
// 初始化信号量
if (sem_init(&thread_semaphore, 0, MAX_THREADS) != 0)
{
perror("sem_init");
return EXIT_FAILURE;
}
int server_fd = create_listen_socket(port); /* 创建服务器socket并绑定到指定端口 */
/* 添加创建服务器socket失败的报错信息 */
if (server_fd < 0)
{
fprintf(stderr, "Failed to create listen socket\n");
return EXIT_FAILURE;
}
pthread_t thread_id; /* 用于创建线程 */
/* 持续监听客户端连接 */
while (1)
{
struct sockaddr_in client_addr; /* 客户端地址结构体 */
socklen_t addr_len = sizeof(client_addr);
int client_fd = accept(server_fd, (struct sockaddr *)&client_addr, &addr_len); /* 接受客户端连接 */
/* 添加接受客户端连接失败的信息 */
if (client_fd < 0)
{
perror("accept");
continue;
}
thread_args *args = malloc(sizeof(thread_args));
/* 添加分配内存失败的报错信息 */
if (!args)
{
perror("malloc");
close(client_fd);
continue;
}
args->client_fd = client_fd;
args->client_addr = client_addr;
/* 等待信号量,限制最大线程数 */
sem_wait(&thread_semaphore);
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
int result = pthread_create(&thread_id, &attr, handle_client, args);
if (result != 0)
{
perror("pthread_create");
free(args); /* 创建失败时释放内存 */
close(client_fd);
sem_post(&thread_semaphore); // 释放信号量
}
pthread_attr_destroy(&attr);
}
close(server_fd);
sem_destroy(&thread_semaphore);
return 0;
}
第三个文件,结合这三个文件,给出一个完整的日志输出机制,包含 DEBUG(调试信息)
INFO(正常请求记录)
WARN(异常情况)
ERROR(严重错误)
这几个等级,便于调式,输出的log文件名以包含main函数的文件名(去掉后缀)为准
最新发布