正确关闭阻塞UDP套接字的方法

10

我有一个C++对象,它创建一个线程以从阻塞的UDP套接字中读取数据:

mRunning.store(true);
while (mRunning.load(boost::memory_order_consume)) {
    ...
    int size = recvfrom(mSocket, buf, kTextBufSize , 0,
                        (struct sockaddr *) &packet->mReplyAddr.mSockAddr, (socklen_t*)&packet->mReplyAddr.mSockAddrLen);

    if (size > 0) {
        //do stuff
    }
}
return 0;

(mRunning 是一个 boost::atomic 类型的对象) 该对象的析构函数会在另一个线程中被调用,并且会执行以下操作:

mRunning.store(false);  
#ifdef WIN32
    if (mSocket != -1) closesocket(mSocket);
#else
    if (mSocket != -1) close(mSocket);
#endif
pthread_join(mThread, NULL);

这似乎可以工作,但我的一个同事提出了一个问题:如果在读取数据时recv被中断,是否会出现问题?这是线程安全的吗?关闭阻塞的UDP套接字的正确方法是什么?(需要跨平台OSX/Linux/Windows)

3个回答

5

可能会有很多不同的问题。我将我的应用程序从一个FreeBSD版本移植到另一个版本时,发现在旧内核上close()函数正常工作,在新内核上close()函数挂起直到从recv()函数返回。而OSX是基于FreeBSD的 :)

跨线程关闭套接字的便携式方法是创建管道,并且在select()中阻塞,而不是在recv()中阻塞。当您需要关闭套接字时,向管道写入一些内容,select()将解除阻塞,然后您可以安全地执行close()。


1
我认为这实际上在跨平台方面不会起作用,因为在Windows上,select()似乎只支持套接字,而不支持管道。 - Müllmusik
1
在Windows中,您可以有一个单独的代码,使用WSAEventSelect()和WaitForMultipleObjects()。 - blaze

1

recvfrom本身是线程安全的。我IRC所有套接字函数都是线程安全的。问题是:

  • 如果在recvfrom将数据复制到您的缓冲区时从下面拉出描述符会发生什么?

这是一个很好的问题,但我怀疑标准对此没有任何规定(我也怀疑实现的特定手册对此有任何规定)。因此,任何实现都可以自由地执行以下操作:

  • 完成操作(可能是因为它不再需要描述符,或者因为它正在执行一些酷炫的引用计数或其他操作)
  • 使recvfrom失败并返回-1ENOTSOCKEINVAL?)
  • 崩溃,因为close释放了缓冲区和内部数据结构。

显然,这只是猜测(我以前犯过很多错误),但除非您在标准中找到支持通过关闭套接字来接收数据的想法的内容,否则您不安全。

那么,你能做什么?最安全的方式:使用同步机制确保只有在 recvfrom 完成后才 close socket(信号量、互斥锁等)。

个人建议,在 recvfrom 后执行 UP 操作,关闭之前执行 DOWN 操作。


0

你的同事是对的,boost sockets 不是线程安全的。

你有以下几个选择:

  1. 使用 ASIO(推荐)
  2. 在阻塞调用上设置超时。虽然这种方法可能可行,但并不是很便携。

2
它们不是Boost套接字,而是POSIX套接字。Boost只是为了确保更改mRunning布尔值是原子的。抱歉,当我将其粘贴并且没有注意到时,它被搞砸了。 - Müllmusik

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接