从不同线程在同一个套接字上发送和接收不起作用

Posted

技术标签:

【中文标题】从不同线程在同一个套接字上发送和接收不起作用【英文标题】:send and recv on same socket from different threads not working 【发布时间】:2013-02-28 17:30:49 【问题描述】:

我读到它应该对不同线程同时进行是安全的,但是我的程序有一些奇怪的行为,我不知道出了什么问题。

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

    发送到套接字的人 一个选择然后从同一个套接字接收

由于我还在发送,客户端已经收到数据并关闭了套接字。 同时,我正在对该套接字进行选择和接收,它返回 0(因为它已关闭)所以我关闭了这个套接字。但是,发送还没有返回......由于我在这个套接字上调用 close,发送调用失败并出现 EBADF。

我知道客户端已经正确接收到数据,因为我在关闭套接字后输出它并且它是正确的。但是,就我而言,我的 send 调用仍然返回错误 (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() 然后我的发送失败。还没完结?!但是我的客户已经得到了数据!

【问题讨论】:

请添加一些代码。可能存在与您怀疑无关的错误。 谢谢,我会添加代码 总是可以给套接字一个引用计数。 @CaptainObvlious 你什么意思?保留参考如何防止错误?我认为同时发送和接收可以吗? 【参考方案1】:

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

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

现在这只是对确切原因的猜测 - 其中很大程度上取决于您的平台。但是您可以看到这是如何发生的。

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

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

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

【讨论】:

感谢您的回答。您描述的问题似乎很有可能,但我认为我没有遇到它,因为它似乎需要非常具体的情况,而且我得到的错误经常发生。另外,我需要使用多个线程,因为它是学校的作业规范。结束标志的评论听起来很有用,如果是为了工作,我会补充一点,但这只是为了标记哈哈。 @cartroo - 当 recv 是阻塞调用时,如何在同一个套接字和同一个线程上接收和发送? poll() 和 select() 听起来像是完全用于多个套接字的。 @JoeManiaci send()recv() 都不一定是阻塞操作。首先,它们仅在无法取得任何进展时才会阻塞,否则它们会返回部分成功 - select()poll() 的重点是检测何时可能成功(即它们不会阻塞)。其次,您可以使用O_NONBLOCK 选项使套接字变为非阻塞。第三,select()poll() 可以在单个套接字上正常工作——它们让您可以在尽可能多的描述符上等待多个事件,甚至是一个。它们被设计为可扩展到多个,但这并不会阻止您将它们与一个一起使用。 但是澄清一下,如果您使用的是select()poll(),那么原则上您不需要O_NONBLOCK。如果您小心,这些呼叫将向您保证,您可以根据您收到的事件对send()recv() 进行一次呼叫,并且不会阻塞。大多数人仍然使用O_NONBLOCK,以防万一他们在某个地方犯了一个小错误,或者为了能够循环send() / recv() 以一次性提取所有数据,即使缓冲区大小固定。但这是实施者的选择。【参考方案2】:

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

我通过将循环更改为在 sentSize 为 0 时停止循环来修复它,它解决了问题!

【讨论】:

嘿,我现在自己才发现。那会教我在深夜睡觉前回答问题!我在回答中谈到的问题仍然有效,但正如我所说,您不会期望经常看到它们。

以上是关于从不同线程在同一个套接字上发送和接收不起作用的主要内容,如果未能解决你的问题,请参考以下文章

PHP/Java 套接字通信不起作用

Posix消息队列接收/发送/打开不起作用?

AsyncTask和套接字在android java中不起作用

为啥使用套接字源和多个接收器的流式查询不起作用?

为啥 subscribe() 不起作用但模板中的异步可以?

套接字:客户端读取消息时多线程不起作用