sscanf,sprintf,fread,fwrite

本文详细介绍了sscanf、sprintf、fread及fwrite等函数的功能与用法。sscanf用于从固定字符串中读取格式化数据;sprintf则负责将格式化的数据写入字符串缓冲区;fread和fwrite分别用于从文件流中读取数据和向指定文件写入数据块。

sscanf 读取格式化的字符串中的数据

定义函数 int sscanf (const char *str,const char * format,........);     

sscanf与scanf类似,都是用于输入,子是后者以键盘(stdin)为输入源,前者以固定字符串为输入源。


sprintf 指的是字符串格式化命令,主要功能是把格式化的数据写入某个字符串缓冲区中。

定义函数 int sprintf( char *buffer, const char *format, [ argument] … );

sprintf 是个变参函数。使用sprintf 对于写入buffer的字符数是没有限制的,这就存在了buffer溢出的可能性。


fread

size_t fread ( void *buffer, size_t size, size_t count, FILE *stream) ;

fread 是一个函数,它从文件流中读数据,最多读取count个项,每个项size个字节,如果调用成功返回实际读取到的项个数(小于或等于count),如果不成功或读到文件末尾返回 0。


fwrite

size_t fwrite(const void* buffer, size_t size, size_t count, FILE* stream);

fwrite() 是 C 语言标准库中的一个文件处理函数,功能是向指定的文件中写入若干数据块,如成功执行则返回实际写入的数据块数目。该函数以二进制形式对文件进行操作,不局限于文本文件。

请优化下这段代码“#include <stdio.h>#include <stdlib.h>#include <string.h>#include <unistd.h>#include <sys/socket.h>#include <netinet/in.h>#include <pthread.h>/*监听端口*/#define PORT 80/*指定系统允许处于等待接受连接状态的连接的最大数量*/#define BACKLOG 10/*网站根目录*/#define ROOT_DIR "./source"// 获取文件类型const char *get_mime_type(const char *filename) { const char *ext = strrchr(filename, '.'); if (!ext) { return "application/octet-stream"; } if (strcmp(ext, ".html") == 0 || strcmp(ext, ".htm") == 0) { return "text/html"; } if (strcmp(ext, ".txt") == 0) { return "text/plain"; } if (strcmp(ext, ".css") == 0) { return "text/css"; } if (strcmp(ext, ".js") == 0) { return "application/javascript"; } if (strcmp(ext, ".jpg") == 0 || strcmp(ext, ".jpeg") == 0) { return "image/jpeg"; } if (strcmp(ext, ".png") == 0) { return "image/png"; } if (strcmp(ext, ".gif") == 0) { return "image/gif"; } if (strcmp(ext, ".pdf") == 0) { return "application/pdf"; } if (strcmp(ext, ".zip") == 0) { return "application/zip"; } return "application/octet-stream";}/*处理http请求*/void *handle_request(void *p_client_socket) { int client_socket = *((int *)p_client_socket); free(p_client_socket); char buffer[4096]; /*从代表客户端的文件中读取数据*/ int bytes_read = read(client_socket, buffer, sizeof(buffer) - 1); /*保证最后一个字符一定是\0,字符串不会溢出*/ buffer[bytes_read] = '\0'; /*从缓冲区读入:请求方法,请求路径,协议*/ char method[10], path[1024], protocol[10]; sscanf(buffer, "%s %s %s", method, path, protocol); /*响应头和响应内容*/ char response[4096]; char content[4096]; int content_length = 0; /*拼接完整路径,/属于默认情况,这里的主页为Index.html,需要注意*/ char full_path[1024]; sprintf(full_path, "%s%s", ROOT_DIR, strcmp(path, "/") == 0 ? "/Index.html" : path); /*处理请求 请求如果不是PUT和DELETE,要操作文件,权限只能是读*/ FILE *file = NULL; if (strcasecmp(method, "PUT") != 0 && strcasecmp(method, "DELETE") != 0) { file = fopen(full_path, "r"); } /*具体的对每个请求方法的处理策略*/ if (strcasecmp(method, "GET") == 0) { if (file) { /*计算文件长度*/ fseek(file, 0, SEEK_END); content_length = ftell(file); rewind(file); printf("GET file from %s\n",full_path); /*获取文件类型*/ const char *mime_type = get_mime_type(full_path); /*格式输出到响应头*/ sprintf(response, "HTTP/1.0 200 OK\r\nContent-Type: %s\r\nContent-Length: %d\r\n\r\n", mime_type, content_length); /*发送响应头*/ if (write(client_socket, response, strlen(response)) == -1) { perror("response_head"); close(client_socket); return; } /*循环保证内容发送完毕*/ while ((content_length = fread(content, 1, sizeof(content), file)) > 0) { if (write(client_socket, content, content_length) == -1) { perror("response_content"); close(client_socket); return; } } } else { /*未找到文件*/ char *error_message = "404 Not Found"; sprintf(response, "HTTP/1.0 404 Not Found\r\nContent-Length: %zu\r\n\r\n%s", strlen(error_message), error_message); /*发送响应头*/ if (write(client_socket, response, strlen(response)) == -1) { perror("response_head"); close(client_socket); return; } } } else if (strcasecmp(method, "HEAD") == 0) { if (file) { /*不读取文件计算文件长度的一种做法,搭配fseek和ftell*/ fseek(file, 0, SEEK_END); content_length = ftell(file); printf("HEAD %s\n", file); sprintf(response, "HTTP/1.0 200 OK\r\nContent-Type: text/html\r\nContent-Length: %d\r\n\r\n", content_length); } else { char *error_message = "404 Not Found"; sprintf(response, "HTTP/1.0 404 Not Found\r\nContent-Length: %zu\r\n\r\n", strlen(error_message)); } /*发送响应头*/ if (write(client_socket, response, strlen(response)) == -1) { perror("response_head"); close(client_socket); return; } } else if (strcasecmp(method, "PUT") == 0) { char *content_length_str = strstr(buffer, "Content-Length:"); if (content_length_str) { sscanf(content_length_str, "Content-Length: %d", &content_length); } int content_read = 0; /*PUT涉及到新建文件,所以要给写权限*/ file = fopen(full_path, "w"); if (file) { printf("PUT %s\n", file); /*从buffer中定位到请求体开始的位置*/ char *body = strstr(buffer, "\r\n\r\n") + 4; //把字符串开头偏移到文件内容开头,这也就是为什么要有一个空行分割请求头和请求体的意义 int body_length = bytes_read - (body - buffer); // 已经读取的请求体长度 if (body_length < content_length) { /*如果已读取的请求体长度小于Content-Length,继续读取 将已经在buffer中的请求体部分写入文件*/ fwrite(body, 1, body_length, file); /*计算还需从客户端读取的数据量*/ int to_read = content_length - body_length; /*还有数据需要读取时,进入循环*/ while (to_read > 0) { /*确定一次读取的数据量,不超过content数组的大小,也不超过剩余需要读取的数据量*/ int max_read_size = sizeof(content); if (max_read_size > to_read) { max_read_size = to_read; } /*从客户端读取数据*/ content_read = read(client_socket, content, max_read_size); /*如果从客户端读到了数据*/ if (content_read > 0) { /*将读取的数据写入文件*/ fwrite(content, 1, content_read, file); /*减少剩余需要读取的数据量*/ to_read -= content_read; } else { /*如果没有读到数据(content_read <= 0),则跳出循环 这可能是因为客户端已关闭连接,或出现读取错误*/ break; } } } else { /*已经读取的部分足够长,直接写入*/ fwrite(body, 1, content_length, file); } sprintf(response, "HTTP/1.0 201 Created\r\n\r\n"); } else { sprintf(response, "HTTP/1.0 500 Internal Server Error\r\n\r\n"); } /*发送响应头*/ if (write(client_socket, response, strlen(response)) == -1) { perror("response_head"); close(client_socket); return; } } else if (strcasecmp(method, "DELETE") == 0) { printf("DELETE"); if (remove(full_path) == 0) { sprintf(response, "HTTP/1.0 200 OK\r\n\r\n"); } else { sprintf(response, "HTTP/1.0 404 Not Found\r\n\r\n"); } /*发送响应头*/ if (write(client_socket, response, strlen(response)) == -1) { perror("response_head"); close(client_socket); return; } } else if (strcasecmp(method, "POST") == 0) { printf("POST\n"); /*解析content-length*/ char *content_length_str = strstr(buffer, "Content-Length:"); if (!content_length_str) { char *error_message = "HTTP/1.0 411 Length Required\r\n\r\n"; write(client_socket, error_message, strlen(error_message)); close(client_socket); return; } sscanf(content_length_str, "Content-Length: %d", &content_length); /*定位请求体起始位置*/ char *body_start = strstr(buffer, "\r\n\r\n"); if (!body_start) { char *error_message = "HTTP/1.0 400 Bad Request\r\n\r\n"; write(client_socket, error_message, strlen(error_message)); close(client_socket); return; } body_start += 4; /*跳过空行*/ /*读取剩余请求体数据*/ int body_read = bytes_read - (body_start - buffer); char *body_buffer = malloc(content_length + 1); memcpy(body_buffer, body_start, body_read); /*继续读取未完整接收的数据*/ int remaining = content_length - body_read; while (remaining > 0) { int n = read(client_socket, body_buffer + body_read, remaining); if (n <= 0) break; body_read += n; remaining -= n; } body_buffer[content_length] = '\0'; /*处理请求体(保存到user.txt)*/ FILE *post_file = fopen(full_path, "wb"); if (post_file) { fwrite(body_buffer, 1, content_length, post_file); fclose(post_file); sprintf(response, "HTTP/1.0 200 OK\r\n\r\nFile saved: %s", full_path); } else { printf("write fail"); sprintf(response, "HTTP/1.0 500 Internal Server Error\r\n\r\n"); } /*发送响应*/ write(client_socket, response, strlen(response)); free(body_buffer); } else { /*没实现的请求方法就返回没实现的状态码501*/ sprintf(response, "HTTP/1.0 501 Not Implemented\r\n\r\n"); /*发送响应头*/ if (write(client_socket, response, strlen(response)) == -1) { perror("response_head"); close(client_socket); return; } } if (file) { //这里统一关闭文件 fclose(file); }}int main() { /*创建服务器套接字*/ /*AF_INET表示IPv4,SOCK_STREAM表示TCP 创建用来TCP通信的socket,创建成功返回非负数 */ int server_socket = socket(AF_INET, SOCK_STREAM, 0); if (server_socket < 0) { perror("socket creation failed"); exit(EXIT_FAILURE); } /*设置SO_REUSEADDR选项,避免端口占用*/ int opt = 1; if (setsockopt(server_socket, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt))) { perror("setsockopt failed"); close(server_socket); exit(EXIT_FAILURE); } /*绑定地址和端口*/ /*struct sockaddr_in是通用的IPv4套接字地址结构,存储关于套接字的所有地址信息*/ struct sockaddr_in server_addr; memset(&server_addr, 0, sizeof(server_addr)); server_addr.sin_family = AF_INET; /*使用IPv4协议*/ 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)) < 0) { perror("bind failed"); close(server_socket); exit(EXIT_FAILURE); } /*开始监听*/ if (listen(server_socket, BACKLOG) < 0) { perror("listen failed"); close(server_socket); exit(EXIT_FAILURE); } int client_socket; struct sockaddr_in client_addr; socklen_t client_addr_len = sizeof(client_addr); printf("Server running on port %d, serving files from %s\n", PORT, ROOT_DIR); printf("Open http://localhost in your browser\n"); /*多线程并发*/ while (1) { /*客户端连接,通过对这个套接字的读写来接收和发送数据*/ struct sockaddr_in client_addr; socklen_t client_addr_len = sizeof(client_addr); int client_socket = accept(server_socket, (struct sockaddr *)&client_addr, &client_addr_len); if (client_socket < 0) { perror("accept failed"); continue; } pthread_t thread_id; int *pclient = malloc(sizeof(int)); *pclient = client_socket; //创建新线程 pthread_create(&thread_id, NULL, handle_request, pclient); //将线程设置为分离状态,执行完毕会自己回收所有资源 pthread_detach(thread_id); /*这里并没有使用互斥锁*/ } close(server_socket); return 0;}”中的多线程部分,在浏览其打开多个网页后程序可能会自动停止。
08-09
<think>我们之前已经实现了一个基本的HTTP服务器,可以处理POST请求并将数据存储到文件。现在用户遇到的问题是:当浏览器打开多个网页(即多个客户端同时连接)时,服务器程序可能会停止运行。这通常是因为并发处理不当导致的,例如资源竞争、线程或进程管理问题。 根据引用[3]中提到的,使用多进程时需要注意套接字的关闭(因为fork会复制文件描述符,所以父子进程都需要关闭套接字)。另外,引用[2]中展示了一个处理HTTP请求的函数`do_http_request`,但它是顺序执行的,没有并发能力。 为了解决多客户端并发访问的问题,我们可以采用多线程或多进程。但用户提到使用多线程优化,因此我们重点讨论多线程方案。 问题分析: 1. 当多个客户端同时连接时,如果主线程在接收连接后,为每个连接创建一个新线程来处理,那么主线程可以继续接受其他连接。 2. 但是,如果线程创建和销毁过于频繁,会导致性能下降;另外,如果线程没有正确分离,线程结束后资源没有回收,可能导致资源耗尽。 3. 另一个常见问题是,多个线程同时访问共享资源(如全局变量、文件等)时,如果没有同步机制,会导致数据竞争,甚至程序崩溃。 优化方案: 1. 使用线程池:预先创建一组线程,避免频繁创建和销毁线程的开销。 2. 对共享资源的访问进行同步(例如使用互斥锁)。 3. 注意线程的分离,避免需要显式回收线程(使用pthread_detach)。 然而,用户的问题描述是“浏览器打开多个网页后程序可能自动停止”,这可能是由于线程创建失败(达到系统限制)或某个线程崩溃导致整个进程退出。 因此,我们考虑以下步骤: 1. 将服务器改为多线程模式,主线程负责接受连接,然后将新的客户端套接字交给工作线程处理。 2. 使用线程池管理线程,避免频繁创建线程。 3. 确保每个线程正确处理自己的连接,并且不会干扰其他线程。 由于线程池的实现相对复杂,我们先实现一个简单的多线程版本(每次连接创建一个新线程),并确保线程安全,然后再考虑线程池。 简单多线程版本步骤: 1. 主线程循环接受客户端连接。 2. 对于每个连接,创建一个新线程,将客户端套接字传递给线程函数。 3. 在线程函数中处理HTTP请求,然后关闭套接字并退出线程。 注意:为了避免僵尸线程,我们可以将线程设置为可分离的(detached),这样线程结束后资源会自动回收。 代码调整: 首先,修改主循环,每次接受连接后创建线程: ```c #include <pthread.h> // 线程函数原型 void *handle_client(void *arg); int main() { // ... 之前的初始化代码(创建套接字、绑定、监听) while (1) { new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen); if (new_socket < 0) { perror("accept"); continue; } // 创建线程处理连接 pthread_t thread_id; int *client_sock = malloc(sizeof(int)); *client_sock = new_socket; if (pthread_create(&thread_id, NULL, handle_client, (void*)client_sock) != 0) { perror("pthread_create"); close(new_socket); free(client_sock); } else { // 设置线程为可分离,避免需要pthread_join pthread_detach(thread_id); } } // ... 关闭服务器套接字 return 0; } ``` 然后,实现线程函数`handle_client`,在其中调用处理HTTP请求的函数: ```c void *handle_client(void *arg) { int sock = *((int*)arg); free(arg); // 释放动态分配的内存 // 读取请求并处理 char buffer[BUFFER_SIZE] = {0}; read(sock, buffer, BUFFER_SIZE); // 解析请求并处理POST等 parse_post_data(buffer); // 注意:如果parse_post_data中涉及共享资源(如写同一个文件),需要加锁 // 发送响应(这里简化,实际需要根据请求生成响应) char *response = "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\nHello from server!"; write(sock, response, strlen(response)); close(sock); return NULL; } ``` 注意:上面的`parse_post_data`函数中,我们写文件`user.txt`,如果多个线程同时写同一个文件,会导致数据混乱或写入失败。因此,我们需要在写文件时加锁。 文件写操作加锁: 我们可以使用互斥锁(mutex)来保护文件写操作。 在全局范围定义互斥锁: ```c pthread_mutex_t file_mutex = PTHREAD_MUTEX_INITIALIZER; ``` 修改`parse_post_data`函数中写文件的部分: ```c void parse_post_data(const char *request) { // ... 解析数据(省略) // 加锁 pthread_mutex_lock(&file_mutex); FILE *file = fopen("user.txt", "a"); // 注意:使用追加模式,避免覆盖 if (!file) { perror("fopen"); pthread_mutex_unlock(&file_mutex); return; } fprintf(file, "Name: %s\nEmail: %s\nMessage: %s\n\n", name, email, message); fclose(file); pthread_mutex_unlock(&file_mutex); } ``` 注意:这里我们使用追加模式("a")打开文件,这样每次写入的数据都会追加到文件末尾,而不是覆盖之前的内容。 这样修改后,多个客户端同时提交表单时,写文件操作会串行执行,避免数据混乱。 但是,这种每次连接创建线程的方式在连接数很大时,线程数量过多会导致系统资源耗尽。因此,我们可以考虑使用线程池来限制线程数量。 线程池实现思路: 1. 预先创建一定数量的线程,这些线程处于等待状态。 2. 主线程接受连接,并将客户端套接字放入一个任务队列。 3. 工作线程从任务队列中取出任务(即套接字)并处理。 4. 使用互斥锁和条件变量来同步任务队列的访问。 由于线程池的实现较为复杂,这里不展开详细代码,但用户可以根据需要进一步优化。 另外,用户提到程序停止的问题,可能还有以下原因: - 某个线程处理请求时发生错误(如段错误),导致整个进程退出。需要确保线程函数中捕获所有异常(但C语言没有异常,所以需要检查指针、数组越界等)。 - 资源耗尽:如文件描述符耗尽(每个连接都会消耗一个文件描述符,而系统对单个进程打开的文件描述符数量有限制)。在关闭套接字后,文件描述符会被释放。但如果不小心泄漏(没有关闭),会导致达到上限后无法再创建新连接。 因此,务必确保每个线程在处理完请求后关闭客户端套接字。 总结: 1. 使用多线程处理并发连接,每个连接一个线程(或线程池)。 2. 对共享资源(如文件)的访问使用互斥锁保护。 3. 设置线程为可分离状态,避免资源泄漏。 4. 注意避免线程函数中的内存泄漏(如动态分配的内存要及时释放)和文件描述符泄漏。 通过以上优化,服务器应该能够处理多个客户端的并发请求,而不会出现程序停止的问题。 如果问题仍然存在,建议添加日志记录,观察服务器在处理多个连接时的行为,以便定位问题。 §§相关问题§§ 1. 如何在C语言中实现线程池? 2. 如何调试多线程程序? 3. 除了多线程,还有哪些处理高并发的服务器模型? 4. 如何测试HTTP服务器的并发性能? 5. 在C语言中,如何避免多线程环境下的死锁?</think>### C语言HTTP服务器多线程优化方案 针对浏览器多页面访问导致程序停止的问题,以下是优化多线程处理的完整解决方案。核心问题在于资源管理不当导致的文件描述符泄漏或线程同步问题。 #### 1. 线程池实现(解决资源耗尽问题) ```c #include <pthread.h> #include <semaphore.h> #define THREAD_POOL_SIZE 10 #define MAX_QUEUE 100 pthread_t thread_pool[THREAD_POOL_SIZE]; pthread_mutex_t queue_mutex = PTHREAD_MUTEX_INITIALIZER; sem_t queue_sem; int client_queue[MAX_QUEUE]; int queue_front = 0, queue_rear = 0; // 工作线程函数 void *thread_worker(void *arg) { while (1) { sem_wait(&queue_sem); // 等待任务 pthread_mutex_lock(&queue_mutex); int client_sock = client_queue[queue_front]; queue_front = (queue_front + 1) % MAX_QUEUE; pthread_mutex_unlock(&queue_mutex); handle_client_request(client_sock); // 处理请求 close(client_sock); // ★ 关键:关闭套接字 } return NULL; } // 初始化线程池 void init_thread_pool() { sem_init(&queue_sem, 0, 0); for (int i = 0; i < THREAD_POOL_SIZE; i++) { pthread_create(&thread_pool[i], NULL, thread_worker, NULL); } } // 添加任务到队列 void enqueue_client(int client_sock) { pthread_mutex_lock(&queue_mutex); client_queue[queue_rear] = client_sock; queue_rear = (queue_rear + 1) % MAX_QUEUE; pthread_mutex_unlock(&queue_mutex); sem_post(&queue_sem); // 通知工作线程 } ``` #### 2. 主线程接受连接(使用非阻塞IO) ```c int main() { init_thread_pool(); // ... [套接字初始化代码] // 设置非阻塞模式 fcntl(server_fd, F_SETFL, O_NONBLOCK); while (1) { int client_sock = accept(server_fd, NULL, NULL); if (client_sock < 0) { if (errno == EWOULDBLOCK) { usleep(10000); // 避免CPU忙等待 continue; } perror("accept"); continue; } enqueue_client(client_sock); // 添加到线程池队列 } // ... [清理代码] } ``` #### 3. 关键优化点:文件描述符管理 ```c void handle_client_request(int client_sock) { char buffer[BUFFER_SIZE]; int len = read(client_sock, buffer, sizeof(buffer)-1); if (len > 0) { buffer[len] = '\0'; // 处理POST请求 if (strstr(buffer, "POST") != NULL) { parse_post_data(buffer); } // 发送响应(简化示例) char *response = "HTTP/1.1 200 OK\r\nContent-Length: 13\r\n\r\nHello, World!"; write(client_sock, response, strlen(response)); } // ★ 关键:在子线程中关闭套接字 close(client_sock); // 防止文件描述符泄漏 } ``` #### 4. 线程安全的文件写入 ```c pthread_mutex_t file_mutex = PTHREAD_MUTEX_INITIALIZER; void save_to_file(const char *name, const char *email, const char *message) { pthread_mutex_lock(&file_mutex); FILE *fp = fopen("user.txt", "a"); if (fp) { fprintf(fp, "Name: %s\nEmail: %s\nMessage: %s\n\n", name, email, message); fclose(fp); } pthread_mutex_unlock(&file_mutex); } ``` ### 问题分析与解决方案 1. **程序停止原因分析**: - 文件描述符泄漏(未正确关闭套接字) - 线程资源耗尽(无限制创建新线程) - 共享资源竞争(如文件写入) 2. **优化措施**: - **线程池管理**:限制最大线程数(THREAD_POOL_SIZE) - **任务队列**:控制并发请求处理数量(MAX_QUEUE) - **非阻塞IO**:避免主线程阻塞在accept() - **双重关闭保护**:主线程只负责accept,子线程负责close - **互斥锁**:保护共享资源(文件/内存) 3. **关键改进点**: ```c // 错误做法(主线程关闭) // close(new_socket); // 正确做法(在子线程中关闭) void *thread_worker(void *arg) { // ... close(client_sock); // ★ 在子线程关闭 } ``` ### 测试建议 1. 使用压力测试工具(如Apache Bench): ```bash ab -n 1000 -c 50 http://localhost:8080/ ``` 2. 监控文件描述符使用: ```bash watch -n 1 "ls /proc/$(pidof your_server)/fd | wc -l" ``` 3. 添加日志跟踪线程生命周期: ```c printf("[%lu] Handling client %d\n", pthread_self(), client_sock); ``` 通过上述优化,服务器可稳定处理高并发请求,避免因浏览器多页面访问导致的程序停止问题[^2][^3]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值