【发布时间】:2012-01-18 05:11:03
【问题描述】:
我有一个应用程序从服务器读取大文件并经常在特定机器上挂起。在RHEL5.2下成功运行了很长时间。我们最近升级到 RHEL6.1,现在它经常挂起。
我创建了一个可以重现问题的测试应用。它挂起大约 100 次中的 98 次。
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/param.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <netdb.h>
#include <sys/socket.h>
#include <sys/time.h>
int mFD = 0;
void open_socket()
{
struct addrinfo hints, *res;
memset(&hints, 0, sizeof(hints));
hints.ai_socktype = SOCK_STREAM;
hints.ai_family = AF_INET;
if (getaddrinfo("localhost", "60000", &hints, &res) != 0)
{
fprintf(stderr, "Exit %d\n", __LINE__);
exit(1);
}
mFD = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
if (mFD == -1)
{
fprintf(stderr, "Exit %d\n", __LINE__);
exit(1);
}
if (connect(mFD, res->ai_addr, res->ai_addrlen) < 0)
{
fprintf(stderr, "Exit %d\n", __LINE__);
exit(1);
}
freeaddrinfo(res);
}
void read_message(int size, void* data)
{
int bytesLeft = size;
int numRd = 0;
while (bytesLeft != 0)
{
fprintf(stderr, "reading %d bytes\n", bytesLeft);
/* Replacing MSG_WAITALL with 0 works fine */
int num = recv(mFD, data, bytesLeft, MSG_WAITALL);
if (num == 0)
{
break;
}
else if (num < 0 && errno != EINTR)
{
fprintf(stderr, "Exit %d\n", __LINE__);
exit(1);
}
else if (num > 0)
{
numRd += num;
data += num;
bytesLeft -= num;
fprintf(stderr, "read %d bytes - remaining = %d\n", num, bytesLeft);
}
}
fprintf(stderr, "read total of %d bytes\n", numRd);
}
int main(int argc, char **argv)
{
open_socket();
uint32_t raw_len = atoi(argv[1]);
char raw[raw_len];
read_message(raw_len, raw);
return 0;
}
我的测试中的一些笔记:
- 如果“localhost”映射到环回地址 127.0.0.1,应用程序会挂起对 recv() 的调用并且永远不会返回。
- 如果“localhost”映射到机器的ip,从而通过以太网接口路由数据包,则应用程序成功完成。
- 当我遇到挂起时,服务器会发送“TCP 窗口已满”消息,客户端会以“TCP ZeroWindow”消息进行响应(参见图片和附加的 tcpdump 捕获)。从这一点开始,它永远挂起,服务器发送保持活动状态,客户端发送 ZeroWindow 消息。客户端似乎从不展开其窗口,以允许完成传输。
- 在挂起期间,如果我检查“netstat -a”的输出,服务器发送队列中有数据,但客户端接收队列为空。
- 如果我从 recv() 调用中删除 MSG_WAITALL 标志,应用程序将成功完成。
- 只有在一台特定机器上使用环回接口时才会出现挂起问题。我怀疑这可能都与时间依赖性有关。
- 当我减小“文件”的大小时,发生挂起的可能性会降低
可在此处找到测试应用的源代码:
可以在此处找到来自环回接口的 tcpdump 捕获:
我通过发出以下命令重现该问题:
> gcc socket_test.c -o socket_test
> perl -e 'for (1..6000000){ print "a" }' | nc -l 60000
> ./socket_test 6000000
这看到发送到测试应用程序的 6000000 字节尝试使用对 recv() 的单个调用来读取数据。
我很想听听任何关于我可能做错了什么的建议或任何其他调试问题的方法。
【问题讨论】:
标签: c linux sockets networking tcp