Boost::Asio - 抛出 get_io_service 异常
Posted
技术标签:
【中文标题】Boost::Asio - 抛出 get_io_service 异常【英文标题】:Boost::Asio - get_io_service exception thrown 【发布时间】:2015-09-04 20:14:08 【问题描述】:谁能向我解释为什么当我想调用 get_io_service() 时出现以下异常?
我看到在启动时接受器已初始化,但是当客户端想要连接并且服务器想要打开新连接时,接受器有一些随机数。我不知道为什么会这样。
我的代码:
main.cpp
#include "TServer.h"
#include "TDatabase.h"
#include "Includes.h"
#include "Structures.h"
int main()
try
std::cout << "========================================" << std::endl
<< "= Game Server v1.0 by Gravity1 =" << std::endl
<< "========================================" << std::endl;
boost::asio::io_service io_service;
Database database;
std::vector<std::vector<TServer>> Server;
srand(time(0));
boost::property_tree::ptree pt;
boost::property_tree::ini_parser::read_ini("game_server_config.ini", pt);
database.host = pt.get<std::string>("DATABASE.HOST");
database.username = pt.get<std::string>("DATABASE.USER");
database.password = pt.get<std::string>("DATABASE.PASS");
database.schema = pt.get<std::string>("DATABASE.SCHEMA");
std::shared_ptr<TDatabase> Database_ptr = std::make_shared<TDatabase>(database);
Database_ptr->Connect();
short server_count = pt.get<short>("GAME_SERVER.SERVER_COUNT");
if (server_count > 0)
Server.resize(server_count);
for (int i = 0; i < server_count; i++)
short channel_count = pt.get<short>("GAME_SERVER.SERVER_" + std::to_string(i + 1) + "_CHANNEL_COUNT");
for (int j = 0; j < channel_count; j++)
Canal CanalTemp;
CanalTemp.ip = pt.get<std::string>("GAME_SERVER.SERVER_" + std::to_string(i + 1) + "_CHANNEL" + std::to_string(j + 1) + "_IP");
CanalTemp.port = pt.get<short>("GAME_SERVER.SERVER_" + std::to_string(i + 1) + "_CHANNEL" + std::to_string(j + 1) + "_PORT");
boost::asio::ip::tcp::endpoint endpoint(boost::asio::ip::address::from_string(CanalTemp.ip), CanalTemp.port);
Server[i].emplace_back(io_service, Database_ptr,endpoint);
io_service.run();
catch (std::exception &e)
std::cerr << e.what() << std::endl;
std::cin.get();
return 0;
TServer.cpp
TServer::TServer(boost::asio::io_service &io_service,std::shared_ptr<TDatabase> database, const boost::asio::ip::tcp::endpoint &endpoint) :
acceptor(io_service,endpoint)
Accept_Connection();
void TServer::Accept_Connection()
Connection = std::make_shared<TSession>(acceptor.get_io_service(),Database);
acceptor.async_accept(*(Connection->Socket()),(boost::bind(&TServer::Handle_Connection, this, Connection, boost::asio::placeholders::error)));
void TServer::Handle_Connection(std::shared_ptr<TSession> Connection, const boost::system::error_code &error)
if (!error)
Connection->Start();
Accept_Connection();
【问题讨论】:
也许你忘了给acceptor.listen()
打电话?
livecoding.tv/sehe (experiment)
【参考方案1】:
问题很简单。
您将TServer
s 放置在向量的后面。当您这样做时,它将(可能)重新分配,使程序其他部分中保存的引用无效。见Iterator invalidation rules
在您的情况下,会立即保留这样的引用,因为 Accept_Connection()
是从构造函数中调用的,并且它绑定s 到 this
指针。请记住,this
指针指向 向量内 TServer
元素的地址。
哎呀。当您的完成处理程序触发时,该元素是/可能已被重新分配。所以指针只是悬空而你有Undefined Behaviour。
你可以用不同的方式修复它:
将向量替换为可确保插入时参考稳定性的容器。例如,您可以只使用list<>
:
std::list<std::list<TServer> > servers;
if (server_count > 0)
servers.resize(server_count);
auto current_server = servers.begin();
for (int i = 0; i < server_count; i++, ++current_server)
short channel_count = pt.get<short>("GAME_SERVER.SERVER_" + std::to_string(i + 1) + "_CHANNEL_COUNT");
for (int j = 0; j < channel_count; j++)
Canal CanalTemp;
CanalTemp.ip = pt.get<std::string>("GAME_SERVER.SERVER_" + std::to_string(i + 1) + "_CHANNEL" + std::to_string(j + 1) + "_IP");
CanalTemp.port = pt.get<short>("GAME_SERVER.SERVER_" + std::to_string(i + 1) + "_CHANNEL" + std::to_string(j + 1) + "_PORT");
tcp::endpoint endpoint(boost::asio::ip::address::from_string(CanalTemp.ip), CanalTemp.port);
current_server->emplace_back(io_service, Database_ptr, endpoint);
或者,您可以将初始绑定推迟到所有频道都添加到所有服务器之后:
TServer(boost::asio::io_service &io_service, std::shared_ptr<TDatabase> database, const boost::asio::ip::tcp::endpoint &endpoint)
: acceptor(io_service, endpoint), database(database)
//Accept_Connection();
在io_service::run()
之前明确地这样做:
for(auto& server: servers)
for(auto& channel: server)
channel.Accept_Connection();
io_service.run();
注意:事实上,在惯用的 Asio 代码中,直接从构造函数中运行异步操作通常不可能。看例如在
TSession
类型;它无法将完成处理程序绑定到成员函数,因为在构造函数 ("Note that prior to callingshared_from_this
on an objectt
, there must be ashared_ptr
that ownst
.") 中不允许shared_from_this()
。
两者都有效。我在这里选择第一个:
Live On Coliru
#include <boost/asio.hpp>
#include <boost/make_shared.hpp>
#include <boost/enable_shared_from_this.hpp>
#include <boost/bind.hpp>
#include <iostream>
// for iterator and reference stability (see:
// https://***.com/questions/6438086/iterator-invalidation-rules)
#include <list>
using tcp = boost::asio::ip::tcp;
struct Canal
std::string ip;
int port;
;
struct Database
std::string host, username, password, schema;
;
struct TDatabase
TDatabase(Database config) : details(config)
void Connect()
std::cout
<< "Connecting to fake database " << details.host << "/" << details.schema
<< " with user " << details.username << " and password '" << std::string(details.password.size(), '*') << "'\n";
private:
Database details;
;
struct TSession : std::enable_shared_from_this<TSession>
TSession(boost::asio::io_service& svc, std::shared_ptr<TDatabase> db) :
_svc(svc), _socket(_svc), _db(db)
tcp::socket& Socket() return _socket;
void Start()
boost::asio::async_read(_socket, _sb,
boost::bind(&TSession::HandleReceived, shared_from_this(),
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
void HandleReceived(boost::system::error_code ec, size_t bytes_transferred)
if (!ec || boost::asio::error::eof == ec)
std::cout << "Received from " << _socket.remote_endpoint() << ": '" << &_sb << "'\n";
else
std::cout << "Error reading from peer: " << ec.message() << "\n";
private:
boost::asio::io_service& _svc;
tcp::socket _socket;
std::shared_ptr<TDatabase> _db;
boost::asio::streambuf _sb;
;
struct TServer
tcp::acceptor acceptor;
std::shared_ptr<TDatabase> database;
TServer(boost::asio::io_service &io_service, std::shared_ptr<TDatabase> database, const boost::asio::ip::tcp::endpoint &endpoint)
: acceptor(io_service, endpoint), database(database)
Accept_Connection();
void Accept_Connection()
auto Connection = std::make_shared<TSession>(acceptor.get_io_service(), database);
acceptor.async_accept(Connection->Socket(),
boost::bind(&TServer::Handle_Connection, this, Connection, boost::asio::placeholders::error));
void Handle_Connection(std::shared_ptr<TSession> Connection, const boost::system::error_code &error)
if (!error)
Connection->Start();
Accept_Connection();
else
std::cout << "Error: " << error.message() << "\n";
;
//#include "TServer.h"
//#include "TDatabase.h"
//#include "Includes.h"
//#include "Structures.h"
#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/ini_parser.hpp>
int main()
try
std::cout << "========================================" << std::endl
<< "= Game Server v1.0 by Gravity1 =" << std::endl
<< "========================================" << std::endl;
boost::asio::io_service io_service;
Database database;
std::list<std::list<TServer> > servers;
srand(time(0));
boost::property_tree::ptree pt;
boost::property_tree::read_ini("game_server_config.ini", pt);
database.host = pt.get<std::string>("DATABASE.HOST");
database.username = pt.get<std::string>("DATABASE.USER");
database.password = pt.get<std::string>("DATABASE.PASS");
database.schema = pt.get<std::string>("DATABASE.SCHEMA");
std::shared_ptr<TDatabase> Database_ptr = std::make_shared<TDatabase>(database);
Database_ptr->Connect();
short server_count = pt.get<short>("GAME_SERVER.SERVER_COUNT");
if (server_count > 0)
servers.resize(server_count);
auto current_server = servers.begin();
for (int i = 0; i < server_count; i++, ++current_server)
short channel_count = pt.get<short>("GAME_SERVER.SERVER_" + std::to_string(i + 1) + "_CHANNEL_COUNT");
for (int j = 0; j < channel_count; j++)
Canal CanalTemp;
CanalTemp.ip = pt.get<std::string>("GAME_SERVER.SERVER_" + std::to_string(i + 1) + "_CHANNEL" + std::to_string(j + 1) + "_IP");
CanalTemp.port = pt.get<short>("GAME_SERVER.SERVER_" + std::to_string(i + 1) + "_CHANNEL" + std::to_string(j + 1) + "_PORT");
tcp::endpoint endpoint(boost::asio::ip::address::from_string(CanalTemp.ip), CanalTemp.port);
current_server->emplace_back(io_service, Database_ptr, endpoint);
io_service.run();
catch (std::exception &e)
std::cerr << e.what() << std::endl;
std::cin.get();
我用了一个配置
[DATABASE]
HOST=localhost
USER=root
PASS=youbet
SCHEMA=my_game
[GAME_SERVER]
SERVER_COUNT=1
SERVER_1_CHANNEL_COUNT=2
SERVER_1_CHANNEL1_IP=127.0.0.1
SERVER_1_CHANNEL1_PORT=6767
SERVER_1_CHANNEL2_IP=127.0.0.2
SERVER_1_CHANNEL2_PORT=6868
当在两个通道(端口 6767 和 6868)上运行客户端时,会打印出“无休止”的重复:
========================================
= Game Server v1.0 by Gravity1 =
========================================
Connecting to fake database localhost/my_game with user root and password '******'
Received from 127.0.0.1:54942: 'hello channel
'
Received from 127.0.0.1:37217: 'hello OTHER channel
'
Received from 127.0.0.1:54945: 'hello channel
'
Received from 127.0.0.1:37220: 'hello OTHER channel
'
Received from 127.0.0.1:54947: 'hello channel
'
Received from 127.0.0.1:37222: 'hello OTHER channel
'
Received from 127.0.0.1:54949: 'hello channel
'
Received from 127.0.0.1:37224: 'hello OTHER channel
'
Received from 127.0.0.1:54951: 'hello channel
'
Received from 127.0.0.1:37226: 'hello OTHER channel
'
Received from 127.0.0.1:54953: 'hello channel
'
Received from 127.0.0.1:37228: 'hello OTHER channel
'
Received from 127.0.0.1:54955: 'hello channel
'
Received from 127.0.0.1:37230: 'hello OTHER channel
'
Received from 127.0.0.1:54957: 'hello channel
'
Received from 127.0.0.1:37232: 'hello OTHER channel
'
【讨论】:
【参考方案2】:完全不相关,但您的配置格式确实要求采用分层格式,例如 JSON 或 XML。
为了好玩,我将该示例重构为使用 XML:
<?xml version="1.0"?>
<CONFIG>
<DATABASE>
<HOST>localhost</HOST>
<USER>root</USER>
<PASS>youbet</PASS>
<SCHEMA>my_game</SCHEMA>
</DATABASE>
<GAME_SERVER>
<SERVER>
<CHANNEL>
<IP>127.0.0.1</IP>
<PORT>6767</PORT>
</CHANNEL>
<CHANNEL>
<IP>127.0.0.2</IP>
<PORT>6868</PORT>
</CHANNEL>
</SERVER>
</GAME_SERVER>
</CONFIG>
您可以使用以下 sn-ps 阅读:
boost::property_tree::ptree pt;
boost::property_tree::read_xml("game_server_config.xml", pt);
if (auto dbconfig = pt.get_child_optional("CONFIG.DATABASE"))
database.host = dbconfig->get<std::string>("HOST");
database.username = dbconfig->get<std::string>("USER");
database.password = dbconfig->get<std::string>("PASS");
database.schema = dbconfig->get<std::string>("SCHEMA");
对于服务器/频道:
for (auto& serverconfig: pt.get_child("CONFIG.GAME_SERVER"))
if ("SERVER" != serverconfig.first)
continue;
servers.emplace_back();
auto& current_server = servers.back();
for (auto& channelconfig: serverconfig.second)
if ("CHANNEL" != channelconfig.first)
continue;
Canal CanalTemp;
CanalTemp.ip = channelconfig.second.get<std::string>("IP");
CanalTemp.port = channelconfig.second.get<short>("PORT");
tcp::endpoint endpoint(boost::asio::ip::address::from_string(CanalTemp.ip), CanalTemp.port);
current_server.emplace_back(io_service, Database_ptr, endpoint);
也看到 Live On Coliru :)
#include <boost/asio.hpp>
#include <boost/make_shared.hpp>
#include <boost/enable_shared_from_this.hpp>
#include <boost/bind.hpp>
#include <iostream>
// for iterator and reference stability (see:
// http://***.com/questions/6438086/iterator-invalidation-rules)
#include <list>
using tcp = boost::asio::ip::tcp;
struct Canal
std::string ip;
int port;
;
struct Database
std::string host, username, password, schema;
;
struct TDatabase
TDatabase(Database config) : details(config)
void Connect()
std::cout
<< "Connecting to fake database " << details.host << "/" << details.schema
<< " with user " << details.username << " and password '" << std::string(details.password.size(), '*') << "'\n";
private:
Database details;
;
struct TSession : std::enable_shared_from_this<TSession>
TSession(boost::asio::io_service& svc, std::shared_ptr<TDatabase> db) :
_svc(svc), _socket(_svc), _db(db)
tcp::socket& Socket() return _socket;
void Start()
boost::asio::async_read(_socket, _sb,
boost::bind(&TSession::HandleReceived, shared_from_this(),
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
void HandleReceived(boost::system::error_code ec, size_t bytes_transferred)
if (!ec || boost::asio::error::eof == ec)
std::cout << "Received from " << _socket.remote_endpoint() << ": '" << &_sb << "'\n";
else
std::cout << "Error reading from peer: " << ec.message() << "\n";
private:
boost::asio::io_service& _svc;
tcp::socket _socket;
std::shared_ptr<TDatabase> _db;
boost::asio::streambuf _sb;
;
struct TServer
tcp::acceptor acceptor;
std::shared_ptr<TDatabase> database;
TServer(boost::asio::io_service &io_service, std::shared_ptr<TDatabase> database, const boost::asio::ip::tcp::endpoint &endpoint)
: acceptor(io_service, endpoint), database(database)
Accept_Connection();
void Accept_Connection()
auto Connection = std::make_shared<TSession>(acceptor.get_io_service(), database);
acceptor.async_accept(Connection->Socket(),
boost::bind(&TServer::Handle_Connection, this, Connection, boost::asio::placeholders::error));
void Handle_Connection(std::shared_ptr<TSession> Connection, const boost::system::error_code &error)
if (!error)
Connection->Start();
Accept_Connection();
else
std::cout << "Error: " << error.message() << "\n";
;
//#include "TServer.h"
//#include "TDatabase.h"
//#include "Includes.h"
//#include "Structures.h"
#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/xml_parser.hpp>
int main()
try
std::cout << "========================================" << std::endl
<< "= Game Server v1.0 by Gravity1 =" << std::endl
<< "========================================" << std::endl;
boost::asio::io_service io_service;
Database database;
std::list<std::list<TServer> > servers;
srand(time(0));
boost::property_tree::ptree pt;
boost::property_tree::read_xml("game_server_config.xml", pt);
if (auto dbconfig = pt.get_child_optional("CONFIG.DATABASE"))
database.host = dbconfig->get<std::string>("HOST");
database.username = dbconfig->get<std::string>("USER");
database.password = dbconfig->get<std::string>("PASS");
database.schema = dbconfig->get<std::string>("SCHEMA");
std::shared_ptr<TDatabase> Database_ptr = std::make_shared<TDatabase>(database);
Database_ptr->Connect();
for (auto& serverconfig: pt.get_child("CONFIG.GAME_SERVER"))
if ("SERVER" != serverconfig.first)
continue;
servers.emplace_back();
auto& current_server = servers.back();
for (auto& channelconfig: serverconfig.second)
if ("CHANNEL" != channelconfig.first)
continue;
Canal CanalTemp;
CanalTemp.ip = channelconfig.second.get<std::string>("IP");
CanalTemp.port = channelconfig.second.get<short>("PORT");
tcp::endpoint endpoint(boost::asio::ip::address::from_string(CanalTemp.ip), CanalTemp.port);
current_server.emplace_back(io_service, Database_ptr, endpoint);
io_service.run();
catch (std::exception &e)
std::cerr << e.what() << std::endl;
std::cin.get();
打印
========================================
= Game Server v1.0 by Gravity1 =
========================================
Connecting to fake database localhost/my_game with user root and password '******'
Received from 127.0.0.1:55712: 'hello channel
'
Received from 127.0.0.1:37987: 'hello OTHER channel
'
Received from 127.0.0.1:55714: 'hello channel
'
Received from 127.0.0.1:37989: 'hello OTHER channel
'
Received from 127.0.0.1:55716: 'hello channel
'
等等
【讨论】:
谢谢。它现在完美运行!再次感谢您的帮助;)。以上是关于Boost::Asio - 抛出 get_io_service 异常的主要内容,如果未能解决你的问题,请参考以下文章
shared_from_this使用boost :: asio抛出bad_weak_ptr