首页 文章

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

提问于
浏览
2

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

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

  • 一个发送到套接字

  • 一个做select然后从同一个套接字recv

正如我仍在发送,客户端已经收到数据并关闭了套接字 . 同时,我正在对该套接字执行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 回答

  • 4

    我必须承认,当你处理线程时,我总是有可能 . 当您调用 send() 时,您很可能会猜测您正在本地网络上进行测试,因此另一端接收数据并关闭连接并将相应的FIN发送回您的终端 . 这可能发生在发送机器仍在运行其他线程或进程时,因为本地以太网网络上的延迟非常低 .

    现在FIN到了 - 你的接收线程没有't done a lot lately since it'等待输入 . 因此,许多调度系统将相当多地提高其优先级并且接下来会运行(例如,您不会使用,但这可能至少在Linux上发生) . 由于零读取,该线程关闭套接字 . 在此之后的某个时刻,发送线程将被重新唤醒,但可能是内核注意到套接字在从被阻止的 send() 返回之前关闭并返回 EBADF .

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

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

    作为一般规则,您不应该确定发送缓冲区已经完全刷新 - 只有在 send() 调用返回后才能确定这一点并指示所有剩余数据已经消失 . 我've handled this in the past by checking the send buffer when I get a zero read and if it' s不为空我设置"closing"标志 . 在你的情况下,发送线程然后将使用它作为提示,一旦刷新一切就关闭 . 这很重要,因为如果远程端使用 shutdown() 进行半关闭,那么即使它仍然可以读取,您也将获得零读取 . 您可能不关心半关闭,但在这种情况下,您的策略是正常的 .

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

  • 5

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

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

相关问题