C++ 如何在动态销毁boost::asio实体时消除崩溃?
注意!!!这个问题针对的是C++ 如何在动态销毁boost::asio实体时消除崩溃?,c++,boost,boost-asio,asio,C++,Boost,Boost Asio,Asio,注意!!!这个问题针对的是boost::asio库方面的专家。不幸的是,我不能做的代码更紧凑,它包含了一个最低数额的描述问题。代码是人工创建的示例。在已知并在评论中描述了其崩溃的地方,它旨在说明崩溃无需调试代码的任何帮助 问题是如何设计asio服务器,而不是它在哪里崩溃 这个例子接近于官方boost::asio文档中的“聊天服务器”设计。但是,与官方示例不同,官方示例仅动态创建/销毁连接类的对象,在我的示例中,服务器及其连接类实体都是动态创建/销毁的。。。我相信这种模式的实现应该在asio爱好者
boost::asio库
方面的专家。不幸的是,我不能做的代码更紧凑,它包含了一个最低数额的描述问题。代码是人工创建的示例。在已知并在评论中描述了其崩溃的地方,它旨在说明崩溃<代码>无需调试代码的任何帮助
问题是如何设计asio服务器,而不是它在哪里崩溃
这个例子接近于官方boost::asio文档中的“聊天服务器”设计。但是,与官方示例不同,官方示例仅动态创建/销毁连接类的对象,在我的示例中,服务器及其连接类实体都是动态创建/销毁的。。。我相信这种模式的实现应该在asio爱好者中广为人知,下面描述的问题应该已经有人解决了
请看代码。
在这里,CAsioServer和CAsioConnection的实体是动态创建和销毁的
#include <map>
#include <array>
#include <set>
#include <vector>
#include <deque>
#include <thread>
#include <iostream>
#include <asio.hpp>
#include <iomanip>
class CAsioConnection
: public std::enable_shared_from_this<CAsioConnection>
{
public:
using PtrType = std::shared_ptr<CAsioConnection>;
CAsioConnection(asio::ip::tcp::socket socket, std::set<CAsioConnection::PtrType>& connections)
: socket_(std::move(socket)), connections_(connections)
{
std::cout << "-- CAsioConnection is creating, socket: " << socket_.native_handle() << "\n";
}
virtual ~CAsioConnection()
{
std::cout << "-- CAsioConnection is destroying , socket: " << socket_.native_handle() << "\n";
}
void read() { do_read(); }
private:
void do_read(void)
{
uint8_t buff[3];
asio::async_read(socket_, asio::buffer(buff,3),
[this](std::error_code ec, std::size_t /*length*/) {
if (!ec)
{
do_read();
}
else
{
std::cout << "-- CAsioConnection::do_read() error : " << ec.message() << "\n";
// Here is the crash N2
connections_.erase(shared_from_this());
// Crash may be fixed by the code below
//if (ec.value() != 1236) // (winerror.h) #define ERROR_CONNECTION_ABORTED 1236L
// connections_.erase(shared_from_this());
}
});
}
asio::ip::tcp::socket socket_;
std::set<CAsioConnection::PtrType>& connections_;
};
class CAsioServer
: public std::enable_shared_from_this<CAsioServer>
{
public:
using PtrType = std::shared_ptr<CAsioServer>;
CAsioServer(int port, asio::io_context& io, const asio::ip::tcp::endpoint& endpoint)
: port_(port), acceptor_(io, endpoint)
{
std::cout << "-- CAsioServer is creating, port: " << port_ << "\n";
}
virtual ~CAsioServer()
{
std::cout << "-- CAsioServer is destroying , port: " << port_ << "\n";
}
int port(void) { return port_; }
void accept(void) { do_accept(); }
private:
void do_accept()
{
acceptor_.async_accept([this](std::error_code ec, asio::ip::tcp::socket socket) {
if (!ec)
{
std::cout << "-- CAsioServer::do_accept() connection to socket: " << socket.native_handle() << "\n";
auto c = std::make_shared<CAsioConnection>(std::move(socket), connections_);
connections_.insert(c);
c->read();
}
else
{
// Here is the crash N1
std::cout << "-- CAsioServer::do_accept() error : " << ec.message() << "\n";
// Crash may be fixed by the code below
//if (ec.value() == 995) // (winerror.h) #define ERROR_OPERATION_ABORTED 995L
// return;
}
// Actually here is the crash N1 )), but the fix is above...
do_accept();
});
}
int port_;
asio::ip::tcp::acceptor acceptor_;
std::set<CAsioConnection::PtrType> connections_;
};
//*****************************************************************************
class CTcpBase
{
public:
CTcpBase()
{
// heart beat timer to keep it alive
do_heart_beat();
t_ = std::thread([this] {
std::cout << "-- io context is RUNNING!!!\n";
io_.run();
std::cout << "-- io context has been STOPED!!!\n";
});
}
virtual ~CTcpBase()
{
io_.stop();
if (t_.joinable())
t_.join();
}
void add_server(int port)
{
io_.post([this, port]
{
for (auto s : servers_)
if (port == s->port())
return;
auto endpoint = asio::ip::tcp::endpoint(asio::ip::tcp::v4(), port);
auto s = std::make_shared<CAsioServer>(port, io_, endpoint);
s->accept();
servers_.insert(s);
});
}
void remove_server(int port)
{
io_.post([this, port]
{
for (auto s : servers_)
if (port == s->port())
{ servers_.erase(s); return; }
});
}
private:
void do_heart_beat(void)
{
std::cout << "-- beat\n";
auto timer = std::make_shared<asio::steady_timer>(io_, asio::chrono::milliseconds(3000));
timer->async_wait([timer, this](const asio::error_code& ec) {
do_heart_beat();
});
}
asio::io_context io_;
std::thread t_;
std::set<CAsioServer::PtrType> servers_;
};
//*****************************************************************************
int main(void)
{
CTcpBase tcp_base;
std::cout << "CONNECT the server to port 502\n";
tcp_base.add_server(502);
std::this_thread::sleep_for(std::chrono::seconds(20));
std::cout << "REMOVE the server from port 502\n";
tcp_base.remove_server(502);
std::this_thread::sleep_for(std::chrono::seconds(10));
return 0;
}
#包括
#包括
启动应用程序。通过任何外部客户端连接到端口502,并等待不到20秒。
崩溃发生在CAsioConnection::do_read()
中,请参见下面的输出。
asio框架似乎在其类的实体已销毁时调用了延迟的asio::async\u read()
和acceptor.async\u accept()
处理程序
我已经用错误检查修复了处理程序,但解决方案似乎不可靠。谁知道还会有什么其他错误和情况…有时,当客户端断开连接时,我需要清除asio::async\u read()
上的连接设置,如何确保服务器或连接对象仍处于活动状态
有没有办法要求boost::asio framework防止调用已销毁对象的延迟处理程序?或者如何通过错误代码识别对象已被销毁的(100%确定)
?或者,在asio范围内可能还有其他解决方案或设计模式-如何在一个运行线程中处理恶意创建/破坏的服务器及其连接,而不使用互斥锁和其他东西…首先检查您的io_服务是否严格运行单线程。这在代码中不可见。如果不是,则共享状态(如连接
)需要同步访问
事实上,您可以有一个接受循环形式的逻辑串,但要利用这一点,您应该使对连接的所有访问都发生在那里,请参见例如
- 在这里,会话列表直接保存会话,根本不需要共享指针:
- 或者在这里,我们有共享指针,并在会话列表中存储弱指针,这些指针可以从接受循环内部“垃圾收集”:
更新
buff
是一个局部变量,它会导致未定义的行为,因为它在异步读取操作的整个时间内都无效
- 一般来说,从这个
习惯用法中共享是没有意义的,而且
还保留一个共享指针容器,它已经决定了生命周期
您的问题似乎是有时CAsioServer
会被简单地销毁,这意味着connections\uu
的所有元素都会被释放,此时它们的CAsioConnection
对象可能会被销毁。它还将销毁CAsioServer
每当Asio对象被销毁时,任何挂起的异步操作都将失败,并出现Asio::error:operation\u aborted
,这确实意味着您已做出响应。但是,当调用完成处理程序时,对象已经无效
在中,我刚刚注意到缺少一个关键成分:您从未在任何完成处理程序中捕获/绑定指向CAsioConnection
的共享指针
这是非常不习惯的
相反,您使用共享指针来管理生命周期。如果您还需要一个连接列表,那么将其设置为一个弱指针列表,以便它只观察生命周期
变化点:
- 无需使服务器从此\u启用\u共享\u
连接
应该包含弱指针,甚至是非拥有指针。这里的弱点显然要安全得多。事实上,您可以选择放弃该容器,因为似乎没有任何东西在使用它。在下面的示例中,我选择保留它,以便您可以看到它的实际效果
- 在完成处理程序中从此
捕获shared\u,以确保对象在激发时仍然有效:
asio::async_read(socket_, asio::buffer(buff,3),
[this, self=shared_from_this()](error_code ec, std::size_t /*length*/) {
简化
注意我选择了std::list
,因为它消除了平等/排序的需要(请参见std::owner_less
),这变得越来越难看,因为在CAsioConnection
类中存储对容器的引用的方式使其循环依赖(在实例化owner\less
类之前,CAsioConnection
类型尚未完成)。我只是选择了(不需要的)复杂性
#include <boost/asio.hpp>
#include <iostream>
#include <list>
#include <memory>
namespace asio = boost::asio;
using error_code = boost::system::error_code; // compat
class CAsioConnection : public std::enable_shared_from_this<CAsioConnection> {
public:
using PtrType = std::shared_ptr<CAsioConnection>;
CAsioConnection(asio::ip::tcp::socket socket) : socket_(std::move(socket)) {
log(__FUNCTION__);
}
~CAsioConnection() { log(__FUNCTION__); }
void read() { do_read(); }
private:
void log(std::string_view msg) const {
error_code ec;
std::clog << msg << ", socket: " << socket_.remote_endpoint(ec) << "\n";
}
uint8_t buff[256];
void do_read() {
asio::async_read(socket_, asio::buffer(buff),
[this, self = shared_from_this()](error_code ec, std::size_t length) {
if (!ec) {
log(__FUNCTION__ + (" length: " + std::to_string(length)));
do_read();
} else {
log(__FUNCTION__ + (" error: " + ec.message()));
}
});
}
asio::ip::tcp::socket socket_;
};
class CAsioServer {
public:
CAsioServer(asio::io_context& io, const asio::ip::tcp::endpoint& endpoint)
: acceptor_(io, endpoint) { log(__FUNCTION__); }
~CAsioServer() { log(__FUNCTION__); }
int port() const { return acceptor_.local_endpoint().port(); }
void accept() { do_accept(); }
private:
void do_accept() {
acceptor_.async_accept([this](error_code ec,
asio::ip::tcp::socket socket) {
if (!ec) {
auto c = std::make_shared<CAsioConnection>(std::move(socket));
connections_.push_back(c);
c->read();
} else {
log(__FUNCTION__ + (" error: " + ec.message()));
}
connections_.remove_if(std::mem_fn(&WeakPtr::expired));
if (acceptor_.is_open())
do_accept();
});
}
void log(std::string_view msg) const {
std::clog << msg << ", port: " << port() << "\n";
}
asio::ip::tcp::acceptor acceptor_;
using WeakPtr = std::weak_ptr<CAsioConnection>;
std::list<WeakPtr> connections_;
};
int main() {
boost::asio::io_context io;
CAsioServer server(io, { {}, 7878 });
server.accept();
io.run_for(std::chrono::seconds(10));
}
#包括
#包括
#包括
#包括
名称空间asio=boost::asio;
使用error\u code=boost::system::error\u code;//compat
类CAsioConnection:public std::从\u启用\u共享\u{
公众:
使用PtrType=std::shared\u ptr;
CAsioConnection(asio::ip::tcp::socket-socket):socket(std::move(socket)){
日志(函数);
}
~CAsioConnection(){log(_函数__);}
void read(){do_read();}
私人:
无效日志(std::string\u view msg)常量{
错误代码ec;
我亲爱的华生,木屐小学
问题的关键是——我非常信任你
./a.out& sleep 1; nc -w 1 127.0.0.1 7878 < main.cpp
CAsioServer, port: 7878
CAsioConnection, socket: 127.0.0.1:50628
operator() length: 256, socket: 127.0.0.1:50628
operator() length: 256, socket: 127.0.0.1:50628
operator() length: 256, socket: 127.0.0.1:50628
operator() length: 256, socket: 127.0.0.1:50628
operator() length: 256, socket: 127.0.0.1:50628
operator() length: 256, socket: 127.0.0.1:50628
operator() length: 256, socket: 127.0.0.1:50628
operator() length: 256, socket: 127.0.0.1:50628
operator() length: 256, socket: 127.0.0.1:50628
operator() error: End of file, socket: 127.0.0.1:50628
~CAsioConnection, socket: 127.0.0.1:50628
~CAsioServer, port: 7878
#include <map>
#include <array>
#include <set>
#include <vector>
#include <deque>
#include <thread>
#include <iostream>
#include <asio.hpp>
#include <iomanip>
class CAsioConnection
: public std::enable_shared_from_this<CAsioConnection>
{
public:
using PtrType = std::shared_ptr<CAsioConnection>;
CAsioConnection(asio::ip::tcp::socket socket, std::set<CAsioConnection::PtrType>& connections)
: socket_(std::move(socket)), connections_(connections), destroying_in_progress(false)
{
std::cout << "-- CAsioConnection is creating\n";
}
virtual ~CAsioConnection()
{
std::cout << "-- CAsioConnection is destroying\n";
}
void read() { do_read(); }
void hasta_la_vista(void)
{
destroying_in_progress = true;
std::error_code ec;
socket_.cancel(ec);
}
private:
void do_read(void)
{
auto self(shared_from_this());
asio::async_read(socket_, asio::buffer(buff),
[this, self](std::error_code ec, std::size_t /*length*/) {
if (destroying_in_progress)
return;
if (!ec)
{
do_read();
}
else
{
std::cout << "-- CAsioConnection::do_read() error : (" << ec.value() << ") " << ec.message() << "\n";
hasta_la_vista();
connections_.erase(shared_from_this());
}
});
}
uint8_t buff[3];
asio::ip::tcp::socket socket_;
bool destroying_in_progress;
std::set<CAsioConnection::PtrType>& connections_;
};
//*****************************************************************************
class CAsioServer
: public std::enable_shared_from_this<CAsioServer>
{
public:
using PtrType = std::shared_ptr<CAsioServer>;
CAsioServer(int port, asio::io_context& io, const asio::ip::tcp::endpoint& endpoint)
: port_(port), destroying_in_progress(false), acceptor_(io, endpoint)
{
std::cout << "-- CAsioServer is creating, port: " << port_ << "\n";
}
virtual ~CAsioServer()
{
for (auto c : connections_)
{
c->hasta_la_vista();
}
std::cout << "-- CAsioServer is destroying , port: " << port_ << "\n";
}
int port(void) { return port_; }
void accept(void) { do_accept(); }
void hasta_la_vista(void)
{
destroying_in_progress = true;
std::error_code ec;
acceptor_.cancel(ec);
}
private:
void do_accept()
{
auto self(shared_from_this());
acceptor_.async_accept([this, self](std::error_code ec, asio::ip::tcp::socket socket) {
if (destroying_in_progress)
return;
if (!ec)
{
std::cout << "-- CAsioServer::do_accept() connection to socket: " << socket.native_handle() << "\n";
auto c = std::make_shared<CAsioConnection>(std::move(socket), connections_);
connections_.insert(c);
c->read();
}
else
{
std::cout << "-- CAsioServer::do_accept() error : (" << ec.value() << ") "<< ec.message() << "\n";
}
do_accept();
});
}
int port_;
bool destroying_in_progress;
asio::ip::tcp::acceptor acceptor_;
std::set<CAsioConnection::PtrType> connections_;
};
//*****************************************************************************
class CTcpBase
{
public:
CTcpBase()
{
// heart beat timer to keep it alive
do_heart_beat();
t_ = std::thread([this] {
std::cout << "-- io context is RUNNING!!!\n";
io_.run();
std::cout << "-- io context has been STOPED!!!\n";
});
}
virtual ~CTcpBase()
{
io_.stop();
if (t_.joinable())
t_.join();
}
void add_server(int port)
{
io_.post([this, port] {
for (auto& s : servers_)
if (port == s->port())
return;
auto endpoint = asio::ip::tcp::endpoint(asio::ip::tcp::v4(), port);
auto s = std::make_shared<CAsioServer>(port, io_, endpoint);
s->accept();
servers_.insert(s);
});
}
void remove_server(int port)
{
io_.post([this, port] {
for (auto s : servers_)
if (port == s->port())
{
s->hasta_la_vista();
servers_.erase(s);
return;
}
});
}
private:
void do_heart_beat(void)
{
std::cout << "-- beat\n";
auto timer = std::make_shared<asio::steady_timer>(io_, asio::chrono::milliseconds(3000));
timer->async_wait([timer, this](const std::error_code& ec) {
do_heart_beat();
});
}
asio::io_context io_;
std::thread t_;
std::set<CAsioServer::PtrType> servers_;
};
//*****************************************************************************
int main(void)
{
CTcpBase tcp_base;
std::cout << "CONNECT the server to port 502\n";
tcp_base.add_server(502);
std::this_thread::sleep_for(std::chrono::seconds(20));
std::cout << "REMOVE the server from port 502\n";
tcp_base.remove_server(502);
std::this_thread::sleep_for(std::chrono::seconds(10));
return 0;
}