套接字recv()使用MSG_WAITALL挂在大消息上

我有一个从服务器读取大文件并经常挂在特定计算机上的应用程序。它已经在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标志,则该应用程序成功完成。
  • 挂起问题仅在1台特定计算机上使用回送接口出现。我怀疑这可能与时序依赖性有关。
  • 当我减小“文件”的大小时,挂起的可能性降低了

可在以下位置找到测试应用程序的源:

套接字测试源

从Loopback接口捕获的tcpdump可以在以下位置找到:

tcpdump捕获

我通过发出以下命令来重现该问题:

>  gcc socket_test.c -o socket_test

> perl -e 'for (1..6000000){ print "a" }' | nc -l 60000

> ./socket_test 6000000

这将看到发送到测试应用程序的6000000字节,该应用程序尝试通过对recv()的一次调用来读取数据。

我很乐意听到关于我可能做错了什么的任何建议,或任何其他调试问题的方法。

回答:

MSG_WAITALL应该

阻塞,直到收到所有数据。在recv的手册页中:

该标志请求操作块,直到满足完整请求为止。

但是,网络堆栈中的缓冲区可能不够大,无法容纳所有内容,这就是服务器上出现错误消息的原因。客户端网络堆栈根本无法容纳那么多数据。

解决方案是增加缓冲区大小(的SO_RCVBUF选项setsockopt),将消息拆分为较小的片段,或接收较小的块将其放入您自己的缓冲区中。最后是我的建议。

我在您的代码中看到您已经按照我的建议进行操作(使用自己的缓冲读取较小的块),因此只需删除该MSG_WAITALL标志即可。

哦,当recv返回零时,意味着另一端已关闭连接,您也应该这样做。

以上是 套接字recv()使用MSG_WAITALL挂在大消息上 的全部内容, 来源链接: utcz.com/qa/422267.html

回到顶部