基于C语言Socket的TCP/IP吞吐量测试程序阻塞问题求助
嘿,看来你在做TCP吞吐量测试的时候遇到了阻塞问题——这种情况我帮不少人排查过,大概率是同步逻辑或者读写返回值处理的问题,咱们一步步来分析:
常见阻塞原因及解决思路
1. 读写同步逻辑混乱
TCP是面向流的协议,而且依赖滑动窗口机制,如果你的客户端和服务器没有严格遵循「客户端发→服务器收→服务器回→客户端收」的循环逻辑,很容易导致双方互相等待:
- 比如客户端连续发送多轮数据,服务器还没处理完上一轮,导致客户端发送缓冲区被填满,
send()直接阻塞; - 或者服务器接收数据后没及时回复,客户端的
recv()就会一直挂起等待响应。
解决方法:
强制每一轮读写操作完成后再进入下一轮。比如客户端必须确认收到服务器的响应,才发起下一次发送;服务器必须处理完当前的接收+回复,才等待下一次客户端的数据。
2. 忽略send()/recv()的返回值
这是最容易踩的坑!很多人默认send()会一次性发完所有数据,recv()会一次性拿到完整的响应,但实际情况并非如此:
send()的返回值是实际发送的字节数,如果内核缓冲区不足,它只会发一部分,剩下的需要你循环发送;recv()返回0表示对方关闭了连接,返回-1可能是信号中断(比如EINTR,可以重试),返回正数才是实际收到的字节数。
给你贴个客户端的一轮读写示例,注意处理返回值:
#define BUF_SIZE 1024 // 发送测试数据(确保全部发送完成) char send_buf[BUF_SIZE] = "dummy_data"; int total_sent = 0; int data_len = strlen(send_buf); while (total_sent < data_len) { int sent = send(client_sock, send_buf + total_sent, data_len - total_sent, 0); if (sent == -1) { perror("send failed"); close(client_sock); exit(EXIT_FAILURE); } total_sent += sent; } // 等待服务器响应 char recv_buf[BUF_SIZE]; int recv_len = recv(client_sock, recv_buf, BUF_SIZE, 0); if (recv_len == -1) { perror("recv failed"); close(client_sock); exit(EXIT_FAILURE); } else if (recv_len == 0) { printf("Server closed connection unexpectedly\n"); close(client_sock); exit(EXIT_FAILURE); } // 这里可以不用处理响应内容,只要确认收到就行
服务器端也要做同样的处理:完整接收客户端数据后,再发送一个响应(哪怕是空数据也行),确保客户端能触发下一轮循环。
3. 连接复用问题
如果你的程序是每次读写对都新建一个Socket连接,10000次连接很容易触发系统资源限制(比如文件描述符耗尽),或者因为TIME_WAIT状态导致端口被占满,最终阻塞。
解决方法:
优先复用同一个TCP连接完成所有10000次读写操作——这不仅能避免资源问题,还更符合吞吐量测试的场景(模拟长连接下的传输效率)。如果一定要用短连接,记得每次用完后调用close()关闭Socket,并且设置SO_REUSEADDR选项:
int opt = 1; setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
4. 内核缓冲区限制(极端情况)
如果你的测试数据量极大,可能会触发内核TCP缓冲区的上限,导致send()/recv()阻塞。这种情况可以临时调整内核参数(比如net.ipv4.tcp_wmem和net.ipv4.tcp_rmem)来增大缓冲区,但这应该是最后一步——先确保代码逻辑没问题再说。
快速排查小技巧
- 先把测试次数改成10次,加上调试打印,看每一步
send()/recv()的返回值,定位到底是哪一步阻塞了; - 用
netstat -anp(Linux)或者netstat -ano(Windows)查看Socket状态,如果是ESTABLISHED但没数据传输,说明双方都在等对方发数据; - 检查服务器端代码,是不是接收完数据后没有进入下一轮等待,或者陷入了死循环。
内容的提问来源于stack exchange,提问作者Miket25

