tcp调用recv接口未判断接收缓冲区长度导致返回值为零误判为链路断开

博客讲述了在TCP编程中,由于忽略recv接口参数len为0可能导致的返回值误解,即返回0不一定是链路断开,可能是接收缓冲区长度为0。在疯狂发送异步消息的场景下,错误地判断链路断开并触发重连。通过抓包分析发现是B方主动断开连接,并非链路故障。最终定位到recv调用时len不应为0的问题,提醒开发者注意异常情况的测试和代码审查。

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

    我们在调用recv(fd,buf,len,0)接口时,都要判断返回值,如果返回值为0,表示链路已断开。然而如果len设置为0,那么返回值也是0,此时如果判断链路断开,显然是不对的.在最近一次测试新功能的时候,测出了这样一个旧bug。追查bug的过程如下:

    为了测试一个新功能,有这样一个场景:A向B疯狂的发送异步消息,其中B调用了我的接口。现象:B没过多久发现recv()的返回值为0,判断链路断开,然后尝试重连A。这种高频发送异步消息的场景我之前也是测试过的,是没有问题的,只是没有到达“疯狂”的程度。这也导致我错误的认为自己的代码没有问题,能够撑住高频异步消息的场景,实际证明,我当时测试的“高频”,还不够高频,所以没有测出问题。

    解决过程:发现B断开链接之后,A和B的代码逻辑中,都认为是对方主动断开,于是进行万能的抓包。所幸A和B不在同一台机器上面,否则还真抓不到,如果他们的通信没有经过网卡的话。抓包发现:B给A发了一个RST的数据包。这个包代表的含义如下:接收缓冲区有数据,主动断开链路,就会给对方发送一个 RST 的包,说明异常断开连接。这就是说,B的接收缓冲区里面还有数据,但是B主动调用close(fd)函数断开链路,此时A就会收到B发过来的RST的包。也就是说,链路是B主动断开的。那么问题来了,B为何会主动断开链接呢,明明B判断是对方断开链路

### 通过 TCP `recv` 判断连接断开的条件 在 TCP 连接中,使用 `recv` 函数可以检测到连接是否断开。具体来说,当对端关闭连接时,`recv` 的返回值0;如果发生错误,则返回 -1,并且可以通过检查 `errno` 来进一步确认错误类型[^5]。 #### 返回值分析 - 如果 `recv` 返回 0,表示对端已正常关闭连接[^5]。 - 如果 `recv` 返回 -1,需要检查 `errno`: - 如果 `errno` 是 `ECONNRESET`,表示对端异常关闭了连接[^4]。 - 如果 `errno` 是 `ETIMEDOUT`,可能是因为网络超时导致连接断开[^3]。 #### 示例代码 以下是一个使用 `recv` 检测连接状态的示例代码: ```c #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <errno.h> int check_socket_connection(int sockfd) { char buffer[1024]; ssize_t bytes_received = recv(sockfd, buffer, sizeof(buffer), 0); if (bytes_received == 0) { printf("Connection closed by peer\n"); return -1; } else if (bytes_received == -1) { if (errno == ECONNRESET) { printf("Connection reset by peer\n"); return -1; } else { perror("recv"); return -1; } } return 0; } int main() { int sockfd = socket(AF_INET, SOCK_STREAM, 0); // 假设 sockfd 已正确初始化并连接到服务器 if (check_socket_connection(sockfd) < 0) { close(sockfd); exit(EXIT_FAILURE); } close(sockfd); return 0; } ``` #### 使用 `select` 提高检测效率 为了提高检测效率,可以结合 `select` 函数设置超时时间来避免阻塞。如果在指定时间内没有收到数据,则可以认为连接可能断开[^5]。 ```c #include <sys/select.h> #include <time.h> int check_socket_with_select(int sockfd) { fd_set read_set; struct timeval timeout; FD_ZERO(&read_set); FD_SET(sockfd, &read_set); timeout.tv_sec = 1; // 超时秒数 timeout.tv_usec = 0; int ret = select(sockfd + 1, &read_set, NULL, NULL, &timeout); if (ret == 0) { printf("Timeout occurred, no data received\n"); return -1; } else if (ret == -1) { perror("select"); return -1; } if (FD_ISSET(sockfd, &read_set)) { char buffer[1024]; ssize_t bytes_received = recv(sockfd, buffer, sizeof(buffer), 0); if (bytes_received == 0) { printf("Connection closed by peer\n"); return -1; } else if (bytes_received == -1) { perror("recv"); return -1; } } return 0; } ``` #### 注意事项 - 即使 `recv` 成功返回,也不能完全保证连接仍然有效,因为底层缓冲区可能会暂时接收数据,但实际传输可能失败。因此,建议结合应用层心跳机制或启用 `SO_KEEPALIVE` 选项以提高检测准确性。 - 如果启用了信号处理(如捕获 `SIGPIPE`),则需要额外注意信号的处理逻辑,以免影响程序正常运行。 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值