/** * @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 #include #include #include #include #include #include #include #include #include #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); }