2016-11-23 39 views
0

我写与升压一个UDP服务器应用程序,应在插座上听5秒,如果没有数据包已经被这5秒内收到,请继续做其他事情。的std ::未来加速UDP套接字不工作异步接收操作

通过some answers的启发,我决定尝试基于的std ::未来的解决方案。

的问题是,调用wait_for()总是超时,就好像没有收到数据。但是,如果我在超时后执行的行上设置断点,并且检查变量,我会看到缓冲区包含收到的数据报,并且对象包含客户端的地址。换句话说,套接字接收按预期工作,但std :: future不会触发。为什么?

这里是我的测试服务器代码:

#include <future> 
#include <boost/asio.hpp> 
#include <boost/asio/use_future.hpp> 

using boost::asio::ip::udp; 

int main() 
{ 
    try 
    { 
     boost::asio::io_service io_service; 
     udp::socket socket(io_service, udp::endpoint(udp::v4(), 10000)); 
     char recv_buf[8]; 

     for (;;) 
     { 
      ZeroMemory(recv_buf, 8); 
      udp::endpoint remote_endpoint; 
      std::future<std::size_t> recv_length; 

      recv_length = socket.async_receive_from(
       boost::asio::buffer(recv_buf), 
       remote_endpoint, 
       0, 
       boost::asio::use_future); 

      if (recv_length.wait_for(
       std::chrono::seconds(5)) == std::future_status::timeout) 
      { 
       printf("time out. Nothing received.\n"); 
      } 
      else 
      { 
       printf("received something: %s\n", recv_buf); 
      } 
     } 
    } 
    catch (std::exception& e) 
    { 
     printf("Error: %s\n", e.what()); 
    } 
    return 0; 
} 

我一直在敲打我的头就这一个了一段时间,所以任何帮助,将不胜感激。我在Windows 10使用Visual Studio 2015年

这里是我的测试客户端代码(在python,抱歉)。

import socket 
import time 

HOST = "server"   # The remote host 
PORT = 10000    # The same port as used by the server 
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s: 
    address = socket.getaddrinfo(HOST, PORT)[0][-1] 

    while True: 
     s.sendto("ping\0", address) 
     time.sleep(1) 
+0

链接的问题和答案提到需要运行'io_service':“与调用线程将被阻塞,等待未来,至少一个其他线程必须处理'io_service'允许异步[... ]行动来实现和履行承诺。“ –

回答

0

我发现解决方案。所以要把它包括起来,这是需要做的。我最初的代码需要2次修改。

(1)将2线在开始启动一个单独的线程以io_service对象来监视超时(由唐纳桑斯伯里所建议的)

boost::asio::io_service::work work(io_service); 
std::thread thread([&io_service](){ io_service.run(); }); 

(2)调用socket.cancel();在插座的条件时间到。如果套接字操作不被取消,插座将继续,尽管(Boost的邮件列表上收到的解决方案),以wait_for()的再次呼吁阻止。

下面是引用修改代码:

#include <future> 
#include <boost/asio.hpp> 
#include <boost/asio/use_future.hpp> 

using boost::asio::ip::udp; 

int main() 
{ 
    try 
    { 
     boost::asio::io_service io_service; 
     boost::asio::io_service::work work(io_service); 
     std::thread thread([&io_service](){ io_service.run(); }); 

     udp::socket socket(io_service, udp::endpoint(udp::v4(), 10000)); 

     char recv_buf[8]; 

     for (;;) 
     { 
      ZeroMemory(recv_buf, 8); 
      udp::endpoint remote_endpoint; 
      std::future<std::size_t> recv_length; 

      recv_length = socket.async_receive_from(
       boost::asio::buffer(recv_buf), 
       remote_endpoint, 
       0, 
       boost::asio::use_future); 

      if (recv_length.wait_for(
       std::chrono::seconds(5)) == std::future_status::timeout) 
      { 
       printf("time out. Nothing received.\n"); 
       socket.cancel(); 
      } 
      else 
      { 
       printf("received something: %s\n", recv_buf); 
      } 
     } 
    } 
    catch (std::exception& e) 
    { 
     printf("Error: %s\n", e.what()); 
    } 
    return 0; 
} 

感谢大家的帮助。

3

您不打电话给io_service对象的run方法。因此asio没有运行。请创建一个调用run方法的线程,然后重试。

0

你在做什么是台异步和同步操作的组合,这是不工作:

  • 您正在使用异步async_receive_from操作,这将在一个ASIO事件循环(io_service)运行并完成时收到了一些东西。在正常情况下,这将在完成时调用回调函数,如果您将来能够完成未来的话。请注意,这将发生在调用io_service.run()
  • 的线程中您正在以同步方式使用未来。这将阻止当前的线程,直到未来完成。
  • 如果未来将从同一个线程实现,而不是您为了等待而阻止的那个线程,那显然永远不会实现。

可能的措施来解决此问题:

  • 只需使用ASIO的阻塞操作超时使用。这些就是你想要在未来实现的那种事情。
  • 使用期货then()方法为了附加一个继续而不是阻塞它。由于它是C++ 17的扩展,因此不适用于旧的stdlib future。然而提振期货可以做到这一点。您仍然需要在主线程上调用io_service.run(),并在回调之前和之后将程序拆分为阶段。
  • 运行ASIO和它的事件循环在后台线程,如果你需要
+0

请您介绍一下如何使用Asio的超时阻塞操作。我的印象是,超时需要异步操作。 –

+0

我猜想有一个超载超时,但似乎并非如此。但还有其他选择。例如。您可以直接在套接字上设置读/写超时选项。然后阻塞操作将利用它。这在接受的答案描述在这里:http://stackoverflow.com/questions/291871/how-to-set-a-timeout-on-blocking-sockets-in-boost-asio – Matthias247

+0

或者增强本身显示一个交叉plattform方法用于阻止读取,但是使用异步选项和io_service.run_one()隐藏:http://www.boost.org/doc/libs/1_52_0/doc/html/boost_asio/example/timeouts/blocking_tcp_client.cpp 看起来很理智,但如果你在同一个io_service上运行异步操作(因为在阻塞等待期间可能会调用这些操作的处理程序),我不会使用它。 – Matthias247

0

对于异步操作,底层的I/O,并完成处理程序的执行是不连续的步骤。在这种情况下,I/O已经完成,但用户代码永远不会运行io_service,所以永远不会执行,将设置recv_length的值完成处理。要解决此问题,请运行io_service


有有助于观察一些细节:

  • 当启动一个异步操作,如果它可以不受阻塞完成,那么它会这样做,它的完成处理程序将被排队进入使用boost::asio::use_future当作为-如果由io_service.post()
  • io_service,所述std::future的值被设置异步操作的完成处理程序内
  • 发布到个
  • 处理程序中当前调用的线程仅调用poll()poll_one()run(),并在io_service

在问题的上下文run_one()成员函数,当

recv_length = socket.async_receive_from(
    boost::asio::buffer(recv_buf), 
    remote_endpoint, 
    0, 
    boost::asio::use_future); 

启动和数据可被读取(socket.available() > 0),那么这两个remote_endpointrecv_buffer将与发起async_receive_from()福中正确的数据填充nction。这将设置recv_length的值完成处理程序发布到io_service。然而,由于代码不处理io_servicerecv_length的值永远不会设置。因此,recv_length.wait_for()将始终导致超时状态。


official futures example创建专用于从该不处理I/O服务线程内的处理上std::future的I/O服务,并等待另外的螺纹:

// We run the io_service off in its own thread so that it operates 
// completely asynchronously with respect to the rest of the program. 
boost::asio::io_service io_service; 
boost::asio::io_service::work work(io_service); 
std::thread thread([&io_service](){ io_service.run(); }); 

... 

std::future<std::size_t> send_length = 
    socket.async_send_to(..., boost::asio::use_future); 

// Do other things here while the send completes. 

send_length.get(); // Blocks until the send is complete. Throws any errors. 

io_service.stop(); 
thread.join(); 
+0

非常好的解释。我希望这是解决方案。我添加了代码来创建线程,并在开始时启动io_service,以及末尾的stop()和join(),但行为仍与最初描述的相同。 – jeancf

+0

@jeancf你添加了“工作”对象吗? –

+0

是的,我做到了。我也尝试使用'recv_length.get()'和'recv_length.wait()',并且两者都按预期工作:阻塞,直到收到数据。它只是'wait_for()'不正确。 – jeancf