我该怎么做才能避免接收端的TCP Zero Window / TCP Window Full?

时间:2010-08-08 07:53:40

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

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

当这个应用程序在Windows上运行时,一切正常,通信正常,文件都被成功复制。

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

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

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

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

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

提前致谢。

4 个答案:

答案 0 :(得分:12)

没有看到你的代码,我不得不猜测。

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

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

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

修改

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

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

请参阅socket(7)

  

SO_RCVTIMEO和SO_SNDTIMEO       指定接收或发送超时,直到报告错误。该   参数是struct timeval。如果   输入或输出功能块   这段时间,数据已经过去了   发送或接收,返回值   那个功能将是金额   数据传输;如果没有数据   转移和超时已经   达到-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函数。

答案 1 :(得分:1)

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

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

答案 2 :(得分:0)

最可能的问题是您的代码中存在错误,您无法正确处理部分读取或部分写入。已知Linux和Windows之间的TCP可以正常工作。

答案 3 :(得分:0)

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

谢谢。