Boost.Asio、tcp::iostream 和多线程

3

tl;dr

如何使基于tcp::iostream的服务器(如下面的代码)在单独的线程中接受多个连接?


我正在尝试使用Boost.Asio实现一个预先存在的库的服务器/客户端接口,通过示例摸索前进。(是的,这是C++11之前的版本。请耐心等待。)
选择tcp::iostream的原因:
由于情况特殊,整个服务器过程是可选的;也就是说,当客户端调用port=0时,它将请求传递给本地Handler实例。在其他所有情况下,它会连接到希望侦听该端口号的服务器,并将请求传递给服务器进程的Handler实例。
(这都是严格的127.0.0.1。服务器存在是因为多个Handler实例之间存在重要的共享状态,因此如果所有用户都向运行在计算机上的一个服务器发送请求,那么对于内存和启动时间来说,这将更加容易。 "local",port = 0选项存在,因为这样我只需要一个客户端可执行文件即可用于基于服务器和本地的使用。)

无论如何,这里有一个boost::asio::ip::tcp::iostream,使得事情变得非常容易:

boost::asio::io_service io_service;

boost::asio::ip::tcp::endpoint endpoint( boost::asio::ip::tcp::v4(), port_ );
boost::asio::ip::tcp::acceptor acceptor( io_service, endpoint );

for (;;)
{
    boost::asio::ip::tcp::iostream stream;
    boost::system::error_code ec;
    acceptor.accept( *stream.rdbuf(), ec );
    if ( ! ec )
    {
        Handler( stream, stream ).run(); // <---- This is the "why"
    }
    else
    {
        throw ec.message();
    }
    stream.close();
}

你看到这里了解情况。 Handler 类接受一个输入流和一个输出流。我可以传递 tcp::iostream,也可以传递指向请求文件和日志文件的 std::fstream,或者传递 std::cin / std::cout。这对我非常有效。
但是问题在于上面的代码严格单线程。由于 Handler 执行的任务需要花费很长时间(几分钟),我需要多个工作线程。

所有关于“如何使Boost.Asio支持多线程”的示例和教程 -- 例如HTTP示例server2(使用独立io_service对象池)和server3(使用多个线程,每个线程调用io_service::run())-- 都采用了一个完全不同的方法(通过在单独的connection对象中包含一个tcp::socket对象,使acceptor::async_accept()工作)。

要么因为我对Boost.Asio的总体架构感到困惑,要么因为缺乏文档,但我无法弄清如何结合这两种方法,即拥有多个工作线程,每个线程处理其连接作为tcp::iostream。似乎没有办法从tcp::iostream中检索tcp::socket,也没有给它提供tcp::socket的方法,这时我有点困惑。


你是否看过某个教程,我记得是在gamedev上?我可以为你找出来,但我几年前用它来做io_service/tcp的事情。据我回忆,io_service/run是一种创建一堆线程的方法,这些线程可以做任何你想做的事情,但通常与asio的各种套接字等一起使用。 - Carlos
找到了,碰巧在我的收藏夹里。这是一个关于如何开始使用Boost.Asio的指南。 - Carlos
@sehe 很高兴知道他是否已经尝试过一条路。 - Carlos
为什么要将这两种方法结合起来呢?为什么不只使用传统的asio方法,因为它听起来应该是最适合您的用例的呢? - David Schwartz
@DavidSchwartz:关于选择 tcp::iostream 的理由是什么?我非常喜欢这个事实,即我可以有一个 Handler 类来处理所有的操作,无论是基于文件、cin/cout 还是网络。此外,“异步_*()` 调用和回调(我认为您所说的是“传统的 asio 方法”)的链接感觉很麻烦。 - DevSolar
显示剩余7条评论
1个回答

2

我觉得我可能没有理解问题的关键。这似乎与Asio无关:

在Coliru上使用C++03在线编程

#define BOOST_THREAD_USES_MOVE
#include <boost/enable_shared_from_this.hpp>
#include <boost/make_shared.hpp>
#include <boost/asio.hpp>
#include <boost/asio/basic_socket_iostream.hpp>
#include <boost/thread.hpp>
#include <iostream>
#include <list>

struct Handler {
    std::istream& is;
    std::ostream& os;

    Handler(std::istream& is, std::ostream& os) : is(is), os(os){}
    void run() {
        std::cout << __PRETTY_FUNCTION__ << ":" << __LINE__ << std::endl;
        std::string line;

        while (getline(is, line)) {
            std::reverse(line.begin(), line.end());
            os << line << std::endl;
        }
    }
};

struct SocketRequest : boost::enable_shared_from_this<SocketRequest> {
    boost::asio::ip::tcp::iostream stream;

    void start() {
        boost::async(boost::launch::async, boost::bind(&SocketRequest::do_run, shared_from_this()));
    }

  private:
    void do_run() {
        return Handler(stream, stream).run();
    }
};

int main() {
    boost::asio::io_service io_service;

    unsigned short port_ = 6767;
    boost::asio::ip::tcp::endpoint endpoint(boost::asio::ip::tcp::v4(), port_);
    boost::asio::ip::tcp::acceptor acceptor(io_service, endpoint);

    for (;;)
    {
        boost::shared_ptr<SocketRequest> req = boost::make_shared<SocketRequest>();

        acceptor.accept(*req->stream.rdbuf());
        req->start();
    }
}

针对样本客户:

(for req in HELLO BYE; do sleep 1; netcat 127.0.0.1 6767 <<< "$req"; done)&

打印

void Handler::run():16
OLLEH
void Handler::run():16
EYB

我在这里使用了C++11线程以提高速度,显然,如果需要C++03兼容性,则应使用Boost Thread。 - sehe
非常抱歉向您提出这个请求,但您能否将其转换为C++03 / Boost.Thread使用的代码?看着(同样陌生的)std::future部分穿插在我难以理解的Boost.Asio代码中,使我的挫败感更加严重,因为我太笨而无法“理解”它。 - DevSolar
换句话说,Boost文档中的所有多线程示例都是关于对io_service::run()进行多次调用。而你的示例甚至没有调用一次io_service::run()。我能看懂代码,但是我无法理解其中的概念。 :-( - DevSolar
哈哈 - C++03中的Boost Future和Boost Move很复杂。我还不知道如何使用BOOST_MOVE_RET,但在您的情况下,您可能不关心返回值:这是一个开始http://coliru.stacked-crooked.com/a/9c2337e92446511d - sehe
修复链接:当您想显式控制线程数时,这里有一种不同的方法。http://coliru.stacked-crooked.com/a/4aa30918f9acc3ff - 在这种情况下,我们使用 io_service 作为额外的目的:它充当任务队列(请参阅文章)。 - sehe
显示剩余4条评论

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