代码之家  ›  专栏  ›  技术社区  ›  Patton

无法实现boost::asio::ssl::stream<boost::asio::ip::tcp::socket>重新连接到服务器

  •  3
  • Patton  · 技术社区  · 7 年前

    我需要实现一个处理连接到ssl服务器的类。差不多吧 基于 this . 然而它没有 reconnect feature . 所以我 修改如下:

    boost::asio::ssl::stream<boost::asio::ip::tcp::socket> socket_;
    

    boost::asio::ssl::stream<boost::asio::ip::tcp::socket> *mpSocket_;
    

    重构与 ->

    但这会导致如下错误:

    /usr/include/boost/asio/impl/read.hpp:271: error: request for member 'async_read_some' in '((boost::asio::detail::read_op<boost::asio::ssl::stream<boost::asio::basic_stream_socket<boost::asio::ip::tcp> >*, boost::asio::mutable_buffers_1, boost::asio::detail::transfer_all_t, boost::_bi::bind_t<void, boost::_mfi::mf2<void, SSLHandler, const boost::system::error_code&, long unsigned int>, boost::_bi::list3<boost::_bi::value<SSLHandler*>, boost::arg<1> (*)(), boost::arg<2> (*)()> > >*)this)->boost::asio::detail::read_op<boost::asio::ssl::stream<boost::asio::basic_stream_socket<boost::asio::ip::tcp> >*, boost::asio::mutable_buffers_1, boost::asio::detail::transfer_all_t, boost::_bi::bind_t<void, boost::_mfi::mf2<void, SSLHandler, const boost::system::error_code&, long unsigned int>, boost::_bi::list3<boost::_bi::value<SSLHandler*>, boost::arg<1> (*)(), boost::arg<2> (*)()> > >::stream_', which is of pointer type 'boost::asio::ssl::stream<boost::asio::basic_stream_socket<boost::asio::ip::tcp> >*' (maybe you meant to use '->' ?)
              stream_.async_read_some(
              ^
    
    /usr/include/boost/asio/impl/write.hpp:258: error: request for member 'async_write_some' in '((boost::asio::detail::write_op<boost::asio::ssl::stream<boost::asio::basic_stream_socket<boost::asio::ip::tcp> >*, boost::asio::mutable_buffers_1, boost::asio::detail::transfer_all_t, boost::_bi::bind_t<void, boost::_mfi::mf2<void, SSLHandler, const boost::system::error_code&, long unsigned int>, boost::_bi::list3<boost::_bi::value<SSLHandler*>, boost::arg<1> (*)(), boost::arg<2> (*)()> > >*)this)->boost::asio::detail::write_op<boost::asio::ssl::stream<boost::asio::basic_stream_socket<boost::asio::ip::tcp> >*, boost::asio::mutable_buffers_1, boost::asio::detail::transfer_all_t, boost::_bi::bind_t<void, boost::_mfi::mf2<void, SSLHandler, const boost::system::error_code&, long unsigned int>, boost::_bi::list3<boost::_bi::value<SSLHandler*>, boost::arg<1> (*)(), boost::arg<2> (*)()> > >::stream_', which is of pointer type 'boost::asio::ssl::stream<boost::asio::basic_stream_socket<boost::asio::ip::tcp> >*' (maybe you meant to use '->' ?)
              stream_.async_write_some(
              ^
    

    然后我尝试取消对指针的引用以保留旧结构,但出现了新错误:(

    boost::asio::async_connect(*socket_.lowest_layer(), mEndpointIterator, boost::bind(&SSLHandler::handle_connect, this, boost::asio::placeholders::error));
    error: request for member 'lowest_layer' in '((SSLHandler*)this)->SSLHandler::socket_', which is of pointer type 'boost::asio::ssl::stream<boost::asio::basic_stream_socket<boost::asio::ip::tcp> >*' (maybe you meant to use '->' ?)
         boost::asio::async_connect(*socket_.lowest_layer(), mEndpointIterator, boost::bind(&SSLHandler::handle_connect, this, boost::asio::placeholders::error));`
    

    请帮忙,我来自java,所以这件事对我来说很复杂。

    1 回复  |  直到 7 年前
        1
  •  6
  •   sehe    7 年前

    这是我对Boost 1.66.0中演示的最小更改。在github上单独查看修补程序: https://github.com/boostorg/asio/compare/develop...sehe:so-q49122521

    注意:我将地址解析移到了连接序列中,因为如果网络配置已更改,结果可能会有所不同,或者应该首选另一个端点。

    为此,我们存储 resolver::query query_ 成员,以便我们可以在重新连接时重复查询。

    //
    // client.cpp
    // ~~~~~~~~~~
    //
    // Copyright (c) 2003-2018 Christopher M. Kohlhoff (chris at kohlhoff dot com)
    //
    // Distributed under the Boost Software License, Version 1.0. (See accompanying
    // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
    //
    
    #include <cstdlib>
    #include <iostream>
    #include <boost/bind.hpp>
    #include <boost/asio.hpp>
    #include <boost/asio/ssl.hpp>
    
    enum { max_length = 1024 };
    
    class client
    {
    public:
      client(boost::asio::io_context& io_context,
          boost::asio::ssl::context& context,
          boost::asio::ip::tcp::resolver::query query)
        : socket_(io_context, context), query_(query), timer_(io_context)
      {
        socket_.set_verify_mode(boost::asio::ssl::verify_peer);
        socket_.set_verify_callback(
            boost::bind(&client::verify_certificate, this, _1, _2));
    
        start_connect();
      }
    
      void start_connect() {
        boost::asio::ip::tcp::resolver r(socket_.get_io_context());
    
        boost::asio::async_connect(socket_.lowest_layer(), r.resolve(query_),
            boost::bind(&client::handle_connect, this,
              boost::asio::placeholders::error));
      }
    
      void do_reconnect() {
        timer_.expires_from_now(boost::posix_time::millisec(500));
        timer_.async_wait(boost::bind(&client::handle_reconnect_timer, this, boost::asio::placeholders::error));
      }
    
      void handle_reconnect_timer(boost::system::error_code ec) {
        if (!ec) {
          start_connect();
        }
      }
    
      bool verify_certificate(bool preverified,
          boost::asio::ssl::verify_context& ctx)
      {
        // The verify callback can be used to check whether the certificate that is
        // being presented is valid for the peer. For example, RFC 2818 describes
        // the steps involved in doing this for HTTPS. Consult the OpenSSL
        // documentation for more details. Note that the callback is called once
        // for each certificate in the certificate chain, starting from the root
        // certificate authority.
    
        // In this example we will simply print the certificate's subject name.
        char subject_name[256];
        X509* cert = X509_STORE_CTX_get_current_cert(ctx.native_handle());
        X509_NAME_oneline(X509_get_subject_name(cert), subject_name, 256);
        std::cout << "Verifying " << subject_name << "\n";
    
        return preverified;
      }
    
      void handle_connect(const boost::system::error_code& error)
      {
        if (!error)
        {
          socket_.async_handshake(boost::asio::ssl::stream_base::client,
              boost::bind(&client::handle_handshake, this,
                boost::asio::placeholders::error));
        }
        else
        {
          std::cout << "Connect failed: " << error.message() << "\n";
          do_reconnect();
        }
      }
    
      void accept_message() {
          std::cout << "Enter message: ";
          std::cin.getline(request_, max_length);
          size_t request_length = strlen(request_);
    
          boost::asio::async_write(socket_,
              boost::asio::buffer(request_, request_length),
              boost::bind(&client::handle_write, this,
                boost::asio::placeholders::error,
                boost::asio::placeholders::bytes_transferred));
        }
    
      void handle_handshake(const boost::system::error_code& error)
      {
        if (!error)
        {
          accept_message();
        }
        else
        {
          std::cout << "Handshake failed: " << error.message() << "\n";
          do_reconnect();
        }
      }
    
      void handle_write(const boost::system::error_code& error,
          size_t bytes_transferred)
      {
        if (!error)
        {
          boost::asio::async_read(socket_,
              boost::asio::buffer(reply_, bytes_transferred),
              boost::bind(&client::handle_read, this,
                boost::asio::placeholders::error,
                boost::asio::placeholders::bytes_transferred));
        }
        else
        {
          std::cout << "Write failed: " << error.message() << "\n";
          do_reconnect();
        }
      }
    
      void handle_read(const boost::system::error_code& error,
          size_t bytes_transferred)
      {
        if (!error)
        {
          std::cout << "Reply: ";
          std::cout.write(reply_, bytes_transferred);
          std::cout << "\n";
    
          accept_message(); // continue using the same socket_ until fail
        }
        else
        {
          std::cout << "Read failed: " << error.message() << "\n";
          do_reconnect();
        }
      }
    
    private:
      boost::asio::ssl::stream<boost::asio::ip::tcp::socket> socket_;
      boost::asio::ip::tcp::resolver::query query_;
      boost::asio::deadline_timer timer_;
      char request_[max_length];
      char reply_[max_length];
    };
    
    int main(int argc, char* argv[])
    {
      try
      {
        if (argc != 3)
        {
          std::cerr << "Usage: client <host> <port>\n";
          return 1;
        }
    
        boost::asio::io_context io_context;
        boost::asio::ssl::context ctx(boost::asio::ssl::context::sslv23);
        ctx.load_verify_file("ca.pem");
    
            client c(io_context, ctx, {argv[1], argv[2]});
    
        io_context.run();
      }
      catch (std::exception& e)
      {
        std::cerr << "Exception: " << e.what() << "\n";
      }
    
      return 0;
    }
    

    这里是现场演示版:

    enter image description here

    进一步思考

    根据您的偏执程度,实际上关闭ssl流会让您感觉更好 do_reconnect() :

    boost::system::error_code ec;
    socket_.shutdown(ec);
    if (ec) std::cout << "shutdown error: " << ec.message() << std::endl;
    

    这也行得通。您甚至可以决定关闭任何最低级别的连接,以防万一:

    auto& ll = socket_.lowest_layer();
    
    if (ll.is_open())
    {
      ll.shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec);
      //if (ec) std::cout << "socket.shutdown error: " << ec.message() << std::endl;
    
      ll.close(ec);
      //if (ec) std::cout << "socket.close error: " << ec.message() << std::endl;
    }
    

    使用动态分配的套接字

    如前所述,最纯粹的解决方案是不重用流/套接字对象:

    boost::optional<stream> socket_;
    

    现在,更新对间接 socket_ , do\u reconnect() 可以成为:

    void do_reconnect() {
      auto& io_context = socket_->get_io_context();
      {
          boost::system::error_code ec;
          socket_->shutdown(ec);
          if (ec) std::cout << "shutdown error: " << ec.message() << std::endl;
      }
    
      socket_.emplace(io_context, context_);
    
      timer_.expires_from_now(boost::posix_time::millisec(500));
      timer_.async_wait(boost::bind(&client::handle_reconnect_timer, this, boost::asio::placeholders::error));
    }
    

    这显然也有效。

    下面是相应的补丁: https://github.com/boostorg/asio/compare/develop...sehe:so-q49122521-dynamic

    //
    // client.cpp
    // ~~~~~~~~~~
    //
    // Copyright (c) 2003-2018 Christopher M. Kohlhoff (chris at kohlhoff dot com)
    //
    // Distributed under the Boost Software License, Version 1.0. (See accompanying
    // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
    //
    
    #include <cstdlib>
    #include <iostream>
    #include <boost/optional.hpp>
    #include <boost/bind.hpp>
    #include <boost/asio.hpp>
    #include <boost/asio/ssl.hpp>
    
    enum { max_length = 1024 };
    
    namespace ssl = boost::asio::ssl;
    using tcp = boost::asio::ip::tcp;
    
    class client
    {
        using stream = ssl::stream<tcp::socket>;
    public:
      client(boost::asio::io_context& io_context, ssl::context& context, tcp::resolver::query query)
        : context_(context), socket_(boost::in_place_init, io_context, context_), query_(query), timer_(io_context)
      {
        socket_->set_verify_mode(ssl::verify_peer);
        socket_->set_verify_callback(
            boost::bind(&client::verify_certificate, this, _1, _2));
    
        start_connect();
      }
    
      void start_connect() {
        tcp::resolver r(socket_->get_io_context());
    
        boost::asio::async_connect(socket_->lowest_layer(), r.resolve(query_),
            boost::bind(&client::handle_connect, this,
              boost::asio::placeholders::error));
      }
    
      void do_reconnect() {
        auto& io_context = socket_->get_io_context();
        {
            boost::system::error_code ec;
            socket_->shutdown(ec);
            if (ec) std::cout << "shutdown error: " << ec.message() << std::endl;
        }
    
        socket_.emplace(io_context, context_);
    
        timer_.expires_from_now(boost::posix_time::millisec(500));
        timer_.async_wait(boost::bind(&client::handle_reconnect_timer, this, boost::asio::placeholders::error));
      }
    
      void handle_reconnect_timer(boost::system::error_code ec) {
        if (!ec) {
          start_connect();
        }
      }
    
      bool verify_certificate(bool preverified,
          ssl::verify_context& ctx)
      {
        // The verify callback can be used to check whether the certificate that is
        // being presented is valid for the peer. For example, RFC 2818 describes
        // the steps involved in doing this for HTTPS. Consult the OpenSSL
        // documentation for more details. Note that the callback is called once
        // for each certificate in the certificate chain, starting from the root
        // certificate authority.
    
        // In this example we will simply print the certificate's subject name.
        char subject_name[256];
        X509* cert = X509_STORE_CTX_get_current_cert(ctx.native_handle());
        X509_NAME_oneline(X509_get_subject_name(cert), subject_name, 256);
        std::cout << "Verifying " << subject_name << "\n";
    
        return preverified;
      }
    
      void handle_connect(const boost::system::error_code& error)
      {
        if (!error)
        {
          socket_->async_handshake(ssl::stream_base::client,
              boost::bind(&client::handle_handshake, this,
                boost::asio::placeholders::error));
        }
        else
        {
          std::cout << "Connect failed: " << error.message() << "\n";
          do_reconnect();
        }
      }
    
      void accept_message() {
        std::cout << "Enter message: ";
        std::cin.getline(request_, max_length);
        size_t request_length = strlen(request_);
    
        boost::asio::async_write(*socket_,
            boost::asio::buffer(request_, request_length),
            boost::bind(&client::handle_write, this,
              boost::asio::placeholders::error,
              boost::asio::placeholders::bytes_transferred));
      }
    
      void handle_handshake(const boost::system::error_code& error)
      {
        if (!error)
        {
          accept_message();
        }
        else
        {
          std::cout << "Handshake failed: " << error.message() << "\n";
          do_reconnect();
        }
      }
    
      void handle_write(const boost::system::error_code& error,
          size_t bytes_transferred)
      {
        if (!error)
        {
          boost::asio::async_read(*socket_,
              boost::asio::buffer(reply_, bytes_transferred),
              boost::bind(&client::handle_read, this,
                boost::asio::placeholders::error,
                boost::asio::placeholders::bytes_transferred));
        }
        else
        {
          std::cout << "Write failed: " << error.message() << "\n";
          do_reconnect();
        }
      }
    
      void handle_read(const boost::system::error_code& error,
          size_t bytes_transferred)
      {
        if (!error)
        {
          std::cout << "Reply: ";
          std::cout.write(reply_, bytes_transferred);
          std::cout << "\n";
    
          accept_message(); // continue using the same socket_ until fail
        }
        else
        {
          std::cout << "Read failed: " << error.message() << "\n";
          do_reconnect();
        }
      }
    
    private:
      ssl::context& context_;
    
      boost::optional<stream> socket_;
      tcp::resolver::query query_;
      boost::asio::deadline_timer timer_;
      char request_[max_length];
      char reply_[max_length];
    };
    
    int main(int argc, char* argv[])
    {
        try
        {
            if (argc != 3)
            {
                std::cerr << "Usage: client <host> <port>\n";
                return 1;
            }
    
            boost::asio::io_context io_context;
            ssl::context ctx(ssl::context::sslv23);
            ctx.load_verify_file("ca.pem");
    
            client c(io_context, ctx, {argv[1], argv[2]});
    
            io_context.run();
        }
        catch (std::exception& e)
        {
            std::cerr << "Exception: " << e.what() << "\n";
        }
    
        return 0;
    }