C Socket HTTP请求无显式命令却自动打印响应问题
看起来你的问题核心是内存操作的未定义行为导致了意外的输出和颜色异常,我来帮你拆解问题并修正代码:
主要问题点
1. recv的buffer未添加空终止符,导致strcat越界读取
recv只会把接收到的字节写入buffer,但不会自动添加'\0'。而strcat需要参数是C风格字符串(以'\0'结尾),所以直接用strcat(response, buffer)会让程序从buffer开始一直读取内存,直到找到'\0'——这时候很可能读到之前printf留在栈上的ANSI颜色转义序列(比如你用的ANSI_COLOR_GREEN),这些内容被追加到response后,可能因为内存越界意外写入stdout缓冲区,导致控制台自动输出绿色的内容。
2. 初始calloc(0,0)的未定义行为
C标准规定,calloc(0, 0)的返回值是实现定义的,可能返回NULL或者一个不能安全解引用的指针。后续基于这个指针做realloc和strcat会埋下隐患。
3. 内存扩展的大小计算错误
你每次realloc时用sizeof(*response) + RESPONSE_BUFFER_SIZE,但sizeof(*response)是char的大小(1字节),这意味着每次只给response扩展RESPONSE_BUFFER_SIZE +1字节,但没有考虑已经存储在response里的内容长度,会导致内存不足或者浪费,同时也会让strcat的行为不可预测。
4. 循环条件的逻辑错误
你在循环里把bytesReceived重写为当前recv的结果,丢失了累计接收的字节数。循环条件bytesReceived > bytesReceivedPrevious无法正确判断是否还有数据(比如当recv返回0表示连接关闭时,循环会直接退出,但累计长度没被正确统计)。
修正后的代码
char *noSSLRequest(REQUEST_HEADER_INFO *request_header_info) { struct sockaddr_in serverAddress; char *requestHeader; unsigned short serverPort; char serverIP[13]; domainToIP(request_header_info->host, serverIP); // 初始化response为NULL,后续通过realloc动态扩展 char *response = NULL; // 跟踪累计接收的字节数 size_t totalBytesReceived = 0; int sockFD; // 创建Socket if ((sockFD = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) { freeRequestHeaderInfo(request_header_info); fprintf(stderr, "Error, could not open socket in http.c getHTMLBody(). Reason: %s", strerror(errno)); exit(-1); } printf(ANSI_COLOR_GREEN "LOG: Socket file descriptor is %d" ANSI_COLOR_RESET, sockFD); serverPort = 80; memset(&serverAddress, 0, sizeof(serverAddress)); serverAddress.sin_family = AF_INET; serverAddress.sin_port = htons(80); inet_aton(serverIP, &serverAddress.sin_addr); // 连接服务器 if (connect(sockFD, (const struct sockaddr *) &serverAddress, sizeof(serverAddress)) < 0) { freeRequestHeaderInfo(request_header_info); fprintf(stderr, "Error, could not connect socket in http.c getHTMLBody(). Reason: %s", strerror(errno)); exit(-1); } printf(ANSI_COLOR_GREEN "\nLOG: Connected socket at descriptor %d to IP %s and port %d" ANSI_COLOR_RESET, sockFD, serverIP, serverPort); // 构造请求头并发送 requestHeader = craftRequestHeader(request_header_info); if (send(sockFD, requestHeader, strlen(requestHeader), 0) < 0) { freeRequestHeaderInfo(request_header_info); fprintf(stderr, "Error, could not send request. Reason: %s", strerror(errno)); exit(-1); } printf(ANSI_COLOR_GREEN "\nLOG: Sent HTTP request from socket at descriptor %d to IP %s and port %d." ANSI_COLOR_RESET, sockFD, serverIP, serverPort); free(requestHeader); printf(ANSI_COLOR_GREEN "\nLOG: Starting receive operation" ANSI_COLOR_RESET); char buffer[RESPONSE_BUFFER_SIZE]; ssize_t bytesReceived; while (totalBytesReceived < RESPONSE_MAX_LEN) { // 留1字节空间,避免后续手动加终止符时越界 bytesReceived = recv(sockFD, buffer, RESPONSE_BUFFER_SIZE - 1, 0); if (bytesReceived <= 0) { // recv返回0表示连接关闭,返回-1表示错误,直接退出循环 break; } // 扩展response内存:已接收长度 + 新接收字节数 + 1(终止符) char *temp = realloc(response, totalBytesReceived + bytesReceived + 1); if (!temp) { // 内存分配失败,清理资源后退出 free(response); freeRequestHeaderInfo(request_header_info); fprintf(stderr, "Error: Out of memory while receiving response"); exit(-1); } response = temp; // 使用memcpy代替strcat,直接拷贝有效字节,无需依赖空终止符 memcpy(response + totalBytesReceived, buffer, bytesReceived); totalBytesReceived += bytesReceived; } // 确保response是合法的C字符串 if (response) { response[totalBytesReceived] = '\0'; } else { // 未收到任何数据时,返回空字符串 response = calloc(1, 1); } printf(ANSI_COLOR_GREEN "\nLOG: Received HTTP response from socket at descriptor %d to IP %s and port %d.\n" ANSI_COLOR_RESET, sockFD, serverIP, serverPort); if (close(sockFD) < 0) { freeRequestHeaderInfo(request_header_info); fprintf(stderr, "Error, could not close socket in http.c getHTMLBody(). Reason: %s", strerror(errno)); exit(-1); } printf(ANSI_COLOR_GREEN "\nLOG: Closed socket at descriptor %d" ANSI_COLOR_RESET, sockFD); freeRequestHeaderInfo(request_header_info); return response; }
关键修正说明
- 用
memcpy代替strcat:我们已经明确知道recv返回的有效字节数,直接用memcpy拷贝数据,避免了对空终止符的依赖,彻底解决了越界读取内存的问题。 - 正确管理内存生命周期:初始response设为NULL,每次
realloc都基于累计接收的字节数计算所需内存,确保有足够空间存储新数据和终止符,同时添加了内存分配失败的错误处理。 - 修复循环逻辑:用
totalBytesReceived单独跟踪累计长度,bytesReceived只记录当前recv的结果,循环条件改为检查累计长度是否超过最大值,同时正确处理连接关闭和接收错误的情况。 - 显式添加空终止符:接收完成后手动为response添加
'\0',确保返回的是合法的C字符串。
这样修改后,应该就能解决你遇到的意外输出和颜色异常问题了——本质上是之前的内存越界读取了栈上的ANSI颜色代码,现在的内存操作更安全,不会再混入无关数据。
内容的提问来源于stack exchange,提问作者rassa45

