【问题标题】:boost asio for sync server keeping TCP session open (with google proto buffers)提升 asio 以保持 TCP 会话打开的同步服务器(使用 google proto 缓冲区)
【发布时间】:2011-08-12 10:51:34
【问题描述】:

我目前有一个非常简单的 boost::asio 服务器,它在连接时发送状态更新(使用 google proto 缓冲区):

try
{
  boost::asio::io_service io_service;
  tcp::acceptor acceptor(io_service,tcp::endpoint(tcp::v4(), 13));
  for (;;)
  {
    tcp::socket socket(io_service);
    acceptor.accept(socket);
    ...
    std::stringstream message;
    protoMsg.SerializeToOstream(&message);
    boost::system::error_code ignored_error;
    boost::asio::write(socket, boost::asio::buffer(message.str()), ignored_error);
  }
}
catch (std::exception& e) { }

我想将其扩展为在接受新连接后首先阅读,检查收到了什么请求,并根据此消息发送不同的消息。我还想保持 TCP 连接打开,这样客户端就不必重新连接,并且想处理多个客户端(不多,可能是 2 个或 3 个)。

我查看了一些关于 boost asio 的示例,即 the async time tcp serverchat server,但两者都让我有点头晕目眩。我什至不明白我是否需要异步服务器。我想我可以在acceptor.accept(socket) 之后阅读,但我想我不会继续听进一步的请求。如果我进入一个循环,我想这意味着我只能处理一个客户。所以我想这意味着我必须异步?有没有更简单的例子可能不是 250 行代码?还是我只需要通过这些例子来咬我的方式?谢谢

【问题讨论】:

    标签: c++ sockets tcp boost-asio


    【解决方案1】:

    您在 Boost.Asio 文档中提到的示例实际上非常适合了解事情是如何工作的。您是对的,起初它可能看起来有点难以理解,特别是如果您不熟悉这些概念。但是,我建议您从聊天服务器示例开始,并在您的计算机上构建它。这将使您能够更仔细地观察事物并开始改变事物以了解它是如何工作的。让我指导您完成一些我认为对入门很重要的事情。

    根据您的描述,您想做什么,似乎聊天服务器为您提供了一个很好的起点,因为它已经有您需要的类似部分。让服务器异步是你想要的,因为你可以很容易地用一个线程处理多个客户端。从一开始就没有什么太复杂的事情。

    在这种情况下,简化的异步意味着您的服务器处理队列,获取处理程序(任务)并执行它。如果队列中没有任何内容,则它只是等待将某些内容放入队列中。在您的情况下,这意味着它可能是来自客户端的连接、来自客户端的新消息读取或类似的东西。为了使其工作,需要设置每个处理程序(处理对特定事件的反应的函数)。

    让我用聊天服务器示例中的代码解释一下。

    server source file 中,您会看到chat_server 类在构造函数中调用start_accept。在这里接受处理程序被设置。

    void start_accept()
    {
        chat_session_ptr new_session(new chat_session(io_service_, room_)); // 1
        acceptor_.async_accept(new_session->socket(),                       // 2
            boost::bind(&chat_server::handle_accept, this, new_session,     // 3
                boost::asio::placeholders::error));                         // 4
    }
    

    第 1 行:创建了一个 chat_session 对象,它表示一个客户端和服务器之间的会话。为接受创建一个会话(还没有客户端连接)。

    第 2 行:套接字的异步接受...

    第 3 行:......当它发生时,一定要调用 chat_server::handle_accept。会话被传递给第一个连接的客户端使用。

    现在,如果我们查看handle_accept,我们会看到在客户端连接时,会为会话调用start(这只是在服务器和客户端之间启动东西)。最后,如果其他客户端也想连接,则将新的接受置于未完成状态。

    void handle_accept(chat_session_ptr session, 
                       const boost::system::error_code& error)
    {
        if (!error)
        {
            session->start();
        }
        start_accept();
    }
    

    这也是你想要的。对传入连接的出色接受。如果多个客户端可以连接,则应该始终有一个未完成的,以便服务器可以处理接受。

    服务器和客户端如何交互都在会话中,您可以遵循相同的设计并对其进行修改以做您想做的事情。您提到服务器需要查看发送的内容并执行不同的操作。看看chat_sessionstart 被服务器调用的handle_accept 函数。

    void start()
    {
        room_.join(shared_from_this());
        boost::asio::async_read(socket_,
            boost::asio::buffer(read_msg_.data(), chat_message::header_length),
            boost::bind(
                &chat_session::handle_read_header, shared_from_this(),
                boost::asio::placeholders::error));
    }
    

    这里重要的是调用boost::asio::async_read。这也是你想要的。这会在套接字上进行未完成的读取,因此服务器可以读取客户端发送的内容。有一个处理程序(函数)绑定到此事件chat_session::handle_read_header。每当服务器读取套接字上的某些内容时,都会调用它。在这个处理函数中,您可以开始放置您的特定代码来确定如果发送特定消息时要执行的操作等等。

    重要的是要知道,每当调用这些异步 boost::asio 函数时,该调用中都不会发生任何事情(即,如果调用 read 函数,则不会读取套接字)。这是异步方面。您只需为某事注册一个处理程序,然后在发生这种情况时回调您的代码。因此,当调用此读取时,它将立即返回,并且您将返回服务器的handle_accept(如果您遵循调用方式)。如果您还记得那里,我们还调用start_accept 来设置另一个异步接受。此时,您有两个未完成的处理程序等待另一个客户端连接或第一个客户端发送内容。根据首先发生的情况,将调用特定的处理程序。

    另外重要的是要了解,无论何时运行某事,它都会不间断地运行,直到它需要做的所有事情都完成为止。即使有触发它们的未完成事件,其他处理程序也必须等待。

    最后,为了运行服务器,您需要 io_service,这是 Asio 的核心概念。

    io_service.run();
    

    这是您在main 函数中看到的一行。这只是说线程(示例中只有一个)应该运行 io_service,它是当有工作要做时处理程序进入队列的队列。当什么都没有时,io_service 只是等待(当然在那里阻塞主线程)。

    我希望这可以帮助您开始您想做的事情。你可以做很多事情,也有很多事情要学习。我觉得它是一个很棒的软件!祝你好运!

    【讨论】:

    • 干杯谢谢。你是对的,我实际上必须咬他们。我不明白的一件事是示例中强烈使用共享指针。它们被保存在哪里?神奇地在asio里面?如果连接断开,它们会自动清理吗?谢谢
    • 共享指针的使用是为了让某些对象在处理程序“内部”保持活动状态。 Asio 仅具有处理程序(从 boost::bind 获得的函数)并且共享指针绑定在那里。这样,只要处理程序处于活动状态,共享指针中的对象就会保持活动状态,这很重要。如果您看的话,会话将作为会话类中的 shared_from_this() 传递给所有 boost::bind ,这非常酷,因为生命周期一直由异步处理程序拥有,是的,它将处理清理,以防万一错误。
    【解决方案2】:

    如果其他人想要这样做,这里是最起码的:(类似于教程,但有点短,有点不同)

    class Session : public boost::enable_shared_from_this<Session>
    {
        tcp::socket socket;
        char buf[1000];
    public:
        Session(boost::asio::io_service& io_service)
            : socket(io_service) { }
        tcp::socket& SocketRef() { return socket; }
        void Read() {
            boost::asio::async_read( socket,boost::asio::buffer(buf),boost::asio::transfer_at_least(1),boost::bind(&Session::Handle_Read,shared_from_this(),boost::asio::placeholders::error));
        }
        void Handle_Read(const boost::system::error_code& error) {
            if (!error)
            {
                //read from buffer and handle requests
                //if you want to write sth, you can do it sync. here: e.g. boost::asio::write(socket, ..., ignored_error);
                Read();
            }
        }
    };
    
    typedef boost::shared_ptr<Session> SessionPtr;
    
    class Server
    {
        boost::asio::io_service io_service;
        tcp::acceptor acceptor;
    public:
        Server() : acceptor(io_service,tcp::endpoint(tcp::v4(), 13)) { }
        ~Server() { }
        void operator()() { StartAccept(); io_service.run(); }
        void StartAccept() {
            SessionPtr session_ptr(new Session(io_service));
            acceptor.async_accept(session_ptr->SocketRef(),boost::bind(&Server::HandleAccept,this,session_ptr,boost::asio::placeholders::error));
        }
        void HandleAccept(SessionPtr session,const boost::system::error_code& error) {
            if (!error)
              session->Read();
            StartAccept();
        }
    };
    

    根据我通过反复试验和阅读收集到的信息:我在 operator()() 中启动它,因此您可以让它在后台运行在另一个线程中。您运行一个服务器实例。要处理多个客户端,您需要一个额外的类,我称之为会话类。为了让 asio 清理死会话,您需要一个如上所述的共享指针。否则,代码应该可以帮助您入门。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-04-14
      • 2021-02-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-11-04
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多