c++ - boost ssl 连接过程失败

标签 c++ ssl boost boost-asio

我正在尝试结合著名的 boost ssl client/server连接示例到一个程序中。为了您的引用,基类是这样的:

#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

主要程序是:

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

这是我得到的输出:

Port is:7200
server() handle_accept
handle_connect
Connect failed: system:111
server() handle_accept  error:Operation canceled

如果客户端和服务器是单独构建和运行的,则该程序可以运行。我想我在 io_service 的使用上有误。 你能帮我检测一下问题吗?

最佳答案

1。风格

我建议您更加努力地 boost 代码的可读性。

Code is for humans to read, not computers

在您的情况下,极度简洁

bt::client c(...);

导致像这样的错误

bt::server(io_service_1, port);

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

bt::server s(io_service_1, port);

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

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;

这会告诉您“125”表示“操作中止”等。这就是让我在这里和那里添加一点跟踪的原因:

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

2。 Asio Review,更多风格

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

    整个迭代器都在跳舞。但不易出错。

  2. 使用 namespace 别名获得可读/可管理的行

    using boost::asio::ip::tcp;
    namespace ba = boost::asio;
    namespace bap = boost::asio::placeholders;
    namespace ssl = boost::asio::ssl;
    
  3. 使用智能指针(删除这个?呃)

  4. 考虑使用 1 个 io_service。使用两个并没有增加任何东西,真的,名字也没有澄清任何事情。事实上,盯着你的代码的第一分钟让我理清了客户端和服务器的代码,煞费苦心地验证它们没有错误地使用错误的服务,导致 run() 过早完成.

  5. 考虑竞争条件。在您的代码中,服务器和客户端独立运行,不同步。 至少添加一个延迟:

    boost::this_thread::sleep_for(boost::chrono::seconds(1));
    

    避免 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();
    
  7. 事实上,使用 1 个 io_service 和 1 个线程,您可以避开上述所有操作(由于 implicit strand,异步操作是同步的)

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

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

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

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

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

    ba::async_read(socket_, ba::buffer(reply_, bytes_transferred),
                   boost::bind(&client::handle_read, this, bap::error, bap::bytes_transferred));
    

    我希望是这样的

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

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

奖金

  1. 为接受添加一个截止日期,以便我们可以在某个空闲时间间隔停止服务器

  2. 既然您现在正在使用智能指针(不是吗?),那么在此位置添加 session 关闭也很容易 (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();
}

打印

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

关于c++ - boost ssl 连接过程失败,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/45812850/

相关文章:

c++ - 在 C++ 中实现数组

c++ - vector 的返回 vector

c++ - 在 C++ 可执行文件中枚举派生类

c++ - 与小代码的结果相比,boost 的字节序检查不同

c++ - 使用 liblttng 在 C++ 中自动插入跟踪点语句

cookies - 在发送敏感数据后关闭 SSL 是否安全?

ssl - 如何使用 openssl 创建 P12 文件

windows - 如果命令成功,则继续下一步

c++ - 将 tm 结构转换为 boost::local_time::local_date_time

c++ - boost::线程函数执行