boost::asio 中的未经请求的消息使应用程序崩溃,没有 SSL 它可以正常工作,为什么
Unsolicited messages in boost::asio crashes application, without SSL it works fine, why?
我想通过SSL连接发送未经请求的消息。这意味着服务器发送消息不是基于来自客户端的请求,而是因为发生了客户端需要知道的某些事件。
我只是使用提升站点中的 SSL 服务器示例,添加了一个在 10 秒后发送"hello"的计时器,在计时器过期之前一切正常(服务器回显的所有内容),还收到"hello",但之后应用程序在下次向服务器发送文本时崩溃。
对我来说,更奇怪的是,当我禁用SSL代码时,因此使用普通套接字并使用telnet执行相同的操作,它可以正常工作并且一直工作正常!!
我现在第二次遇到这个问题,我真的不知道为什么会这样发生。
以下是我为演示问题而更改的总来源。在没有 SSL 定义的情况下编译它并使用 telnet,一切正常,定义 SSL 并使用 openssl,或者来自提升网站的客户端 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 19 Apr 2012。我尝试尽可能多地调查此问题,上次遇到此问题时(几个月前),我收到一个错误号,我可以追溯到此错误消息:error: decryption failed or bad record mac
。
但我不知道出了什么问题以及如何解决这个问题,欢迎任何建议。
问题是多个并发异步读取和写入。即使使用原始套接字,我也能够使该程序崩溃(glibc检测到双重释放或损坏)。让我们看看会话启动后会发生什么(在大括号中,我输入并发计划的异步读取和写入次数):
-
计划异步读取 (1, 0)
-
(假设数据来了)
handle_read
执行,它会调度异步写入 (0, 1) -
(写入数据)执行
handle_write
,它调度异步读取 (1, 0)现在,它可以循环访问 1。 - 3. 无限期没有任何问题。但是随后计时器过期...
-
(假设客户端没有新数据,所以仍然有一个异步读取计划)计时器过期,所以
SayHello
执行,它调度异步写入,仍然没有问题(1,1) -
(写入
SayHello
的数据,但仍然没有来自客户端的新数据) 执行handle_write
,它会调度异步读取 (2, 0)现在,我们完成了。如果来自客户端的任何新数据,其中一部分可以由一个异步读取读取,部分可以由另一个异步读取读取。对于原始套接字,它甚至可能看起来可以工作(尽管有可能,可能会计划有 2 个并发写入,因此客户端的 echo 可能看起来很混合)。对于SSL,这可能会损坏传入的数据流,这可能是发生的事情。
如何解决:
- 在这种情况下,
strand
将无济于事(它不是并发处理程序执行,而是计划的异步读取和写入)。 - 如果
SayHello
中的异步写入处理程序不执行任何操作,这还不够(届时不会有并发读取,但仍可能发生并发写入)。 - 如果你真的想要两种不同的写入(echo和timer),你必须实现某种消息队列来写入,以避免混合来自echo和timer的写入。
- 一般评论:这是一个简单的例子,但使用
shared_ptr
而不是delete this
是使用 boost::asio 处理内存分配的更好方法。它将防止丢失导致内存泄漏的错误。
- 为什么在Windows上的VS 2019和Clang 9中"size_t"在没有标题的情况下工作
- 为什么我的 std::ref 无法按预期工作?
- 为什么std::condition_variable notify_all的工作速度比notify_one快(对于随机请
- 有人能解释一下为什么下界是这样工作的吗C++的
- 当我在第一个循环中使用"auto"时,它工作正常,但是使用"int"它会给出错误,为什么?
- 为什么stream::忽略未按预期工作
- 为什么常量词在重载运算符中不与 ostream 对象一起使用<<?
- 为什么 HeapFree() 不能正常工作?
- 为什么我们将单个或多维数组的大小声明为常量值?
- 为什么我在 AVR 中的中断无法正常工作?
- 为什么指针在对二维数组进行排序时无法正常工作?
- 为什么C++需要公共继承,忽略朋友声明,才能使动态向下工作?
- std::async 如何工作:为什么它会调用这么多次复制/移动?
- 静态 constexpr 函数在模板结构中工作,但不能在结构中工作.为什么?
- 禁用 GPU 使我的 CNTK 程序正常工作.为什么
- 为什么STD ::计数将常数传递给Lambda,而不是在弦上工作时而不是字符
- C++余弦在没有 std 命名空间的情况下工作 - 为什么
- c++的pow(2,1000)对于double来说通常太大了,但它正在工作.为什么
- 程序不是在win上工作,而是在mac上工作.为什么?
- 当我用字符串代替char时,代码可以完美地工作.为什么如此