从不同的线程中的相同套接字发送和接收不起作用

时间:2013-03-12 23:19:44

标签: c++ sockets tcp send recv

我读到它应该同时安全地从不同的线程,但我的程序有一些奇怪的行为,我不知道什么是错的。

我有并发线程与客户端套接字进行通信

  1. 一个发送到套接字
  2. 一个做select然后从同一个套接字recv
  3. 当我还在发送时,客户端已经收到了数据并关闭了套接字。 同时,我正在对该套接字执行select和recv,它返回0(因为它已关闭)所以我关闭了这个套接字。但是,发送还没有返回...因为我在这个套接字上调用close,所以发送调用因EBADF而失败。

    我知道客户端已正确接收数据,因为我在关闭套接字后输出它并且它是正确的。但是,在我的结尾,我的发送调用仍然返回错误(EBADF),所以我想修复它,以便它不会失败。

    这并不总是发生。它可能发生在40%的时间。我不在任何地方睡觉。我应该在发送或接收之间暂停一下吗?

    以下是一些代码:

    发送:

    while(true)
    {
        // keep sending until send returns 0
        n = send(_sfd, bytesPtr, sentSize, 0);
    
        if (n == 0)
        {
            break;
        }
        else if(n<0)
        {
            cerr << "ERROR: send returned an error "<<errno<< endl; // this case is triggered
            return n;
        }
    
        sentSize -= n;
        bytesPtr += n;
    }
    

    接收:

     while(true)
    {
        memset(bufferPointer,0,sizeLeft);
        n = recv(_sfd,bufferPointer,sizeLeft, 0);
        if (debug) cerr << "Receiving..."<<sizeLeft<<endl;
        if(n == 0)
        {
            cerr << "Connection closed"<<endl; // this case is triggered
            return n;
        }
        else if (n < 0)
        {
            cerr << "ERROR reading from socket"<<endl;
            return n;
        }
         bufferPointer += n;
         sizeLeft -= n;
         if(sizeLeft <= 0) break;
    
    }
    

    在客户端上,我使用相同的接收代码,然后在套接字上调用close()。 然后在我身边,我从接收呼叫中得到0,并且还在套接字上调用close() 然后我的发送失败。它还没有完成?!但我的客户已经获得了数据!

2 个答案:

答案 0 :(得分:5)

我必须承认,我很惊讶你经常看到这个问题,但是当你处理线程时,它总是有可能。当你调用send()时,你最终会进入内核将数据附加到那里的套接字缓冲区,因此很可能会有一个上下文切换,可能是系统中的另一个进程。同时内核可能很快就缓冲并传输了数据包。我猜你正在本地网络上进行测试,所以另一端接收数据并关闭连接,并很快将适当的FIN发送回你的终端。这可能发生在发送机器仍在运行其他线程或进程时,因为本地以太网网络上的延迟非常低。

现在FIN到了 - 你的接收线程最近没有做太多,因为它一直在等待输入。因此,许多调度系统将提高其优先级,并且很有可能它将在下一次运行(您没有指定您正在使用哪个操作系统,但这可能至少在Linux上发生)。由于零读取,该线程关闭套接字。在此之后的某个时刻,发送线程将被重新唤醒,但可能是内核注意到套接字在从被阻止的send()返回之前已关闭并返回EBADF

现在这只是关于确切原因的猜测 - 除其他外,它在很大程度上取决于您的平台。但是你可以看到这是如何发生的。

最简单的解决方案可能是在发送线程中使用poll(),但是等待套接字变为可写入而不是读取就绪。显然你还需要等到有任何缓冲数据要发送 - 你如何做到这一点取决于哪个线程缓冲数据。 poll()调用可让您通过使用POLLHUP标记连接来检测连接何时关闭,您可以在尝试send()之前检测到该连接。

作为一般规则,在确定发送缓冲区已完全刷新之前,不应关闭套接字 - 只有在send()调用返回后才能确定这一点,并指示所有剩下的数据已经消失。我在过去通过检查发送缓冲区来处理这个,当我得到零读取时,如果它不为空,我设置一个“关闭”标志。在你的情况下,发送线程然后将使用它作为提示,一旦刷新一切就关闭。这很重要,因为如果远程端使用shutdown()进行半关闭,那么即使它仍然可以读取,您也将获得零读取。您可能不关心半关闭,但是,在这种情况下,您的策略是正常的。

最后,我个人会避免发送和接收线程的麻烦,并且只有一个线程同时执行这两者 - 这或多或少是select()poll()的点,以允许单个线程处理一个或多个文件句柄的执行,而不必担心执行阻塞和饿死其他连接的操作。

答案 1 :(得分:4)

发现问题。这是我的循环。请注意,这是一个无限循环。当我没有剩余要发送时,我的sentSize是0,但我仍然会循环尝试发送更多。此时,另一个线程已经关闭了这个线程,因此我的0字节发送调用返回错误。

我通过更改循环来修复它,当sentSize为0时停止循环并修复了问题!