【发布时间】:2020-12-21 23:06:01
【问题描述】:
注意!!!问题是针对boost::asio library 的专家。不幸的是,我不能让代码更紧凑,它包含描述问题的最少数量。该代码是示例,人工创建的。在 cmets 中已知和描述的崩溃的地方,它旨在说明崩溃! NO need 对代码调试有任何帮助...
问题是关于如何设计 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;
}
假设CTcpBase::add_server() 和CTcpBase::remove_server() 将被不同线程的外部客户端调用。 asio 上下文在它自己的线程中处理它。
让我们考虑两种情况:
- 启动应用程序并等待半分钟。
崩溃发生在
CAsioServer::do_accept()中,请参见下面的输出。 Debug Console Output - 启动应用程序。任何外部客户端连接到端口 502 并等待不到 20 秒。
崩溃发生在
CAsioConnection::do_read(),请参见下面的输出。 Debug Console Output
似乎 asio 框架调用推迟了 asio::async_read() 和 acceptor_.async_accept() 处理程序,而其类的实体已经销毁。
我已通过错误检查修复了处理程序,但该解决方案似乎并不可靠。谁知道可能还有什么其他错误和场景...有时,当客户端断开连接时,我需要清理asio::async_read() 设置的connection_,我如何确定服务器或连接对象仍然存在?...
有什么方法可以让 boost::asio 框架阻止为已经被销毁的对象调用延迟的处理程序?或者如何通过对象已经被销毁的错误码识别(be 100% sure)?或者我在 asio 范围内还有其他解决方案或设计模式 - 如何在一个运行的线程中处理动态创建/销毁的服务器及其连接,而无需互斥锁和其他东西......
【问题讨论】:
-
不相关的战术说明:遍历
set寻找项目没有多大意义。set专为高速查找独特元素而设计,循环通过set将比使用set::find要求 set 完成工作要慢几个数量级。也就是说,如果您的编译器至少支持 C++11,set::erase将一次性为您完成整个工作。 -
我建议使用minimal reproducible example 更新代码。如果您添加包含并将
pseudo_main5更改为main,看起来您离编译的示例不远了,而且为了制作他们可以试验的示例而更改的人越少,他们就越不可能'会添加他们自己的错误或意外修复你正在寻找的错误。另外,通过在制作 MRE 时进行几轮分而治之,您可能会在其他人之前发现错误。这是每个人的胜利。 -
注意:不要只在控制台中调试,使用 IDE 附带的调试器来收集信息 还要留意程序的错误消息。当您看到像 DDDDDDDD 这样的数字太常规而不能算运气时,look it up。 DDDDDDDD 通常是释放堆内存。
this已发布。弄清楚this应该指的是什么(在调试器中使用回溯),您首先确定了是谁发布了它。 -
感谢 cmets。我已经更新了代码以使其更具可编译性。正如您通过输出“调试控制台输出”图片所看到的那样,我对其进行了测试。我确切地知道崩溃发生的地点和原因。这不是我的真实代码 - 它是问题的示例/描述。问题是 - 如何强制 asio 不对已经销毁的对象调用 posponed 处理程序,或者如何通过错误代码识别对象已经被销毁,或者我在 asio 范围内还有其他解决方案......跨度>
-
我投票结束这个问题,因为作者不明白 minimal reproducible example 是什么。基本上他在这里放弃调试工作。
标签: c++ boost boost-asio asio