使用Boost Asio进行多个异步网络客户端操作

3

我想使用boost::asio(或者asio独立库)通过异步套接字每分钟向多个网络设备查询数据。为了测试,我已经实现了一个Client类和一个控制台程序来对一个设备进行查询(无重复)。

class MyClient
{
public:
    MyClient(asio::io_service& io_service);

    void GetData(CompletionHandler completionHandler);
}; 

MyClient::GetData类内部使用多个异步操作,每个操作的完成都会触发下一个操作直到数据可用:
  • 连接
  • 读取头文件
  • 读取数据
  • 断开连接
使用该类的控制台程序的工作流程如下:
int main(...)
{
    asio::io_service io_service_;

    MyClient c(io_service_, ...);
    ...
    c.GetData([](std::error_code ec, const FloatVector& values){
        //do something with values
    });

    io_service_.run();
    ...
}

现在我想在GUI程序中使用MyClient类,每分钟连接>10个设备,但我卡在整体设计上了。
首先,我创建了一个线程池,其中每个线程执行单个io_service实例的io_service::run()。
现在,每当我的程序想要从设备读取数据时,它都必须循环遍历所有设备,并且必须为每个设备创建MyClient实例并调用GetData()方法。
那么,这如何与io_service一起工作,现在io_service::run()在池的线程中执行?我可以直接在GUI线程中调用MyClient::GetData(),因为它在内部使用异步操作吗?还是我必须调用像io_service::post()这样的东西?
更新: 我的代码和控制台演示程序大致遵循此示例:www.boost.org/doc/libs/1_36_0/doc/html/boost_asio/example/http/client/async_client.cpp 但在GUI程序中,我不想在GUI线程中运行io_service.run()。现在假设我至少有一个额外的线程执行io_service.run(),并且用户按下应该启动设备读取的按钮。最终完成处理程序应将数据存储在数据库中并向用户更新图形显示。
也许按钮处理程序可以简单地实例化MyClient并在其上调用GetData(),并且一切正常,因为MyClient知道io_service并在异步连接等方面使用它。
是这样工作的还是我在这里弄错了?
注意:此时我的问题不是如何处理完成处理程序中的数据!正确获取多线程GUI程序中的数据是我的问题。

用户可以从GUI界面上做什么?GUI只是显示这些设备的结果吗?考虑一个UI线程和另一个单独的线程与所有执行io_service::run()的设备进行通信。asio线程通常会在获取到最新数据后向GUI线程发布消息,然后GUI线程处理该事件并显示结果... - Guy Sirton
2个回答

1

请看这个例子 www.boost.org/doc/libs/1_36_0/doc/html/boost_asio/example/http/client/async_client.cpp。它应该会有所帮助。

如果您看到handle_resolve成功,它将调用async_connect,这将导致handle_connect被触发。 如果handle_connect在没有错误的情况下被调用,则会向连接写入一些数据(async_write),这反过来会调用async_read_until(在没有错误的情况下),它将触发handle_read_status_line,可能会触发handle_read_headers,然后可能会触发handle_read_content。
如果您发现没有显式断开连接,则析构函数将在内部执行此操作。


是的,现在我大致上就有了一个客户端类和io_service :: run()在控制台程序中相同的线程中。如何将其推进到GUI程序?我将尝试更新我的问题以使其更清晰。 - NicolasR

1
这是您需要做的大致概述:
  • 首先请注意,如果io_service没有任何工作要执行,则io_service.run()将立即返回。因此,根据您的工作流程,您可能需要推迟调用它,直到您实际上已经排队了第一个异步连接。如果您查看示例客户端,您会发现首先实例化客户端,这将排队第一个操作(在这种情况下是异步解析),然后调用io_service.run()。
  • 因此,假设您从按钮按下开始,此时您需要做的是安排连接或解析,然后启动一个新线程,并从该线程调用io_service.run()。
  • 一旦异步操作链完成并且您拥有数据,您的处理程序将在您旋转的新线程的上下文中被调用。这意味着您必须向您的UI发布消息(因为通常只能在主线程上完成UI工作)。例如,在您的示例中,您的lambda需要执行某种UI发布消息操作(详细信息取决于我们在谈论哪个OS/GUI)。
  • 然后您的GUI线程将接收该消息并更新您想要更新的任何UI状态(例如,显示结果)

谢谢,这回答了我大部分的问题。只有一件事:如果连接至少10个设备同时连接的话,使用和处理器数量相同的线程池不是很合理吗?如果是这样,我事先创建线程池是不是更好呢?我读到了一些关于创建“虚假”工作项来防止io_service.run()立即返回的内容。 - NicolasR
1
@NicolasR 如果你有足够的工作来让单个核心保持100%繁忙,那么线程池就很有意义。看起来这对你来说不是问题,所以一个线程可能就足够了。由于真正的工作是异步完成的,连接到10甚至数千个设备应该没有问题... - Guy Sirton
太好了!这就是我需要的全部。 - NicolasR
@GuySirton 在第二点中,您提到了调度连接和解析,然后启动另一个线程来运行 io_service.run()。但据我所知,异步操作应该排队在调用 io_service.run() 的同一线程上,不是吗? - ashishsony
@ashishsony 不一定。可以有多个线程同时调用io_service.run()。通常,操作是由回调排队的,该回调在调用io_service.run()的某个线程上发生,但这不一定是必须的,例如...您排队的第一个异步操作。 - Guy Sirton

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