我有一个从服务器读取大文件并在特定计算机上频繁挂起的应用程序。它在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;
}
我测试的一些注意事项:
测试应用的来源可以在这里找到:
可以在此处找到loopback接口的tcpdump捕获:
我通过发出以下命令重现该问题:
> gcc socket_test.c -o socket_test
> perl -e 'for (1..6000000){ print "a" }' | nc -l 60000
> ./socket_test 6000000
这会发现6000000字节发送到测试应用程序,该应用程序尝试使用一次调用recv()来读取数据。
我很想听听有关我可能做错的建议或任何进一步调试问题的方法。
答案 0 :(得分:16)
MSG_WAITALL
应阻止,直到收到所有数据为止。来自manual page on recv:
该标志请求操作块直到满足完整请求。
但是,网络堆栈中的缓冲区可能不足以容纳所有内容,这就是服务器上出现错误消息的原因。客户端网络堆栈根本无法保存那么多数据。
解决方案是增加缓冲区大小(SO_RCVBUF
选项到setsockopt
),将消息拆分成更小的块,或者接收更小的块将它放入自己的缓冲区。最后一个是我推荐的。
编辑:我在您的代码中看到您已经按照我的建议行事(使用自己的缓冲读取较小的块),所以只需删除MSG_WAITALL
标志即可。
哦,当recv
返回零时,这意味着另一端关闭了连接,你也应该这样做。
答案 1 :(得分:1)
考虑以下两个可能的规则:
接收方可能会在收到已发送的内容之前等待发件人发送更多内容。
发送者可能会等待接收者在发送更多内容之前接收已发送的内容。
我们可以拥有这些规则中的任何一个,但我们不能同时拥有这两个规则。
为什么呢?因为如果允许接收方等待发送方,这意味着发送方在发送更多之前不能等待接收方接收,否则我们就会死锁。如果发送者被允许等待接收者,这意味着接收者不能等待发送者在接收更多之前发送,否则我们就会陷入僵局。
如果这两件事同时发生,我们就陷入僵局。在接收方收到已发送的内容之前,发送方不会发送更多内容,除非发送方发送更多内容,否则接收方将不会收到已发送的内容。吊杆。
TCP选择规则2(原因应该是显而易见的)。因此,它不能支持规则1.但是在您的代码中,您是接收者,并且您正在等待发送者在收到已发送的内容之前发送更多内容。所以这将陷入僵局。