【问题标题】:What can I do to avoid TCP Zero Window/ TCP Window Full on the receiver side?我可以做些什么来避免接收方的 TCP 零窗口/ TCP 窗口已满?
【发布时间】:2011-03-26 21:36:00
【问题描述】:

我有一个小型应用程序,它通过网络将文件发送到位于 Windows 操作系统上的代理。

当此应用程序在 Windows 上运行时,一切正常,通信正常,文件全部复制成功。

但是,当这个应用程序在 Linux 上运行时(RedHat 5.3,接收器仍然是 Windows) - 我在 Wireshark 网络跟踪消息中看到 TCP Zero Window 和 TCP Window Full 每隔 1-2 秒出现一次。然后代理会在几分钟后关闭连接。

Windows - Linux 代码几乎相同,而且非常简单。唯一重要的操作是带有 SO_SNDBUF 和值为 0xFFFF 的 setsockopt。删除此代码没有帮助。

有人可以帮我解决这个问题吗?

编辑:添加发送代码 - 看起来它可以正确处理部分写入:

int totalSent=0;
while(totalSent != dataLen)
{
    int bytesSent 
        = ::send(_socket,(char *)(data+totalSent), dataLen-totalSent, 0);

    if (bytesSent ==0) {
        return totalSent;
    }
    else if(bytesSent == SOCKET_ERROR){
#ifdef __WIN32
        int errcode = WSAGetLastError();
        if( errcode==WSAEWOULDBLOCK ){
#else
            if ((errno == EWOULDBLOCK) || (errno == EAGAIN)) {
#endif
            }
            else{
                if( !totalSent ) {
                    totalSent = SOCKET_ERROR;
                }
                break;
            }
        }
        else{
            totalSent+=bytesSent;
        }
    }
}

提前致谢。

【问题讨论】:

  • 更多详情?文件传输是否成功,只是传输速度较慢,还是传输失败?如果它失败了,它在哪里失败了?有什么东西通过了还是中途失败了?
  • @罗伯特,谢谢。传输失败。例如,如果我传输的文件夹包含 2 GB 的 3 KB - 50 KB 文件,它有时会传输 ~0.5 GB,有时会传输 ~1.3 GB 的数据,然后失败。
  • 您收到了哪些错误消息以及哪一方正在关闭连接?您使用的是阻塞还是非阻塞 I/O。你有专门的线程做 I/O 吗?越详细越好,如果能贴出代码片段就最好了。
  • 什么是::send(...)?这是包装标准send(...) 函数的类的成员吗?
  • 您也可以发布接收代码吗?听起来数据可能无法在接收端提取。

标签: c++ tcp cross-platform network-programming


【解决方案1】:

没有看到你的代码,我只能猜测。

在 TCP 中获得零窗口的原因是接收器的接收缓冲区中没有空间。

发生这种情况的方式有很多种。此问题的一个常见原因是当您通过 LAN 或其他相对较快的网络连接发送数据时,一台计算机的速度明显快于另一台计算机。举个极端的例子,假设您有一台 3Ghz 计算机通过千兆以太网尽可能快地向另一台运行 1Ghz cpu 的机器发送数据。由于发送方的发送速度比接收方读取的速度快得多,因此接收方的接收缓冲区将填满,导致 TCP 堆栈向发送方通告零窗口。

现在,如果发送方和接收方都没有准备好处理这个问题,这可能会导致发送方和接收方出现问题。在发送端,如果您使用非阻塞 I/O,这可能会导致发送缓冲区填满并调用发送阻塞或失败。在接收端,您可能会在 I/O 上花费大量时间,以至于应用程序没有机会处理任何数据,并且看起来像是被锁定了。

编辑

从您的一些答案和代码看来,您的应用是单线程的,并且出于某种原因您正尝试进行非阻塞发送。我假设您在代码的其他部分将套接字设置为非阻塞。

一般来说,我会说这不是一个好主意。理想情况下,如果您担心您的应用挂在send(2) 上,您应该使用setsockopt 在套接字上设置较长的超时时间,并使用单独的线程进行实际发送。

socket(7):

SO_RCVTIMEO 和 SO_SNDTIMEO 指定接收或发送超时,直到报告错误。这 参数是一个结构时间。如果 输入或输出功能块 这段时间,数据已经 发送或接收,返回值 该函数将是 传输的数据;如果没有数据 已转移且已超时 达到然后 -1 与 errno 返回 设置为 EAGAIN 或 EWOULDBLOCK 就像 如果套接字被指定为 非阻塞。如果超时设置为 零(默认)然后操作 永远不会超时。

您的主线程可以将每个文件描述符推送到 queue 中,例如使用 boost mutex 进行队列访问,然后启动 1 - N 个线程以使用具有发送超时的阻塞 I/O 进行实际发送。

您的发送函数应如下所示(假设您设置了超时):

// blocking send, timeout is handled by caller reading errno on short send
int doSend(int s, const void *buf, size_t dataLen) {    
    int totalSent=0;

    while(totalSent != dataLen)
    {
        int bytesSent 
            = send(s,((char *)data)+totalSent, dataLen-totalSent, MSG_NOSIGNAL);

        if( bytesSent < 0 && errno != EINTR )
            break;

        totalSent += bytesSent;
    }
    return totalSent;
}

MSG_NOSIGNAL 标志确保您的应用程序不会因写入已被对等方关闭或重置的套接字而被杀死。有时 I/O 操作会被信号中断,检查 EINTR 允许您重新启动 send

通常,您应该在循环中调用doSend,并使用TCP_MAXSEG 大小的数据块。

在接收端,您可以在单独的线程中使用超时编写类似的阻塞 recv 函数。

【讨论】:

  • 感谢这篇文章。它非常有用,尤其是 MSG_NOSIGNAL,我认为这是我的一个应用程序的问题。
【解决方案2】:

使用 TCP 套接字进行开发时的一个常见错误是关于 read()/write() 行为的错误假设。

当你执行读/写操作时,你必须检查返回值,他们可能没有读/写请求的字节,你通常需要一个循环来跟踪并确保整个数据被传输。

【讨论】:

  • 仅供参考,在 java 中的 read 和 write 方法返回类型是无效的。如何检查返回值
【解决方案3】:

最可能的问题是您的代码中有一个错误,您无法正确处理部分读取或部分写入。众所周知,Linux 和 Windows 之间的 TCP 可以工作。

【讨论】:

    【解决方案4】:

    我尝试禁用 Nagle 的算法(使用 TCP_NODELAY),不知何故,它有所帮助。 传输速率要高得多,TCP 窗口大小未满或未重置。 奇怪的是,当我改变窗口大小时,它没有任何影响。

    谢谢。

    【讨论】:

    • 这真的很奇怪。通常禁用 Nagle 仅对您希望以浪费带宽为代价获得非常低延迟的实时应用程序有用。禁用它以进行批量文件传输似乎违反直觉。您是否实际测试并客观地看到禁用 Nagle 是什么造成了不同?也许你所做的其他一些改变可能是负责任的?
    • @Robert S. Barnes:这真的很奇怪,我同意。但这是唯一做出的改变,而且它有所帮助。此外,接收方已经禁用了 Nagle。我知道它可能指的是隐藏在某个地方的潜在基本问题,等待在另一个时间跳出来咬人。但作为一种解决方法,它已经足够好了。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-12-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多