compartido de esto causando mal ptr débil
Estoy tratando de mantener una lista de clientes conectados en asio. He adaptado el ejemplo del servidor de chat de los documentos (http://www.boost.org/doc/libs/1_57_0/doc/html/boost_asio/example/cpp03/chat/chat_server.cppy aquí está la parte importante de lo que terminé con:
#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;
}
En la llamada a shared_from_this, mi servidor se bloquea con el mensaje "Exception: tr1::bad_weak_ptr."He hecho algunas búsquedas y parece shared_from_this()
es bastante particular, pero parece que no puedo encontrar exactamente lo que necesito cambiar.
2 answers
El análisis esencial de John Zwinck es acertado:
El error es que estás usando shared_from_this() en un objeto que no tiene shared_ptr apuntando a él. Esto viola una precondición de shared_from_this (), a saber, que al menos un shared_ptr ya debe haber sido creado (y todavía existir) apuntando a esto.
Sin embargo, su consejo parece completamente fuera del punto y peligroso en el código Asio.
Debe resolver esto - de hecho-no manejando raw punteros a tcp_connection
en primer lugar, pero siempre usando shared_ptr
en su lugar.
boost::bind
tiene la característica impresionante que se une a shared_ptr<>
muy bien por lo que automágicamente mantiene vivo el objeto apuntado mientras alguna operación asíncrona esté operando en él.
Esto - en su código de ejemplo-significa que no necesita el vector clients
, yendo en la dirección opuesta a la respuesta de John:
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();
}
}
He incluido una muestra que hace que tcp_connection
haga algún trabajo trivial (se bucea escribiendo 'hola mundo' a el cliente cada segundo, hasta que el cliente interrumpa la conexión. Cuando lo hace, puede ver el destructor de la operación tcp_connection
que se está ejecutando:
#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";
}
}
Salida típica:
sehe@desktop:/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
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-12-30 15:57:08
El error es que estás usando shared_from_this()
en un objeto que no tiene shared_ptr
apuntando a él. Esto viola una precondición de shared_from_this()
, a saber, que al menos uno shared_ptr
debe ya haber sido creado (y todavía existir) apuntando a this
.
La causa raíz de sus problemas parece ser el hecho de que está almacenando el resultado de new
en un puntero raw inicialmente. Debe almacenar el resultado de new
en un puntero inteligente (siempre, básicamente). Tal vez usted puede almacenar el puntero inteligente en su clients
lista inmediatamente, entonces.
Otro enfoque que mencioné en los comentarios es dejar de usar shared_from_this()
por completo. No lo necesitas. En cuanto a este bit de código que mencionaste:
if ((boost::asio::error::eof == ec) || (boost::asio::error::connection_reset == ec))
{
clients.erase(shared_from_this());
}
Puedes reemplazarlo por:
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);
}
Es decir, crear un "tonto" puntero inteligente que nunca deallocate (https://stackoverflow.com/a/5233034/4323) pero que le dará lo que usted necesita para eliminar de la lista de clientes. Hay otras maneras de hacerlo también, tales como al buscar el std::set
usando una función de comparación que toma un shared_ptr
y un puntero raw y sabe comparar las direcciones a las que apuntan. No importa mucho el camino que elija, pero escapa de la situación shared_from_this()
por completo.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:10:38