php socket_recv() msg_waitall,Socket recv()挂在MSG_WAITALL的大消息上

我有一个应用程序从服务器读取大型文件,并经常挂在特定的机器上。 它已经在RHEL5.2下成功运行了很长一段时间。 我们最近升级到RHEL6.1,现在它定期挂起。

我已经创build了一个重现问题的testing应用程序。 它挂起100次约98次。

#include #include #include #include #include #include #include #include #include #include #include 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; }

从我testing的一些笔记:

如果“localhost”映射到回送地址127.0.0.1,则应用程序挂起对recv()的调用,并且不返回。

如果“本地主机”映射到机器的IP,因此通过以太网接口路由数据包,应用程序成功完成。

当我遇到挂起时,服务器发送一个“TCP Window Full”消息,并且客户端响应“TCP ZeroWindow”消息(请参阅图像和附加的tcpdump捕获)。 从这一点来看,服务器发送keepalive和客户端发送ZeroWindow消息永远挂起。 客户似乎从来没有扩大其窗口,允许转移完成。

在挂起期间,如果我检查“netstat -a”的输出,则服务器中有数据发送队列,但客户端接收队列是空的。

如果我从recv()调用中删除MSG_WAITALL标志,则应用程序成功完成。

挂起的问题只出现在1台特定的机器上使用回送接口。 我怀疑这可能都与时间依赖性有关。

当我放弃'文件'的大小,挂起发生的可能性减less

testing应用程序的来源可以在这里find:

套接字testing源

从loopback接口捕获的tcpdump可以在这里find:

tcpdump捕获

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

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

这看到6000000字节发送到testing应用程序尝试读取数据使用recv()的单个调用。

我很乐意听到关于我可能做错了什么的build议或任何进一步debugging问题的方法。

应该阻塞MSG_WAITALL直到收到所有数据。 从recv手册页面 :

该标志请求操作块直到完整的请求被满足。

但是,网络堆栈中的缓冲区可能不足以包含所有内容,这是服务器上的错误消息的原因。 客户端网络堆栈根本不能容纳那么多的数据。

解决方法是增加缓冲区大小( SO_RCVBUF选项setsockopt ),将消息拆分成更小的块,或接收更小的块放入你自己的缓冲区。 最后是我会建议。

编辑:我在你的代码中看到,你已经做了我所建议的(用自己的缓冲区读取更小的块),所以只要删除MSG_WAITALL标志,它应该工作。

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

考虑这两个可能的规则:

接收者可能会在收到已发送的内容之前等待发送者发送更多内容。

发送者可以等待接收者在发送更多之前接收已经发送的内容。

我们可以有这些规则中的任何一个,但是我们不能同时拥有这些规则。

为什么? 因为如果接收方被允许等待发送方,这意味着发送方在发送之前不能等待接收方接收,否则我们会死锁。 而且如果发送者被允许等待接收者,那就意味着接收者不能等待发送者接收更多的发送,否则我们会死锁。

如果这两件事情同时发生,我们就陷入僵局。 在接收方收到已发送的内容之前,发送方不会再发送更多内容,除非发送方发送更多内容,否则接收方将不会收到已经发送的内容。 繁荣。

TCP选择规则2(原因应该是显而易见的)。 因此它不能支持规则1.但是在你的代码中,你是接收者,并且在你接收到已经发送的内容之前,你正在等待发送者发送更多的内容。 所以这会陷入僵局。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值