2014-12-30 1 views
18

연결된 클라이언트의 목록을 asio에 보관하려고합니다. 나는 워드 프로세서에서 채팅 서버 예 (http://www.boost.org/doc/libs/1_57_0/doc/html/boost_asio/example/cpp03/chat/chat_server.cpp)을 적용하고 여기에 내가 함께 결국 일의 중요한 부분 : 메시지 "예외와shared_from_this bad_weak_ptr을 일으키는 경우

shared_from_this에 대한 호출에서
#include <iostream> 
#include <boost/bind.hpp> 
#include <boost/shared_ptr.hpp> 
#include <boost/enable_shared_from_this.hpp> 
#include <boost/asio.hpp> 
#include <set> 

using boost::asio::ip::tcp; 

class tcp_connection; 

std::set<boost::shared_ptr<tcp_connection>> clients; 

void add_client(boost::shared_ptr<tcp_connection> client) 
{ 
    clients.insert(client); 
} 

class tcp_connection : public boost::enable_shared_from_this<tcp_connection> 
{ 
public: 
    tcp_connection(boost::asio::io_service& io_service) : socket_(io_service) 
    { 
    } 

    tcp::socket socket_; 

    void start() 
    { 
     add_client(shared_from_this()); 
    } 

    tcp::socket& socket() 
    { 
     return socket_; 
    } 
}; 

class tcp_server 
{ 
public: 
    tcp_server(boost::asio::io_service& io_service) 
     : io_service_(io_service), 
     acceptor_(io_service, tcp::endpoint(tcp::v4(), 6767)) 
    { 
     tcp_connection* new_connection = new tcp_connection(io_service_); 
     acceptor_.async_accept(new_connection->socket(), 
          boost::bind(&tcp_server::start_accept, this, new_connection, 
             boost::asio::placeholders::error)); 
    } 

private: 
    void start_accept(tcp_connection* new_connection, 
         const boost::system::error_code& error) 
    { 
     if (!error) 
     { 
      new_connection->start(); 
      new_connection = new tcp_connection(io_service_); 
      acceptor_.async_accept(new_connection->socket(), 
            boost::bind(&tcp_server::start_accept, this, new_connection, 
               boost::asio::placeholders::error)); 
     } 
    } 

    boost::asio::io_service& io_service_; 
    tcp::acceptor acceptor_; 
}; 

int main() 
{ 
    try 
    { 
     boost::asio::io_service io_service; 
     tcp_server server(io_service); 
     io_service.run(); 
    } 
    catch (std::exception& e) 
    { 
     std::cerr << "Exception: " << e.what() << "\n"; 
    } 

    return 0; 
} 

, 내 서버 충돌 : TR1 :: bad_weak_ptr. " 나는 약간의 검색을했는데 shared_from_this()이 꽤 특별한 것처럼 보입니다. 그러나 정확히 바꿀 필요가있는 것을 찾을 수 없습니다.

+1

는 왜 새로운'의 결과를 저장 않는 것이 수행하면 tcp_connection 작동되는 실행의 소멸자를 볼 수 있습니다 '원시 포인터에서 나중에'shared_from_this()'를 사용 하는가? 이 문제를 완전히 없애기 위해 설계를 간소화 할 수있는 것 같습니다. –

+2

'enable_shared_from_this '에 대한 boost 문서는'당신이 가지고 있지 않은 것 같은 t를 소유 한 적어도 하나의 shared_ptr 인스턴스 p가 있어야합니다.'라고 말합니다. –

+0

@JonathanPotter 나는 그것을 읽었지만 나는 그것을 이해하지 못한다. – chrisvj

답변

21

존 Zwinck의 필수 분석에 자리입니다 :

The bug is that you're using shared_from_this() on an object which has no shared_ptr pointing to it. This violates a precondition of shared_from_this(), namely that at least one shared_ptr must already have been created (and still exist) pointing to this.

그러나, 그의 조언하는 Asio 코드의 포인트와 위험한 옆에 완전하게 보인다.

먼저 실제로 tcp_connection에 원시 포인터를 처리하지 말고 항상 shared_ptr을 사용하여이 문제를 해결해야합니다.

boost::bind에는 shared_ptr<>에 바인딩하는 멋진 기능이 있으므로 자동으로 일부 비동기 작업이 작동하는 한 자동으로 개체를 계속 유지합니다. (일부 사소한 일을 내가 포함했다

void start_accept() 
{ 
    tcp_connection::sptr new_connection = boost::make_shared<tcp_connection>(io_service_); 
    acceptor_.async_accept(new_connection->socket(), 
      boost::bind(
       &tcp_server::handle_accept, 
       this, new_connection, asio::placeholders::error 
      ) 
     ); 
} 

void handle_accept(tcp_connection::sptr client, boost::system::error_code const& error) 
{ 
    if (!error) 
    { 
     client->start(); 
     start_accept(); 
    } 
} 

tcp_connection을 만드는 샘플 :

- - 샘플 코드는 요한의 대답에서 반대 방향으로 가고는 clients 벡터를 필요가 없습니다 의미 클라이언트가 연결을 끊을 때까지 'hello world'를 매 초마다 반복합니다.

Live On Coliru

#include <iostream> 
#include <boost/bind.hpp> 
#include <boost/make_shared.hpp> 
#include <boost/enable_shared_from_this.hpp> 
#include <boost/asio.hpp> 
#include <boost/thread.hpp> 

namespace asio = boost::asio; 
using asio::ip::tcp; 

class tcp_connection : public boost::enable_shared_from_this<tcp_connection> 
{ 
public: 
    typedef boost::shared_ptr<tcp_connection> sptr; 

    tcp_connection(asio::io_service& io_service) : socket_(io_service), timer_(io_service) 
    { 
    } 

    void start() 
    { 
     std::cout << "Created tcp_connection session\n"; 

     // post some work bound to this object; if you don't, the client gets 
     // 'garbage collected' as the ref count goes to zero 
     do_hello(); 
    } 

    ~tcp_connection() { 
     std::cout << "Destroyed tcp_connection\n"; 
    } 

    tcp::socket& socket() 
    { 
     return socket_; 
    } 

    private: 
    tcp::socket socket_; 
    asio::deadline_timer timer_; 

    void do_hello(boost::system::error_code const& ec = {}) { 
     if (!ec) { 
      asio::async_write(socket_, asio::buffer("Hello world\n"), 
        boost::bind(&tcp_connection::handle_written, shared_from_this(), asio::placeholders::error, asio::placeholders::bytes_transferred) 
       ); 
     } 
    } 

    void handle_written(boost::system::error_code const& ec, size_t /*bytes_transferred*/) { 
     if (!ec) { 
      timer_.expires_from_now(boost::posix_time::seconds(1)); 
      timer_.async_wait(boost::bind(&tcp_connection::do_hello, shared_from_this(), asio::placeholders::error)); 
     } 
    } 
}; 

class tcp_server 
{ 
public: 
    tcp_server(asio::io_service& io_service) 
     : io_service_(io_service), 
      acceptor_(io_service, tcp::endpoint(tcp::v4(), 6767)) 
    { 
     start_accept(); 
    } 

private: 
    void start_accept() 
    { 
     tcp_connection::sptr new_connection = boost::make_shared<tcp_connection>(io_service_); 
     acceptor_.async_accept(new_connection->socket(), 
       boost::bind(
        &tcp_server::handle_accept, 
        this, new_connection, asio::placeholders::error 
       ) 
      ); 
    } 

    void handle_accept(tcp_connection::sptr client, boost::system::error_code const& error) 
    { 
     if (!error) 
     { 
      client->start(); 
      start_accept(); 
     } 
    } 

    asio::io_service& io_service_; 
    tcp::acceptor acceptor_; 
}; 

int main() 
{ 
    try 
    { 
     asio::io_service io_service; 
     tcp_server server(io_service); 

     boost::thread(boost::bind(&asio::io_service::run, &io_service)).detach(); 

     boost::this_thread::sleep_for(boost::chrono::seconds(4)); 
     io_service.stop(); 
    } 
    catch (std::exception& e) 
    { 
     std::cerr << "Exception: " << e.what() << "\n"; 
    } 
} 

일반적인 출력 :

[email protected]:/tmp$ time (./test& (for a in {1..4}; do nc 127.0.0.1 6767& done | nl&); sleep 2; killall nc; wait) 
Created tcp_connection session 
Created tcp_connection session 
    1 Hello world 
Created tcp_connection session 
    2 Hello world 
Created tcp_connection session 
    3 Hello world 
    4 Hello world 
    5 Hello world 
    6 Hello world 
    7 Hello world 
    8 Hello world 
    9 Hello world 
    10 Hello world 
    11 Hello world 
    12 Hello world 
    13 
Destroyed tcp_connection 
Destroyed tcp_connection 
Destroyed tcp_connection 
Destroyed tcp_connection 
Destroyed tcp_connection 

real 0m4.003s 
user 0m0.000s 
sys 0m0.015s 
6

shared_ptr을 가리키는 개체에 shared_from_this()을 사용하고있는 것이 문제입니다. 이는 shared_from_this()의 전제 조건을 위반합니다. 즉, 하나 이상의 shared_ptr이 이미이어야하고 this을 가리켜 야합니다 (아직 존재 함).

문제의 근본 원인은 처음에 new의 결과를 원시 포인터에 저장하고있는 것 같습니다. new의 결과를 스마트 포인터 (항상, 기본적으로)에 저장해야합니다. 아마도 clients 목록에 스마트 포인터를 저장할 수 있습니다.

내가 언급 한 또 다른 접근 방식은 shared_from_this()의 사용을 완전히 중단하는 것입니다. 너는 필요 없어. 코드의이 비트에 관해서는 언급 :

입니다
if ((boost::asio::error::eof == ec) || (boost::asio::error::connection_reset == ec)) 
{ 
    boost::shared_ptr<tcp_connection> victim(this, boost::serialization::null_deleter()); 
    clients.erase(victim); 
} 

, 할당을 해제하지 않을 것 "바보"스마트 포인터 (https://stackoverflow.com/a/5233034/4323)를 생성하지만, 어떤을 줄 것이다 :

if ((boost::asio::error::eof == ec) || (boost::asio::error::connection_reset == ec)) 
{ 
    clients.erase(shared_from_this()); 
} 

다음과 같은 방법으로 교체 할 수 있습니다 당신은 당신이 클라이언트의 목록에서 그것을 삭제할 필요가 있습니다. shared_ptr과 하나의 미가공 포인터를 사용하고 그들이 가리키는 주소를 비교하는 것을 알고있는 비교 함수를 사용하여 std::set을 검색하는 것과 같이 다른 방법도 있습니다. 어떤 방법으로 선택하든 상관 없지만 shared_from_this() 상황을 완전히 피할 수 있습니다.

+1

'shared_from_this' "상황"은 Boost Asio의 세션 관리에 관용적입니다. 비동기 세션의 평생 관리를 쉽게하는 것이 목적입니다. 실제로 '클라이언트'는 여기에 설정하면 비동기 작업이 실패 할 수있는 모든 장소 (일반 예외 안전성을 고려하지 않음)에서 정리를 올바르게 수행하는 것이 사실상 불가능하기 때문에 매우 어렵습니다. – sehe

+4

@sehe : 합리적인 소리, 'this'에 대한 감사합니다. . –

관련 문제