185 lines
5.1 KiB
C
185 lines
5.1 KiB
C
/**
|
||
* @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);
|
||
}
|