boost :: asio中的未经请求的消息崩溃应用程序,没有SSL它工作正常,为什么?

问题描述:

我想通过SSL连接发送未经请求的消息。这意味着服务器发送的消息不是基于客户端的请求,而是因为客户端需要了解的某些事件。boost :: asio中的未经请求的消息崩溃应用程序,没有SSL它工作正常,为什么?

我只使用boost站点的SSL服务器示例,添加了一个定时器,它在10秒后发送'hello',在定时器到期之前一切正常(服务器回声的一切),'hello'也被接收到,但在此之后,应用程序在下次发送文本到服务器时崩溃。

对我来说,更奇怪的是,当我禁用SSL代码,所以使用正常的套接字,并使用telnet做同样的事情,它工作得很好,并保持良好的工作!

我第二次遇到这个问题,我真的不知道为什么会发生这种情况。

下面是我为了演示问题而改变的总体来源。编译它没有SSL定义和使用telnet和一切工作正常,定义SSL和使用openssl,或从boost网站的客户端SSL示例和事情崩溃。

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

//#define SSL 

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) 
#ifdef SSL 
    : socket_(io_service, context) 
#else 
    : socket_(io_service) 

#endif  
    { 
    } 

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

    void start() 
    { 
#ifdef SSL  
    socket_.async_handshake(boost::asio::ssl::stream_base::server, 
     boost::bind(&session::handle_handshake, this, 
      boost::asio::placeholders::error)); 
#else 
     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)); 

     boost::shared_ptr<boost::asio::deadline_timer> timer(new  boost::asio::deadline_timer(socket_.get_io_service())); 
     timer->expires_from_now(boost::posix_time::seconds(10)); 
     timer->async_wait(boost::bind(&session::SayHello, this, _1, timer)); 

#endif  
    } 

    void handle_handshake(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)); 

     boost::shared_ptr<boost::asio::deadline_timer> timer(new  boost::asio::deadline_timer(socket_.get_io_service())); 
     timer->expires_from_now(boost::posix_time::seconds(10)); 
     timer->async_wait(boost::bind(&session::SayHello, this, _1, timer)); 

    } 
    else 
    { 
     delete this; 
    } 
    } 

    void SayHello(const boost::system::error_code& error, boost::shared_ptr<  boost::asio::deadline_timer > timer) { 
     std::string hello = "hello"; 

     boost::asio::async_write(socket_, 
      boost::asio::buffer(hello, hello.length()), 
      boost::bind(&session::handle_write, this, 
      boost::asio::placeholders::error)); 

     timer->expires_from_now(boost::posix_time::seconds(10)); 
     timer->async_wait(boost::bind(&session::SayHello, this, _1, timer)); 

    } 


    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 
    { 
     std::cout << "session::handle_read() -> Delete, ErrorCode: "<< error.value() <<  std::endl; 
     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 
    { 
     std::cout << "session::handle_write() -> Delete, ErrorCode: "<< error.value() <<  std::endl; 
     delete this; 
    } 
    } 

private: 
#ifdef SSL  
    ssl_socket socket_; 
#else 
    boost::asio::ip::tcp::socket socket_; 
#endif 
    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_(boost::asio::ssl::context::sslv23) 
    { 
#ifdef SSL   
    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("dh512.pem"); 
#endif 
    start_accept(); 
    } 

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

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

    void handle_accept(session* new_session, 
     const boost::system::error_code& error) 
    { 
    if (!error) 
    { 
     new_session->start(); 
    } 
    else 
    { 
     delete new_session; 
    } 

    start_accept(); 
    } 

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

int main(int argc, char* argv[]) 
{ 
    try 
    { 
    boost::asio::io_service io_service; 

    using namespace std; // For atoi. 
    server s(io_service, 7777 /*atoi(argv[1])*/); 

    io_service.run(); 
    } 
    catch (std::exception& e) 
    { 
    std::cerr << "Exception: " << e.what() << "\n"; 
    } 

    return 0; 
} 

我使用boost 1.49和OpenSSL 1.0.0i-FIPS 4月19日2012年我试图尽可能多地研究这个问题,我最后一次有这个问题(几个月前),我收到了错误号码,我可以跟踪到此错误消息:error: decryption failed or bad record mac

但我不知道怎么回事,如何解决这个问题,欢迎提出任何建议。

问题是多个并发异步读取和写入。即使使用原始套接字,我也能够崩溃此程序(glibc检测到双倍空闲或损坏)。让我们看看会开始后会发生什么(在括号中我把并发调度异步的数量读取和写入):

  1. 时间表异步读取(1,0)
  2. (假定这些数据的来源)handle_read被执行时,它调度异步写入(0,1)
  3. handle_write执行(数据被写入),它的时间表异步读取(1,0)

    现在,它可以遍历1 - 3没有任何问题下去。但是,然后定时器到期...

  4. (假设,没有新的数据来自客户端,所以仍然有一个异步读取计划)定时器到期,所以SayHello执行,它调度异步写入,仍然没有问题(1, 1)
  5. handle_write执行(从SayHello数据被写入,但仍然没有新的数据来自客户端),它的时间表异步读取(2,0)

    现在,我们正在这样做。如果来自客户端的任何新数据将会出现,其中一部分可以通过一次异步读取和另一部分读取。对于原始套接字,它甚至可能工作(尽管可能,可能有2个并发写入计划,所以在客户端的回显可能看起来混合)。对于SSL,这可能会破坏传入的数据流,这可能会发生。

如何解决此问题:

  1. strand不会在这种情况下帮助(这是不是并发处理程序执行,但计划异步读取和写入)。
  2. 如果SayHello中的异步写入处理程序不做任何事情(那么将没有并发读取,但仍可能发生并发写入),这还不够。
  3. 如果你真的想要两种不同类型的写入(echo和timer),你必须实现某种消息队列来写,以避免混合来自echo和timer的写入。
  4. 一般评论:这是一个简单的例子,但使用shared_ptr而不是delete this是更好的方式来处理与boost :: asio的内存分配。它将防止错误导致内存泄漏。
+0

非常感谢您的非常清晰的分析,我现在明白了这个问题。但是不是增加一个队列,混合写入也可以通过使用互斥锁来阻止,对吧? – ikku 2012-08-12 12:01:20

+0

要使用互斥锁,您还需要使用同步写入,这可能不是您想要的。而不是队列,一个额外的缓冲区和布尔标志可以做到这一点。它甚至更好,因为它限制了内存使用。 – Greg 2012-08-12 12:07:33

+0

不提升:: asio已经排队了吗?多次调用'boost :: asio :: async_write'会混淆发送的数据吗? – ikku 2012-08-12 12:12:31