使用同一个UDP套接字进行异步接收/发送

7
我在我的UDP服务器中使用相同的套接字来接收客户端在某个端口上发送的数据,稍后在处理请求后使用ip::ud::socket ::async_send_to回复给客户端。
使用async_receive_from异步执行接收操作。套接字使用相同的ioService(毕竟是同一个套接字)。文档没有清楚地说明是否可以同时从客户端A(以异步方式)接收数据报文,并可能向客户端B(异步发送)发送另一个数据报文。我怀疑这可能会导致问题。我最终使用相同的套接字进行回复,因为在回复另一个客户端时无法将另一个套接字绑定到相同的服务器端口。
如何将另一个套接字绑定到相同的服务器端口?
编辑。我尝试使用以下方式将第二个UDP套接字绑定到相同的UDP端口:
socket(ioService, boost::asio::ip::udp::endpoint(boost::asio::ip::udp::v4(), port))

第一次执行此操作(绑定服务器的“接收”套接字),它是可以的,但第二次尝试创建另一个套接字时,例如在绑定时报告错误(asio抛出异常)。


2
请明确您的实际问题。您的设置是否正常工作,并且您正在寻求确认它是否符合设计要求?还是它没有工作,如果是这样,您观察到了什么错误?或者您是在询问如何将另一个套接字绑定到服务器端口? - mtrw
请编辑您的问题以澄清,而不是添加评论。 - Sam Miller
我更新了你的问题,包括实际的问题。如果您能发布一小段代码来显示您尝试绑定第二个套接字以及发生的错误,那将会很有帮助。 - mtrw
1
我认为这里的关键问题是尝试使用相同的端口绑定多个套接字,这是不可能且没有意义的。 - E net4
3
使用UDP协议确实可以通过SO_REUSEADDR来实现,但我同意这是没有意义的。关于套接字(sockets):它们从底层全双工,你可以同时读取和写入它们。 - user207421
显示剩余2条评论
1个回答

16

可以同时从一个远程端点接收和向另一个远程端点发送UDP套接字。但是,根据Boost.Asio Threads and Boost.Asio文档,单个对象的并发调用通常是不安全的。

因此,这是安全的:

 thread_1                             | thread_2
--------------------------------------+---------------------------------------
socket.async_receive_from( ... );     |
socket.async_send_to( ... );          |

这也是安全的:

 thread_1                             | thread_2
--------------------------------------+---------------------------------------
socket.async_receive_from( ... );     |
                                      | socket.async_send_to( ... );

但是,这被指定为不安全的:

 thread_1                             | thread_2
--------------------------------------+---------------------------------------
socket.async_receive_from( ... );     | socket.async_send_to( ... );
                                      |

请注意,一些函数(例如boost::asio::async_read)是组合操作,并具有额外的线程安全限制。


如果以下任一条件为真,则无需进行额外的同步,因为流程将隐式同步:
  • All socket calls occur within handlers, and io_service::run() is only invoked from a single thread.
  • async_receive_from and async_send_to are only invoked within the same chain of asynchronous operations. For example, the ReadHandler passed to async_receive_from invokes async_send_to, and the WriteHandler passed to async_send_to invokes async_receive_from.

    void read()
    {
      socket.async_receive_from( ..., handle_read );  --.
    }                                                   |
        .-----------------------------------------------'
        |      .----------------------------------------.
        V      V                                        |
    void handle_read( ... )                             |
    {                                                   |
      socket.async_send_to( ..., handle_write );  --.   |
    }                                               |   |
        .-------------------------------------------'   |
        |                                               |
        V                                               |
    void handle_write( ... )                            |
    {                                                   |
      socket.async_receive_from( ..., handle_read );  --'
    }
    
另一方面,如果有多个线程可能同时调用套接字,则需要进行同步。可以通过通过boost::asio::io_service::strand调用函数和处理程序来执行同步,或者使用其他同步机制,例如Boost.Thread的mutex
除了线程安全性之外,还必须考虑对象生命周期的管理。如果服务器需要同时处理多个请求,则要小心每个请求->处理->响应链中bufferendpoint的所有权。根据async_receive_from的文档,调用者保留bufferendpoint的所有权。因此,通过boost::shared_ptr管理对象的生命周期可能更容易。否则,如果链足够快以至于不需要并发链,则简化了管理,允许在每个请求中使用相同的bufferendpoint

最后,socket_base::reuse_address 类允许将套接字绑定到已经被占用的地址。然而,我认为这并不是适用的解决方案,因为它通常用于:

  • TCP:即使端口处于 TIME_WAIT 状态,允许进程重新启动并监听同一端口。
  • UDP:允许多个进程绑定到同一端口,使每个进程能够通过组播接收和广播。

相当令人印象深刻的答案。对于我的情况来说,我只有一个ioService线程(主线程),但即使不能交错调用async_receive_from/async_send_to,其中一个可以在另一个完成之前被调用(调用另一个的完成处理程序)。这是因为我在另一个与ioService线程无关的线程中进行处理。 - Ghita
另外一件事。在哪里规定了端点类必须以特定方式处理。一旦我在async_receive_from中将端点作为输出参数接收到,我会使用复制构造函数对其进行复制,并将其传递为“状态”(以便我知道稍后要响应的位置),直到我需要发送响应为止。 - Ghita
2
在一个对象上有多个异步操作挂起是可以的;只是指定并发调用对象是不安全的。如果套接字调用来自处理程序之外,并且只有一个线程调用io_service::run(),那么将套接字调用发布到io_service将进行同步。Boost.Asio要求调用者保证某些参数在处理程序被调用之前保持有效;它从不强制用户以特定方式管理它们。在某些情况下,通过智能指针将对象的生命周期与异步链相关联可能更容易。 - Tanner Sansbury
asio-users邮件列表中的帖子似乎证实了您所说的。我将把您的答案标记为已接受。 - Ghita
顺便问一下,如果您在共享中使用此方法,在完成处理程序中没有进行相应的异步调用时,是否保证一旦离开处理程序,就会被删除? - Ghita
如果这是共享指针的最后一个副本,那么是的。所有处理程序的副本以及其对应的参数(即共享指针的副本)在处理程序调用之后立即销毁。有关更多详细信息,请参阅此链接中的问题。 - Tanner Sansbury

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