note/tech/simp_httpd.c
2025-11-19 10:16:05 +08:00

185 lines
5.1 KiB
C
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* @file simp_httpd.c
* @author maxwell@void
* @version 0.1
* @date 2024-07-11
*
* @copyright Copyright (c) 2024
* 功能:
* 1. 实现一个简单的HTTP服务器可以处理GET请求并返回指定文件的内容。
* 2. 支持命令行参数,可以指定端口号和目录。
* 3. 支持线程池,可以处理多个客户端连接。
* 4. 支持SIGPIPE信号可以忽略客户端断开连接时产生的错误。
*
* 编译gcc -o sim_httpd simp_httpd.c -lpthread
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <pthread.h>
#include <signal.h>
#define BUFFER_SIZE 1024
void handle_client(int client_socket);
void send_file(int client_socket, const char *filename);
// 客户端处理线程
void *client_thread(void *arg) {
int client_socket = *(int *)arg;
free(arg); // 释放分配的内存
handle_client(client_socket);
close(client_socket);
return NULL;
}
int main(int argc, char *argv[]) {
int server_socket, *client_socket;
struct sockaddr_in server_addr, client_addr;
socklen_t client_addr_len = sizeof(client_addr);
int port = 8080; // 默认端口
char *directory = "."; // 默认目录
// 解析命令行参数
int opt;
while ((opt = getopt(argc, argv, "p:d:")) != -1) {
switch (opt) {
case 'p':
port = atoi(optarg);
break;
case 'd':
directory = optarg;
break;
default:
fprintf(stderr, "Usage: %s [-p port] [-d directory]\n", argv[0]);
exit(EXIT_FAILURE);
}
}
// 改变当前工作目录
if (chdir(directory) != 0) {
perror("chdir failed");
exit(EXIT_FAILURE);
}
// 忽略 SIGPIPE 信号
signal(SIGPIPE, SIG_IGN);
// 创建服务器套接字
server_socket = socket(AF_INET, SOCK_STREAM, 0);
if (server_socket == -1) {
perror("Socket creation failed");
exit(EXIT_FAILURE);
}
// 设置服务器地址
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = INADDR_ANY;
server_addr.sin_port = htons(port);
// 绑定套接字到指定端口
if (bind(server_socket, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) {
perror("Bind failed");
close(server_socket);
exit(EXIT_FAILURE);
}
// 监听连接
if (listen(server_socket, 10) == -1) {
perror("Listen failed");
close(server_socket);
exit(EXIT_FAILURE);
}
printf("Server is listening on port %d, serving directory %s...\n", port, directory);
// 接受并处理客户端连接
while (1) {
client_socket = malloc(sizeof(int));
if (client_socket == NULL) {
perror("Malloc failed");
continue;
}
*client_socket = accept(server_socket, (struct sockaddr *)&client_addr, &client_addr_len);
if (*client_socket == -1) {
perror("Accept failed");
free(client_socket);
continue;
}
printf("Client connected: %s\n", inet_ntoa(client_addr.sin_addr));
pthread_t thread_id;
if (pthread_create(&thread_id, NULL, client_thread, client_socket) != 0) {
perror("Thread creation failed");
close(*client_socket);
free(client_socket);
} else {
pthread_detach(thread_id); // 分离线程
}
}
close(server_socket);
return 0;
}
void handle_client(int client_socket) {
char buffer[BUFFER_SIZE];
int received = recv(client_socket, buffer, BUFFER_SIZE - 1, 0);
if (received < 0) {
perror("Receive failed");
return;
}
buffer[received] = '\0';
printf("Request: %s\n", buffer);
// 简单解析HTTP GET请求
char method[16], path[256], protocol[16];
sscanf(buffer, "%s %s %s", method, path, protocol);
if (strcmp(method, "GET") == 0) {
// 去掉路径中的'/'
if (path[0] == '/') {
memmove(path, path + 1, strlen(path));
}
send_file(client_socket, path);
} else {
const char *response = "HTTP/1.1 405 Method Not Allowed\r\n\r\n";
send(client_socket, response, strlen(response), 0);
}
}
void send_file(int client_socket, const char *filename) {
FILE *file = fopen(filename, "rb");
if (file == NULL) {
const char *response = "HTTP/1.1 404 Not Found\r\n\r\n";
send(client_socket, response, strlen(response), 0);
return;
}
fseek(file, 0, SEEK_END);
long file_size = ftell(file);
fseek(file, 0, SEEK_SET);
char header[BUFFER_SIZE];
snprintf(header, BUFFER_SIZE, "HTTP/1.1 200 OK\r\nContent-Length: %ld\r\n\r\n", file_size);
send(client_socket, header, strlen(header), 0);
char file_buffer[BUFFER_SIZE];
size_t bytes_read;
while ((bytes_read = fread(file_buffer, 1, BUFFER_SIZE, file)) > 0) {
if (send(client_socket, file_buffer, bytes_read, 0) == -1) {
perror("Send failed");
break;
}
}
fclose(file);
}