boost ssl连接过程失败

rah*_*man 1 c++ ssl boost boost-asio

我正在尝试将著名的boost ssl客户端/服务器连接示例合并到一个程序中。供您参考,基类如下:

#include <cstdlib>
#include <iostream>
#include <boost/bind.hpp>
#include <boost/asio.hpp>
#include <boost/asio/ssl.hpp>

namespace bt
{
//
// client.cpp
// ~~~~~~~~~~
//
// Copyright (c) 2003-2011 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)
//


enum { max_length = 1024 };

class client
{
public:
  client(boost::asio::io_service& io_service, boost::asio::ssl::context& context,
      boost::asio::ip::tcp::resolver::iterator endpoint_iterator)
    : socket_(io_service, context)
  {
    boost::asio::ip::tcp::endpoint endpoint = *endpoint_iterator;
    socket_.lowest_layer().async_connect(endpoint,
        boost::bind(&client::handle_connect, this,
          boost::asio::placeholders::error, ++endpoint_iterator));
  }

  void handle_connect(const boost::system::error_code& error,
      boost::asio::ip::tcp::resolver::iterator endpoint_iterator)
  {
      std::cout << "handle_connect\n";
    if (!error)
    {
        std::cout << "handle_connect No error\n";
      socket_.async_handshake(boost::asio::ssl::stream_base::client,
          boost::bind(&client::handle_handshake, this,
            boost::asio::placeholders::error));
    }
    else if (endpoint_iterator != boost::asio::ip::tcp::resolver::iterator())
    {
        std::cout << "handle_connect retry!\n";
      socket_.lowest_layer().close();
      boost::asio::ip::tcp::endpoint endpoint = *endpoint_iterator;
      socket_.lowest_layer().async_connect(endpoint,
          boost::bind(&client::handle_connect, this,
            boost::asio::placeholders::error, ++endpoint_iterator));
    }
    else
    {
      std::cout << "Connect failed: " << error << "\n";
    }
  }

  void handle_handshake(const boost::system::error_code& error)
  {
      std::cout << "client handle_handshake\n";
    if (!error)
    {
      std::cout << "Enter message: ";
//      std::cin.getline(request_, max_length);
      sprintf(request_, "%s", "Hi Testing...");
      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));
    }
    else
    {
      std::cout << "Handshake failed: " << error << "\n";
    }
  }

  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 << "\n";
    }
  }

  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";
    }
    else
    {
      std::cout << "Read failed: " << error << "\n";
    }
  }

private:
  boost::asio::ssl::stream<boost::asio::ip::tcp::socket> socket_;
  char request_[max_length];
  char reply_[max_length];
};


//
// server.cpp
// ~~~~~~~~~~
//
// Copyright (c) 2003-2011 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)
//


typedef boost::asio::ssl::stream<boost::asio::ip::tcp::socket> ssl_socket;

class session
{
public:
  session(boost::asio::io_service& io_service, boost::asio::ssl::context& context)
    : socket_(io_service, context)
  {
  }

  ssl_socket::lowest_layer_type& socket()
  {
    return socket_.lowest_layer();
  }

  void start()
  {
      std::cout << "session start->handshake\n";
    socket_.async_handshake(boost::asio::ssl::stream_base::server,
        boost::bind(&session::handle_handshake, this,
          boost::asio::placeholders::error));
  }

  void handle_handshake(const boost::system::error_code& error)
  {
      std::cout << "session handle_handshake\n";
    if (!error)
    {
      socket_.async_read_some(boost::asio::buffer(data_, max_length),
          boost::bind(&session::handle_read, this,
            boost::asio::placeholders::error,
            boost::asio::placeholders::bytes_transferred));
    }
    else
    {
      delete this;
    }
  }

  void handle_read(const boost::system::error_code& error,
      size_t bytes_transferred)
  {
    if (!error)
    {
      boost::asio::async_write(socket_,
          boost::asio::buffer(data_, bytes_transferred),
          boost::bind(&session::handle_write, this,
            boost::asio::placeholders::error));
    }
    else
    {
      delete this;
    }
  }

  void handle_write(const boost::system::error_code& error)
  {
    if (!error)
    {
      socket_.async_read_some(boost::asio::buffer(data_, max_length),
          boost::bind(&session::handle_read, this,
            boost::asio::placeholders::error,
            boost::asio::placeholders::bytes_transferred));
    }
    else
    {
      delete this;
    }
  }

private:
  ssl_socket socket_;
  enum { max_length = 1024 };
  char data_[max_length];
};

class server
{
public:
  server(boost::asio::io_service& io_service, unsigned short port)
    : io_service_(io_service),
      acceptor_(io_service,
          boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), port)),
      context_(io_service, boost::asio::ssl::context::sslv23)
  {
      //std::cout << "server()\n";
    context_.set_options(
        boost::asio::ssl::context::default_workarounds
        | boost::asio::ssl::context::no_sslv2
        | boost::asio::ssl::context::single_dh_use);
    context_.set_password_callback(boost::bind(&server::get_password, this));
    context_.use_certificate_chain_file("server.crt");
    context_.use_private_key_file("server.key", boost::asio::ssl::context::pem);
    context_.use_tmp_dh_file("dh1024.pem");

    session* new_session = new session(io_service_, context_);
    acceptor_.async_accept(new_session->socket(),
        boost::bind(&server::handle_accept, this, new_session,
          boost::asio::placeholders::error));
  }

  std::string get_password() const
  {
    return "test";
  }

  void handle_accept(session* new_session,
      const boost::system::error_code& error)
  {
      std::cout << "server() handle_accept\n";
    if (!error)
    {
        std::cout << "server() handle_accept  !error\n";
      new_session->start();
      new_session = new session(io_service_, context_);
      acceptor_.async_accept(new_session->socket(),
          boost::bind(&server::handle_accept, this, new_session,
            boost::asio::placeholders::error));
    }
    else
    {
        std::cout << "server() handle_accept  error:" << error.message() << std::endl;
      delete new_session;
    }
  }

private:
  boost::asio::io_service& io_service_;
  boost::asio::ip::tcp::acceptor acceptor_;
  boost::asio::ssl::context context_;
};

}//namespace bt
Run Code Online (Sandbox Code Playgroud)

并且主程序是:

BOOST_AUTO_TEST_CASE(accept_ssl_connection_1)
{
    boost::asio::io_service io_service_1;
    boost::asio::io_service io_service_2;

    int port = random_port();
    std::stringstream i("");
    i << port;
    std::cout << "Port is:" << i.str() << std::endl;
    //server
    bt::server(io_service_1, port);
    //client
    boost::asio::ip::tcp::resolver resolver(io_service_2);
    boost::asio::ip::tcp::resolver::query query("127.0.0.1", i.str());
    boost::asio::ip::tcp::resolver::iterator iterator = resolver.resolve(query);

    boost::asio::ssl::context ctx(io_service_2, boost::asio::ssl::context::sslv23);
    ctx.set_verify_mode(boost::asio::ssl::context::verify_peer);
    ctx.load_verify_file("server.crt");

    bt::client c(io_service_2, ctx, iterator);

    boost::thread thread1(boost::bind(&boost::asio::io_service::run, &io_service_1));
    boost::thread thread2(boost::bind(&boost::asio::io_service::run, &io_service_2));

    thread1.join();
    thread2.join();
}
Run Code Online (Sandbox Code Playgroud)

这是我得到的输出:

Port is:7200
server() handle_accept
handle_connect
Connect failed: system:111
server() handle_accept  error:Operation canceled
Run Code Online (Sandbox Code Playgroud)

如果clien和服务器分别构建和运行,则该程序可以运行。我想我在io_service的使用上有误。您能帮我发现问题吗?

seh*_*ehe 5

1.风格

我建议您花更多精力使代码可读。

代码供人类阅读,而不是计算机

就您而言,简洁至极

bt::client c(...);
Run Code Online (Sandbox Code Playgroud)

导致类似的错误

bt::server(io_service_1, port);
Run Code Online (Sandbox Code Playgroud)

变量声明(可能是预期的)并没有太大区别

bt::server s(io_service_1, port);
Run Code Online (Sandbox Code Playgroud)

否则,新构建的服务器将立即被销毁,从而取消所有挂起的操作。

2.调试

尝试实际呈现可读消息:

std::cout << "Connect failed: " << error.message() << "\n";
std::cout << "Handshake failed: " << error.message() << "\n";
std::cout << "Write failed: " << error.message() << "\n";
std::cout << "Read failed: " << error.message() << "\n";
std::cout << "server() handle_accept  error:" << error.message() << std::endl;
Run Code Online (Sandbox Code Playgroud)

这将告诉您“ 125”表示“操作已中止”等。这就是让我在此处和此处添加一些痕迹的原因:

~session() { std::cout << "Deleting session!\n"; }
~server() { std::cout << "Deleting server!\n"; }
Run Code Online (Sandbox Code Playgroud)

2. Asio评论,更多风格

  1. 与其手动处理,不如使用boost中定义的组合操作:

    client(ba::io_service &io_service, ssl::context &context, tcp::resolver::iterator endpoint_iterator)
            : socket_(io_service, context) 
    {
        ba::async_connect(socket_.lowest_layer(), endpoint_iterator,
                          boost::bind(&client::handle_connect, this, bap::error));
    }
    
    void handle_connect(const boost::system::error_code &error) {
        std::cout << "handle_connect\n";
        if (!error) {
            std::cout << "handle_connect No error\n";
            socket_.async_handshake(ssl::stream_base::client, boost::bind(&client::handle_handshake, this, bap::error));
        } else {
            std::cout << "Connect failed: " << error.message() << "\n";
        }
    }
    
    Run Code Online (Sandbox Code Playgroud)

    这完成了整个迭代器的舞蹈。但是不太容易出错。

  2. 使用名称空间别名获取可读/可管理的行

    using boost::asio::ip::tcp;
    namespace ba = boost::asio;
    namespace bap = boost::asio::placeholders;
    namespace ssl = boost::asio::ssl;
    
    Run Code Online (Sandbox Code Playgroud)
  3. 使用智能指针(delete this?ugh)

  4. 考虑使用1 io_service。实际上,使用两个不会添加任何内容,并且名称也不能使事情变得清晰。实际上,盯着您的代码的第一分钟让我解开了客户端和服务器的代码,不遗余力地验证它们没有错误地使用错误的服务,从而导致过早run()完成。

  5. 考虑比赛条件。在您的代码中,服务器和客户端在不同步的情况下独立运行。在至少增加延时:

    boost::this_thread::sleep_for(boost::chrono::seconds(1));
    
    Run Code Online (Sandbox Code Playgroud)

    避免在开始接受连接之前先client连接到server

  6. 优先选择boost::thread_group丢失线程:

    boost::thread_group tg;
    // ...
    tg.create_thread(boost::bind(&ba::io_service::run, &io_service_1));
    // ...
    tg.create_thread(boost::bind(&ba::io_service::run, &io_service_2));
    // ...
    tg.join_all();
    
    Run Code Online (Sandbox Code Playgroud)
  7. 实际上,使用1 io_service和1个线程,就可以避免上述所有操作(由于隐式strand,异步操作得以同步)

  8. 使用更高级别的标准库功能(例如,std::to_string(int)而不是std::ostringstream;如果不能使用c ++ 11,请使用boost::lexical_cast或编写自己的to_string-type helper函数)。

  9. 如果地址被硬编码为回送,则无需“解析”任何内容:只需连接到 tcp::endpoint{{}, port}

  10. 考虑ctx移入client(就像您也将的ssl参数server移入该类一样)

  11. 比原始数组(request_reply_)更喜欢boost :: array / std :: array

  12. 为什么读取的字节数与发送的一样多?你的意思是

    ba::async_read(socket_, ba::buffer(reply_, bytes_transferred),
                   boost::bind(&client::handle_read, this, bap::error, bap::bytes_transferred));
    
    Run Code Online (Sandbox Code Playgroud)

    我希望有类似的东西

    ba::async_read(socket_, ba::buffer(reply_, reply.size()), // assuming array<>, see previous
                   boost::bind(&client::handle_read, this, bap::error, bap::bytes_transferred));
    
    Run Code Online (Sandbox Code Playgroud)
  13. read_some再次考虑组合操作。read_some可能无法阅读完整的请求。考虑添加帧协议或预先发送请求长度。

  14. 避免代码重复:async_accept编码两次。而是使其成为一个单独的函数并调用两次:

    void do_accept() {
        session::ptr new_session = boost::make_shared<session>(io_service_, context_);
        acceptor_.async_accept(new_session->socket(), boost::bind(&server::handle_accept, this, new_session, bap::error));
    }
    
    Run Code Online (Sandbox Code Playgroud)

奖金

  1. 为接受添加最后期限,以便我们可以在一定的空闲时间间隔内停止服务器

  2. 由于您现在正在使用智能指针(不是吗?),因此在此位置添加会话关闭也很容易(session::close()

  3. 让我们以一个的价格做两个客户,只是为了好玩

Live On Coliru

//#define BOOST_ASIO_ENABLE_HANDLER_TRACKING 1
#include <boost/asio.hpp>
#include <boost/asio/ssl.hpp>
#include <boost/bind.hpp>
#include <boost/enable_shared_from_this.hpp>
#include <boost/make_shared.hpp>
#include <cstdlib>
#include <iostream>

using boost::asio::ip::tcp;
namespace ba = boost::asio;
namespace bap = boost::asio::placeholders;
namespace ssl = boost::asio::ssl;

namespace bt {

enum { max_length = 1024, idle_timeout_seconds = 2 };

class client {
  public:
    client(ba::io_service &io_service, tcp::resolver::iterator endpoint_iterator, std::string const& request)
            : ctx_(io_service, ssl::context::sslv23),
              socket_(io_service, ctx_),
              request_(request)
    {
        ctx_.set_verify_mode(ssl::context::verify_peer);
        ctx_.load_verify_file("server.crt");
        ba::async_connect(socket_.lowest_layer(), endpoint_iterator,
                          boost::bind(&client::handle_connect, this, bap::error));
    }

    void handle_connect(const boost::system::error_code &error) {
        std::cout << "handle_connect\n";
        if (!error) {
            std::cout << "handle_connect No error\n";
            socket_.async_handshake(ssl::stream_base::client, boost::bind(&client::handle_handshake, this, bap::error));
        } else {
            std::cout << "Connect failed: " << error.message() << "\n";
        }
    }

    void handle_handshake(const boost::system::error_code &error) {
        std::cout << "client handle_handshake\n";
        if (!error) {
            ba::async_write(socket_, ba::buffer(request_),
                            boost::bind(&client::handle_write, this, bap::error, bap::bytes_transferred));
        } else {
            std::cout << "Handshake failed: " << error.message() << "\n";
        }
    }

    void handle_write(const boost::system::error_code &error, size_t bytes_transferred) {
        if (!error) {
            ba::async_read(socket_, ba::buffer(reply_, bytes_transferred),
                           boost::bind(&client::handle_read, this, bap::error, bap::bytes_transferred));
        } else {
            std::cout << "Write failed: " << error.message() << "\n";
        }
    }

    void handle_read(const boost::system::error_code &error, size_t bytes_transferred) {
        if (!error) {
            std::cout << "Reply: ";
            std::cout.write(reply_.data(), bytes_transferred);
            std::cout << "\n";
        } else {
            std::cout << "Read failed: " << error.message() << "\n";
        }
    }

  private:
    ssl::context ctx_;
    ssl::stream<tcp::socket> socket_;
    std::string request_;
    std::array<char, max_length> reply_;
};

class session : public boost::enable_shared_from_this<session> {
  public:
    using ptr = boost::shared_ptr<session>;
    session(ba::io_service &io_service, ssl::context &context) : socket_(io_service, context) {}

    typedef ssl::stream<tcp::socket> ssl_socket;
    ssl_socket::lowest_layer_type &socket() { return socket_.lowest_layer(); }

    void start() {
        std::cout << "session start->handshake\n";
        socket_.async_handshake(ssl::stream_base::server, boost::bind(&session::handle_handshake, shared_from_this(), bap::error));
    }

    void handle_handshake(const boost::system::error_code &error) {
        std::cout << "session handle_handshake\n";
        if (error) return;
        socket_.async_read_some(ba::buffer(data_),
                boost::bind(&session::handle_read, shared_from_this(), bap::error, bap::bytes_transferred));
    }

    void handle_read(const boost::system::error_code &error, size_t bytes_transferred) {
        if (error) return;
        ba::async_write(socket_, ba::buffer(data_, bytes_transferred),
                boost::bind(&session::handle_write, shared_from_this(), bap::error));
    }

    void handle_write(const boost::system::error_code &error) {
        if (error) return;
        socket_.async_read_some(ba::buffer(data_),
                boost::bind(&session::handle_read, shared_from_this(), bap::error, bap::bytes_transferred));
    }

    void close() {
        socket_.get_io_service().post([this] {
                std::cout << "session::close()\n";
                socket_.lowest_layer().cancel();
                socket_.lowest_layer().close();
            });
    }

    ~session() { std::cout << "Deleting session\n"; }

  private:
    ssl_socket socket_;
    std::array<char, max_length> data_;
};

class server {
  public:
    server(ba::io_service &io_service, unsigned short port)
            : io_service_(io_service), acceptor_(io_service, tcp::endpoint(tcp::v4(), port)),
              context_(io_service, ssl::context::sslv23),
              deadline_(io_service)
    {
        // std::cout << "server()\n";
        context_.set_options(ssl::context::default_workarounds | ssl::context::no_sslv2 | ssl::context::single_dh_use);
        context_.set_password_callback(boost::bind(&server::get_password, this));
        context_.use_certificate_chain_file("server.crt");
        context_.use_private_key_file("server.crt", ssl::context::pem);
        context_.use_tmp_dh_file("dh2048.pem");

        do_accept();
    }

    ~server() { std::cout << "Deleting server\n"; }

    std::string get_password() const { return "test"; }

    void do_accept() {
        session::ptr new_session = boost::make_shared<session>(io_service_, context_);
        deadline_.expires_from_now(boost::posix_time::seconds(idle_timeout_seconds));
        deadline_.async_wait(boost::bind(&server::handle_deadline, this, bap::error()));
        acceptor_.async_accept(new_session->socket(), boost::bind(&server::handle_accept, this, new_session, bap::error));
    }

    void handle_accept(session::ptr new_session, const boost::system::error_code &error) {
        std::cout << "server() handle_accept\n";
        if (!error) {
            std::cout << "server() handle_accept ok\n";
            sessions_.push_back(new_session);
            new_session->start();
            do_accept();
        } else {
            std::cout << "server() handle_accept error:" << error.message() << std::endl;
        }
    }

    void handle_deadline(boost::system::error_code ec) {
        if (!ec) {
            io_service_.post([this] {
                // assuming 1 thread runs io_service, no more locking required
                std::cout << "server() shutdown after idle timeout\n";
                acceptor_.cancel();
                acceptor_.close();

                for (auto weak_sess : sessions_)
                    if (auto sess = weak_sess.lock())
                        sess->close();
            });
        }
    }

  private:
    ba::io_service &io_service_;
    tcp::acceptor acceptor_;
    ssl::context context_;
    ba::deadline_timer deadline_;
    std::vector<boost::weak_ptr<session> > sessions_;
};

} // namespace bt

void accept_ssl_connection_1() {
    ba::io_service svc;
    int port = 6767;

    std::cout << "Port is:" << port << std::endl;

    // server
    bt::server s(svc, port);

    // client
    tcp::resolver resolver(svc);
    bt::client c(svc, resolver.resolve({"127.0.0.1", std::to_string(port)}), "Hello, I'm Bob");
    bt::client d(svc, resolver.resolve({"127.0.0.1", std::to_string(port)}), "Hello, I'm Cindy");

    svc.run();
}

int main() {
    accept_ssl_connection_1();
}
Run Code Online (Sandbox Code Playgroud)

版画

Port is:6767
server() handle_accept
server() handle_accept ok
session start->handshake
handle_connect
handle_connect No error
handle_connect
handle_connect No error
server() handle_accept
server() handle_accept ok
session start->handshake
session handle_handshake
client handle_handshake
session handle_handshake
client handle_handshake
Reply: Hello, I'm Bob
Reply: Hello, I'm Cindy
server() shutdown after idle timeout
server() handle_accept
server() handle_accept error:Operation canceled
Deleting session
session::close()
session::close()
Deleting session
Deleting session
Deleting server
Run Code Online (Sandbox Code Playgroud)