closesocket,shutdown区别 recv,send意义 CLOSE_WAIT影响

本文深入探讨WinSock中closesocket和shutdown的作用,解释了它们如何影响TCP连接的关闭过程。同时,文章详细阐述了应用层的recv和send函数与内核中套接字的实际读写机制,揭示了这两个函数在数据传输过程中的真正意义。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

http://blog.youkuaiyun.com/yby4769250/article/details/7296859

区分 WinSock中,closesocket 和 shutdown的作用

如果是shutdown(SD_SEND)则会发送FIN包,这是对协议层产生的影响,此时,协议层会首先把缓冲区中的数据发送出去,最后再发送FIN包,次称为‘从容关闭’或者‘优雅关闭’。

closesocket虽然也会发送FIN包,但是,此时会丢弃掉缓冲区中的数据,导致数据丢失,此称为‘强制关闭’,并且closesocket会使连接释放,并且,系统会释放与该socket相关的资源

 

http://blog.youkuaiyun.com/yby4769250/article/details/7296860

recv和send的真正作用

区分应用层的recvsend和内核中套接字的真正读取数据,要知道,TCP协议层是运行与内核当中,而通信是网卡直接的通信,因此,不要认为,没有调用recv就不会有数据,不管调没调用recv,只要对方send,线路上有数据,那么,协议就会从网卡里面读取该数据进内核的socket缓冲区中,而调用recv的真正作用只是把数据从socket内核缓冲区中把数据拷贝到应用层指定的buffer中,仅此而已,对协议完全没影响,同理,因为socket默认是全缓冲,如果没有setsocketopt的话,只有当socket内核缓冲区中的数据满了之后才会执行真正的发送数据,才会把数据从socket 的缓冲区中通过网卡把数据发送出去,其实就是执行真正的IO操作一样,因此,send的操作,只是把应用层的数据拷贝到socket的缓冲区中而已,并不表示把数据发送出去了,明白这几点。

 

http://blog.youkuaiyun.com/yby4769250/article/details/7296858

CLOSE_WAIT的产生以及影响和解决方案

今天,做了CoolDown的测试时才发现,C/S架构中,如果服务器先执行主动关闭,则非常有可能导致客户端的socket一直处于CLOSE_WAIT状态,相应的导致服务器端的socket一直处于FIN_WAIT2状态,愿意是,当服务器端执行主动关闭时,发送FIN包,客户端相应ACK,,这个时候,服务器端进入FIN_WAIT2,而客户端进入CLOSE_WAIT状态,如果这个时候,客户端不执行shutdown(SD_SEND)的话,无法给服务器端发送FIN包,则两端的状态都一直不变,这个时候导致关闭一场,socket连接永远无法释放,占用系统资源,所以,一定要注意这点

#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 <sys/epoll.h> #include <fcntl.h> #include <pthread.h> #include <dirent.h> #include <sys/stat.h> #include <time.h> #include <ctype.h> #include <errno.h> #define PORT 8080 #define MAX_EVENTS 1024 #define THREAD_POOL_SIZE 8 #define BUFFER_SIZE 4096 #define WEB_ROOT "web" // 当前目录作为根目录 // 线程池结构 typedef struct { pthread_t *threads; int thread_count; pthread_mutex_t lock; pthread_cond_t cond; int shutdown; } ThreadPool; // 任务结构 typedef struct { int client_fd; struct sockaddr_in client_addr; } Task; // 全局任务队列 Task *task_queue; int task_count = 0; int task_capacity = 0; ThreadPool thread_pool; // HTTP响应头 const char *http_header = "HTTP/1.1 200 OK\r\n" "Server: SimpleWebServer\r\n" "Connection: close\r\n" "Content-Type: %s\r\n" "Content-Length: %ld\r\n\r\n"; // 十六进制字符转换 int hex_char_to_int(char c) { if (c >= '0' && c <= '9') return c - '0'; if (c >= 'a' && c <= 'f') return c - 'a' + 10; if (c >= 'A' && c <= 'F') return c - 'A' + 10; return 0; } // URL解码 void url_decode(char *dst, const char *src) { char a, b; while (*src) { if (*src == '%' && (a = src[1]) && (b = src[2]) && isxdigit(a) && isxdigit(b)) { *dst++ = (hex_char_to_int(a) << 4) | hex_char_to_int(b); src += 3; } else if (*src == '+') { *dst++ = ' '; src++; } else { *dst++ = *src++; } } *dst = '\0'; } // 文件扩展名到MIME类型映射 const char *extension_to_type(const char *ext) { if (strcmp(ext, "html") == 0) return "text/html"; if (strcmp(ext, "htm") == 0) return "text/html"; 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, "ico") == 0) return "image/x-icon"; if (strcmp(ext, "json") == 0) return "application/json"; if (strcmp(ext, "txt") == 0) return "text/plain"; if (strcmp(ext, "svg") == 0) return "image/svg+xml"; return "application/octet-stream"; } // 获取文件扩展名 const char *get_file_extension(const char *filename) { const char *dot = strrchr(filename, '.'); if (!dot || dot == filename) return ""; return dot + 1; } // 发送404错误响应 void send_404(int client_fd) { const char *response = "HTTP/1.1 404 Not Found\r\n" "Content-Type: text/html; charset=UTF-8\r\n" "Connection: close\r\n" "Content-Length: 86\r\n\r\n" "<html><head><title>404 Not Found</title></head><body><h1>404 Not Found</h1></body></html>"; send(client_fd, response, strlen(response), 0); } // 发送400错误响应 void send_400(int client_fd) { const char *response = "HTTP/1.1 400 Bad Request\r\n" "Content-Type: text/html; charset=UTF-8\r\n" "Connection: close\r\n" "Content-Length: 84\r\n\r\n" "<html><head><title>400 Bad Request</title></head><body><h1>400 Bad Request</h1></body></html>"; send(client_fd, response, strlen(response), 0); } // 发送500错误响应 void send_500(int client_fd) { const char *response = "HTTP/1.1 500 Internal Server Error\r\n" "Content-Type: text/html; charset=UTF-8\r\n" "Connection: close\r\n" "Content-Length: 92\r\n\r\n" "<html><head><title>500 Internal Error</title></head><body><h1>500 Internal Server Error</h1></body></html>"; send(client_fd, response, strlen(response), 0); } // 处理客户端请求 void handle_request(int client_fd, struct sockaddr_in client_addr) { char buffer[BUFFER_SIZE]; ssize_t bytes_read = recv(client_fd, buffer, BUFFER_SIZE - 1, 0); if (bytes_read <= 0) { close(client_fd); return; } buffer[bytes_read] = '\0'; // 解析HTTP请求 char method[16], path[256], protocol[16]; if (sscanf(buffer, "%s %s %s", method, path, protocol) != 3) { send_400(client_fd); close(client_fd); return; } // 日志输出 char client_ip[INET_ADDRSTRLEN]; inet_ntop(AF_INET, &(client_addr.sin_addr), client_ip, INET_ADDRSTRLEN); printf("[%s] %s %s %s\n", client_ip, method, path, protocol); // URL解码 char decoded_path[256]; url_decode(decoded_path, path); printf("Decoded path: %s\n", decoded_path); // 只处理GET请求 if (strcmp(method, "GET") != 0 && strcmp(method, "POST") != 0) { send_400(client_fd); close(client_fd); return; } // 安全处理路径 char safe_path[512]; if (strcmp(decoded_path, "/") == 0) { // 尝试多个可能的默认文件 const char *default_files = {"Index.html"}; int found = 0; snprintf(safe_path, sizeof(safe_path), "%s/%s", WEB_ROOT, default_files); if (access(safe_path, F_OK) == 0) { found = 1; } if (!found) { strcpy(safe_path, WEB_ROOT "/Index.html"); } } else { // 处理路径中的斜杠问题 if (decoded_path[0] == '/') { snprintf(safe_path, sizeof(safe_path), "%s%s", WEB_ROOT, decoded_path); } else { snprintf(safe_path, sizeof(safe_path), "%s/%s", WEB_ROOT, decoded_path); } } printf("Final path: %s\n", safe_path); // 防止路径遍历攻击 if (strstr(safe_path, "..")) { printf("Path traversal attempt detected: %s\n", safe_path); send_404(client_fd); close(client_fd); return; } // 检查文件是否存在 struct stat file_stat; if (stat(safe_path, &file_stat) < 0) { perror("stat failed"); send_404(client_fd); close(client_fd); return; } // 检查是否是普通文件 if (!S_ISREG(file_stat.st_mode)) { printf("Not a regular file: %s\n", safe_path); send_404(client_fd); close(client_fd); return; } // 检查文件权限 if (access(safe_path, R_OK) != 0) { perror("access failed"); send_404(client_fd); close(client_fd); return; } // 打开文件 FILE *file = fopen(safe_path, "rb"); if (!file) { perror("fopen failed"); send_500(client_fd); close(client_fd); return; } // 获取MIME类型 const char *ext = get_file_extension(safe_path); const char *mime_type = extension_to_type(ext); // 构建HTTP响应头 char header[1024]; snprintf(header, sizeof(header), http_header, mime_type, file_stat.st_size); // 发送响应头 ssize_t sent = send(client_fd, header, strlen(header), 0); if (sent <= 0) { perror("send header failed"); fclose(file); close(client_fd); return; } // 发送文件内容 char file_buffer[BUFFER_SIZE]; size_t bytes; while ((bytes = fread(file_buffer, 1, sizeof(file_buffer), file)) > 0) { sent = send(client_fd, file_buffer, bytes, 0); if (sent <= 0) { perror("send file content failed"); break; } } fclose(file); close(client_fd); // 打印日志 printf("[%s] %s %s - Sent %ld bytes\n", client_ip, method, decoded_path, file_stat.st_size); } // 线程工作函数 void *worker_thread(void *arg) { while (1) { pthread_mutex_lock(&thread_pool.lock); // 等待任务 while (task_count == 0 && !thread_pool.shutdown) { pthread_cond_wait(&thread_pool.cond, &thread_pool.lock); } if (thread_pool.shutdown) { pthread_mutex_unlock(&thread_pool.lock); pthread_exit(NULL); } // 获取任务 Task task = task_queue[--task_count]; pthread_mutex_unlock(&thread_pool.lock); // 处理任务 handle_request(task.client_fd, task.client_addr); } return NULL; } // 初始化线程池 void thread_pool_init(ThreadPool *pool, int thread_count) { pool->threads = (pthread_t *)malloc(thread_count * sizeof(pthread_t)); if (!pool->threads) { perror("malloc failed"); exit(EXIT_FAILURE); } pool->thread_count = thread_count; pool->shutdown = 0; pthread_mutex_init(&pool->lock, NULL); pthread_cond_init(&pool->cond, NULL); // 初始化任务队列 task_capacity = 256; task_queue = (Task *)malloc(task_capacity * sizeof(Task)); if (!task_queue) { perror("malloc failed"); exit(EXIT_FAILURE); } task_count = 0; // 创建工作线程 for (int i = 0; i < thread_count; i++) { if (pthread_create(&pool->threads[i], NULL, worker_thread, NULL) != 0) { perror("pthread_create failed"); exit(EXIT_FAILURE); } } } // 添加任务到线程池 void add_task(int client_fd, struct sockaddr_in client_addr) { pthread_mutex_lock(&thread_pool.lock); // 如果任务队列已满,扩大容量 if (task_count == task_capacity) { task_capacity *= 2; Task *new_queue = (Task *)realloc(task_queue, task_capacity * sizeof(Task)); if (!new_queue) { perror("realloc failed"); pthread_mutex_unlock(&thread_pool.lock); close(client_fd); return; } task_queue = new_queue; } // 添加任务 task_queue[task_count].client_fd = client_fd; task_queue[task_count].client_addr = client_addr; task_count++; // 通知工作线程 pthread_cond_signal(&thread_pool.cond); pthread_mutex_unlock(&thread_pool.lock); } // 设置文件描述符为非阻塞 void set_nonblocking(int fd) { int flags = fcntl(fd, F_GETFL, 0); if (flags == -1) { perror("fcntl F_GETFL failed"); return; } if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1) { perror("fcntl F_SETFL failed"); } } int main() { int server_fd, epoll_fd; struct sockaddr_in address; struct epoll_event event, events[MAX_EVENTS]; socklen_t addrlen = sizeof(address); // 创建服务器socket if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) { perror("socket failed"); exit(EXIT_FAILURE); } // 设置socket选项 int opt = 1; if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt))) { perror("setsockopt"); close(server_fd); exit(EXIT_FAILURE); } // 绑定地址和端口 address.sin_family = AF_INET; address.sin_addr.s_addr = INADDR_ANY; address.sin_port = htons(PORT); if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) { perror("bind failed"); close(server_fd); exit(EXIT_FAILURE); } // 开始监听 if (listen(server_fd, 128) < 0) { perror("listen"); close(server_fd); exit(EXIT_FAILURE); } printf("Server listening on port %d\n", PORT); printf("Web root: %s\n", WEB_ROOT); // 创建epoll实例 epoll_fd = epoll_create1(0); if (epoll_fd < 0) { perror("epoll_create1"); close(server_fd); exit(EXIT_FAILURE); } // 添加服务器socket到epoll event.events = EPOLLIN; event.data.fd = server_fd; if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, server_fd, &event) < 0) { perror("epoll_ctl: server_fd"); close(server_fd); close(epoll_fd); exit(EXIT_FAILURE); } // 初始化线程池 thread_pool_init(&thread_pool, THREAD_POOL_SIZE); printf("Thread pool initialized with %d threads\n", THREAD_POOL_SIZE); // 主循环 while (1) { int nfds = epoll_wait(epoll_fd, events, MAX_EVENTS, -1); if (nfds < 0) { perror("epoll_wait"); break; } for (int i = 0; i < nfds; i++) { if (events[i].data.fd == server_fd) { // 处理新连接 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; } // 设置非阻塞 set_nonblocking(client_fd); // 添加客户端socket到epoll event.events = EPOLLIN | EPOLLET; event.data.fd = client_fd; if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, client_fd, &event) < 0) { perror("epoll_ctl: client_fd"); close(client_fd); continue; } // 打印新连接信息 char client_ip[INET_ADDRSTRLEN]; inet_ntop(AF_INET, &(client_addr.sin_addr), client_ip, INET_ADDRSTRLEN); printf("New connection from %s:%d\n", client_ip, ntohs(client_addr.sin_port)); } else { // 处理客户端请求 int client_fd = events[i].data.fd; // 获取客户端地址 struct sockaddr_in client_addr; socklen_t addr_len = sizeof(client_addr); if (getpeername(client_fd, (struct sockaddr *)&client_addr, &addr_len) < 0) { perror("getpeername failed"); close(client_fd); continue; } // 将任务添加到线程池 add_task(client_fd, client_addr); // 从epoll中移除,避免重复处理 epoll_ctl(epoll_fd, EPOLL_CTL_DEL, client_fd, NULL); } } } // 清理 close(server_fd); close(epoll_fd); // 关闭线程池 thread_pool.shutdown = 1; pthread_cond_broadcast(&thread_pool.cond); for (int i = 0; i < thread_pool.thread_count; i++) { pthread_join(thread_pool.threads[i], NULL); } free(thread_pool.threads); free(task_queue); return 0; } 以这一版为准 运行后,windows浏览器偶尔会对GET请求报错:net:ERR_CONTENT_LENGTH_MISMATCH
最新发布
08-16
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值