poll()提示有消息但recv()无消息且阻塞的Socket问题
Socket服务器poll()误报可读导致recv()阻塞问题
搭建基础Socket服务器,使用Google Chrome通过localhost测试消息接收功能。此前遇到recv()阻塞问题,查阅select()与poll()区别后改用poll()方案。当前代码能获取消息,但poll()始终提示有消息可读,实际调用recv()时出现阻塞。
简化代码
/* request is a pointer to a struct with the following members: char *data; int size; */ request->data = (char *)malloc(1); char *data_ptr = request->data; if (request->data == NULL) { perror("Error allocating memory"); return 8; } request->size = 1; struct pollfd fds[1]; struct pollfd read; read.fd = *connectionptr; read.events = POLL_IN | POLL_PRI; fds[0] = read; int available_ops = poll(fds, 1, 0); while (available_ops > 0) { if (fds[0].revents & POLL_IN || fds[0].revents & POLL_PRI) { if (recv(*connectionptr, data_ptr, 1, 0) < 0) { perror("Error receiving data"); return 6; } request->data = (char *)realloc(request->data, request->size + 1); request->size++; data_ptr = request->data + request->size - 1; if (request->data == NULL) { perror("Error allocating mem"); return 8; } } available_ops = poll(fds, 1, 0); printf("Read %s thus far, %d characters more to read\n", request->data, available_ops); }
运行输出
Read GET / HTTP/1.1 Host: 127.0.0.1:443 Connection: keep-alive Cache-Control: max-age=0 sec-ch-ua: "Not.A/Brand";v="8", "Chromium";v="114", "Google Chrome";v="114" sec-ch-ua-mobile: ?0 sec-ch-ua-platform: "macOS" Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7 Sec-Fetch-Site: none Sec-Fetch-Mode: navigate Sec-Fetch-User: ?1 Sec-Fetch-Dest: document Accept-Encoding: gzip, deflate, br Accept-Language: en-US,en;q=0.9 thus far, 1 characters more to read
程序在此处停滞,调试确认是recv()调用导致阻塞。Socket已配置SO_KEEPALIVE(关闭后问题仍存在)与SO_REUSEADDR。
完整可复现代码
main.c
#include "server.h" int main(void) { int socket_desc = socket(AF_INET6, SOCK_STREAM, 0); if (socket_desc < 0) { perror("Error initializing socket"); close(socket_desc); return 1; } printf("Socket created\n"); int reuse = 1; int keep_alive = 1; if (setsockopt(socket_desc, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) < 0) { perror("Socket Address Reuse could not be set"); close(socket_desc); return 2; } printf("Socket set to reuse address\n"); if (setsockopt(socket_desc, SOL_SOCKET, SO_KEEPALIVE, &keep_alive, sizeof(keep_alive)) < 0) { perror("Keep Alive could not be activated"); close(socket_desc); return 3; } printf("Socket set to keep connection alive\n"); struct sockaddr_in6 address; memset(&address, 0, sizeof(address)); address.sin6_family = AF_INET6; address.sin6_port = htons(443); address.sin6_addr = in6addr_any; int binding = bind(socket_desc, (struct sockaddr *)&address, sizeof(address)); if (binding < 0) { perror("Bind failed"); close(socket_desc); } printf("Socket bound to port 443\n"); if (listen(socket_desc, 5) < 0) { perror("Error in listening"); close(socket_desc); return 4; } printf("Listening...\n"); struct sockaddr client_info = {0}; socklen_t client_info_length = sizeof(client_info); int soc_conn = accept(socket_desc, &client_info, &client_info_length); if (soc_conn < 0) { perror("Error accepting connection to client"); close(socket_desc); return 5; } struct chararr receive_buffer; int code = process_request(&soc_conn, &receive_buffer); if (code != 0) { free(receive_buffer.data); close(socket_desc); close(soc_conn); return code; } printf("Message Sent!\n"); printf("ending program...\n"); close(socket_desc); close(soc_conn); free(receive_buffer.data); return 0; } int process_request(int *connectionptr, struct chararr *request) { request->data = (char *)malloc(1); char *data_ptr = request->data; if (request->data == NULL) { perror("Error allocating memory"); return 8; } request->size = 1; printf("Receiving Data\n"); struct pollfd fds[1]; struct pollfd readcheck; readcheck.fd = *connectionptr; readcheck.events = POLL_IN | POLL_PRI; fds[0] = readcheck; int available_ops = poll(fds, 1, 0); while (available_ops > 0) { if (fds[0].revents & POLL_IN || fds[0].revents & POLL_PRI) { if (recv(*connectionptr, data_ptr, 1, 0) < 0) { perror("Error receiving data"); return 6; } request->data = (char *)realloc(request->data, request->size + 1); request->size++; data_ptr = request->data + request->size - 1; if (request->data == NULL) { perror("Error allocating mem"); return 8; } } available_ops = poll(fds, 1, 0); printf("Read %s thus far, %d characters more to read\n", request->data, available_ops); } return 0; }
server.h
#include <arpa/inet.h> #include <sys/socket.h> #include <sys/types.h> #include <netinet/in.h> #include <ifaddrs.h> #include <netdb.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <errno.h> #include <poll.h> struct chararr { char *data; int size; }; int process_request(int *connectionptr, struct chararr *request);
问题原因及修复方案
核心问题
poll()的revents未重置:每次调用poll()前未清空fds[0].revents,旧事件标志会保留,导致后续poll()误判为有数据可读。recv()返回值未完全处理:仅判断recv() < 0的错误情况,未处理recv() == 0(客户端关闭连接)的场景,此时poll()会返回POLL_IN,但recv()无数据可读,最终导致阻塞。- 内存分配与字符串终止问题:接收的字符串未添加
\0终止符,导致printf输出异常,且无法正确识别请求结束标识。
修复步骤
- 重置
revents:每次调用poll()前将fds[0].revents设为0,避免旧事件干扰。 - 处理
recv()所有返回值:判断recv()返回0时退出循环;返回大于0时处理接收到的字节数。 - 添加字符串终止符:接收完成后为
request->data添加\0,确保字符串格式正确。 - 设置
poll()超时:避免无限等待,可设置合理超时时间。
修复后的process_request函数示例
int process_request(int *connectionptr, struct chararr *request) { request->size = 0; // 初始分配更大内存,减少realloc次数 request->data = (char *)malloc(128); if (request->data == NULL) { perror("Error allocating memory"); return 8; } char *data_ptr = request->data; printf("Receiving Data\n"); struct pollfd fds[1]; fds[0].fd = *connectionptr; fds[0].events = POLL_IN | POLL_PRI; while (1) { fds[0].revents = 0; // 重置事件标志 // 设置5秒超时,避免无限阻塞 int available_ops = poll(fds, 1, 5000); if (available_ops < 0) { perror("poll failed"); return 7; } else if (available_ops == 0) { printf("poll timeout\n"); break; } if (fds[0].revents & (POLL_IN | POLL_PRI)) { // 预留1字节给终止符,避免缓冲区溢出 ssize_t recv_len = recv(*connectionptr, data_ptr, 127 - request->size, 0); if (recv_len < 0) { perror("Error receiving data"); return 6; } else if (recv_len == 0) { printf("Client closed connection\n"); break; } request->size += recv_len; data_ptr += recv_len; // 缓冲区不足时扩容 if (request->size >= 128 - 1) { char *new_data = realloc(request->data, request->size * 2); if (new_data == NULL) { perror("Error reallocating memory"); return 8; } request->data = new_data; data_ptr = request->data + request->size; } // 检查HTTP请求是否结束(空行标识) if (request->size >= 4 && memcmp(data_ptr - 4, "\r\n\r\n", 4) == 0) { printf("Received full HTTP request\n"); break; } } printf("Read %.*s thus far, %d events pending\n", request->size, request->data, available_ops); } // 添加字符串终止符 request->data[request->size] = '\0'; return 0; }
内容的提问来源于stack exchange,提问作者AmLearning
相关产品推荐
相关产品推荐

