The problem is quite simple.
You're emplacing TServer
s to the back of a vector. When you do, it will (may) reallocate, invalidating the references that are held in other parts of your program. See Iterator invalidation rules
In your case, such a reference is immediately held, because Accept_Connection()
is called from within the constructor and it binds to the this
pointer. Remember, the this
pointer points to the address of a TServer
element inside the vector.
OOPS. When your completion handler fires, the element is/may have been reallocated. So the pointer is simply dangling and you have Undefined Behaviour.
You can fix it in different ways:
replace the vectors with a container that guarantees stability of reference on insertion. For example, you can just use a list<>
instead:
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);
}
}
Alternatively, you can postpone the initial bind until after all the channels were added to all servers:
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();
}
And do that explicitly before io_service::run()
:
for(auto& server: servers)
for(auto& channel: server)
channel.Accept_Connection();
io_service.run();
Note: In fact, in idiomatic Asio code, running asynchronous operations directly from within a constructor is frequently not possible. Look e.g. at the TSession
type; it couldn't bind a completion handler to a member function because shared_from_this()
is not allowed from within the constructor ("Note that prior to calling shared_from_this
on an object t
, there must be a shared_ptr
that owns t
.").
Both work. I opt for the first here:
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://stackoverflow.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();
}
I used a configuration of
[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
Which, when running clients on both channels (port 6767 and 6868) prints an "endless" repeat of:
========================================
= 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
'