Can't implement boost::asio::ssl::stream

2019-05-25 08:40发布

I need to implement a class which handle connect to ssl server. Pretty much based on this. However. it doesn't have reconnect feature. So I modify it like this:

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

to

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

and refactor everything related to ->

But it leads to errors like this:

/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(
          ^

Then I tried to dereference the pointer to keep the old structure but there's new error :(

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));`

Please help, I'm coming from java so it's quite complicated with me this thing.

1条回答
女痞
2楼-- · 2019-05-25 08:54

This is my minimal change to that demo in Boost 1.66.0. See the patch in isolation on github: https://github.com/boostorg/asio/compare/develop...sehe:so-q49122521

Note: I moved the address resolve into the connect sequence, because if the network configuration has been changed, the result may differ, or another one of the endpoints should be preferred.

For this end, we store a resolver::query query_ member so we can repeat the query on reconnect.

//
// 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;
}

Here it is live demo-ed:

enter image description here

Further Thoughts

Depending on your level of paranoia you could feel better actually closing the ssl stream in do_reconnect():

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

That also works. You could even decide to kill off any lowest level connection just in case:

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;
}

Using a dynamically allocated socket

As mentioned, the purest solution would be to not reuse the stream/socket objects:

boost::optional<stream> socket_;

Now, updating all references to indirect socket_, do_reconnect() can become:

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));
}

Which obviously also works.

Here's the corresponding patch: 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;
}
查看更多
登录 后发表回答