2015-09-04 238 views
1

任何人都可以向我解释为什么当我想调用get_io_service()时,我得到以下异常?Boost :: Asio - 引发get_io_service异常

enter image description here

我看到,在启动受体初始化,但是当客户要连接和服务器要打开新的连接,然后受体具有一定的随机数。我不知道为什么会发生这种情况。

我的代码:

的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(); 
} 
} 
+0

也许你忘了打电话'acceptor.listen()'? – PSIAlt

+0

https://www.livecoding.tv/sehe/([experiment](http://chat.stackoverflow.com/transcript/10?m=24182469#24182469)) – sehe

回答

0

这个问题很简单。

你正在将TServer s放到一个向量的后面。当你这样做时,它会(可能)重新分配,使在程序的其他部分保存的引用无效。见Iterator invalidation rules

在你的情况,这样的提法立即举行,因为Accept_Connection()从构造函数中调用,它绑定 S到this指针。请记住,this指针指向向量内的TServer元素的地址。

OOPS。当你的完成处理程序触发时,该元素可能已被重新分配。所以指针简直就是晃来晃去,你有Undefined Behaviour

可以以不同的方式解决这个问题:

  1. 与保证对插入基准稳定性的容器替换载体。例如,你可以只使用一个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); 
        } 
    } 
    
  2. 或者,你可以推迟初始绑定到所有通道被添加到所有服务器:

    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类型;它无法将完成处理程序绑定到成员函数,因为shared_from_this()不允许在构造函数("Note that prior to calling shared_from_this on an object t, there must be a shared_ptr that owns t.")内的

两者都工作。我选择了先在这里:

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(); 
} 

我用的

[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 
' 
0

完全无关,但您的配置格式确实如此要求分层格式,如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> 

,你可以用下面的代码片段阅读:

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://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/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 
' 

+0

谢谢。它现在完美运行!再次感谢您的帮助;)。 – JMII89