std :: future不能在Boost UDP套接字异步接收操作中工作

时间:2016-11-23 21:49:25

标签: c++ boost udp boost-asio future

我正在编写一个带有Boost的UDP服务器应用,应该在套接字上监听5秒钟,如果在这5秒内没有收到数据报,继续做其他事情。

some answers的启发,我决定尝试基于std :: future的解决方案。

问题是对wait_for()的调用总是超时,好像没有收到数据一样。但是如果我在超时后执行的行上设置断点并且我检查变量,我会看到缓冲区包含接收的数据报,remote_endpoint对象包含客户端的地址。换句话说,套接字接收按预期工作,但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;
}

我一直在敲打这个问题,所以任何帮助都会受到赞赏。我在使用Visual Studio 2015的Windows 10上。

这是我的测试客户端代码(在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)

4 个答案:

答案 0 :(得分:3)

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

答案 1 :(得分:0)

您正在做的是混合使用异步和同步操作,但这些操作无效:

  • 您正在使用异步async_receive_from操作,该操作将在asio事件循环(io_service)上运行,并在收到任何内容时完成。在正常情况下,这将在完成时调用回调,如果你给它未来它将完成未来。请注意,这将在调用io_service.run()
  • 的线程中发生
  • 您正在以同步方式使用未来。这将阻止当前线程,直到将来完成。
  • 如果未来将通过与您阻止的线程相同的线程来实现,以便等待它,那么显然永远无法实现。

解决此问题的可能步骤:

  • 只需使用asio的阻止操作即可。这些完全符合您希望未来实现的目标。
  • 使用期货then()方法以附加延续而不是阻止。不适用于旧的stdlib期货,因为它是C ++ 17扩展。然而,提振期货可以做到。您仍然需要在主线程上调用io_service.run()并将程序拆分为回调之前和之后的阶段。
  • 如果需要
  • ,请在后台线程中运行asio及其事件循环

答案 2 :(得分:0)

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

有一些细节有助于观察:

  • 当启动异步操作时,如果它可以在没有阻塞的情况下完成,那么它将执行此操作,并且其完成处理程序将排列到io_service as-if {{3} }
  • 使用boost::asio::use_future时,std::future的值在异步操作的完成处理程序中设置
  • 发布到io_service
  • 处理程序仅在当前调用poll()poll_one()run()run_one()成员函数的线程中调用io_service

在问题的背景下,何时

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()功能中填充正确的数据。设置recv_length值的完成处理程序将发布到io_service。但是,由于代码不处理io_service,因此永远不会设置recv_length的值。因此,recv_length.wait_for()将始终导致超时状态。

io_service.post()创建了一个额外的线程,专门用于处理I / O服务,并在未处理I / O服务的线程内等待std::future

// 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();

答案 3 :(得分:0)

我找到了解决方案。所以要把它包起来,这是需要做的。我的初始代码需要2次修改。

(1)在开头添加2行,用io_service启动一个单独的线程来监控超时(正如Tanner Sansbury所建议的那样)

<add name="ConnectionString_LIVE_customer_support" connectionString="server=REMOVED;port=REMOVED;User Id=REMOVED;password=REMOVED;Persist Security Info=True;database=customer_support;Sql Server Mode=True;Allow User Variables=True" providerName="MySql.Data.MySqlClient" />

(2)在socket time_out的条件下调用boost::asio::io_service::work work(io_service); std::thread thread([&io_service](){ io_service.run(); }); 。如果未取消套接字操作,则尽管重新调用socket.cancel();(在Boost&#39的邮件列表中收到解决方案),套接字仍将保持阻塞状态。

以下是修订后的参考代码:

wait_for()

谢谢大家的帮助。