Ответ 1
Документация Boost.Asio гласит:
Буферный объект представляет собой смежную область памяти в виде 2-кортежей, состоящую из указателя и размера в байтах. Кортеж формы {void *, size_t} указывает изменяемую (изменяемую) область памяти.
Это означает, что для того, чтобы вызов async_read
записывал данные в буфер, он должен быть (в базовом буфере) непрерывным блоком памяти. Кроме того, буферный объект должен иметь возможность записывать в этот блок памяти.
std::string
не позволяет произвольные записи в свой буфер, поэтому async_read
не может записывать фрагменты памяти в строковый буфер (обратите внимание, что std::string
предоставляет доступ только для чтения вызывающего пользователя к базовому буферу через data()
, который гарантирует, что возвращаемый указатель будет действителен до следующего вызова функции не-const-члена. По этой причине Asio может легко создать обертку const_buffer
std::string
, и вы можете использовать ее с помощью async_write
).
Документация Asio содержит примерный код для простой программы чата (см. http://www.boost.org/doc/libs/1_43_0/doc/html/boost_asio/examples.html#boost_asio.examples.chat), который имеет хороший метод преодоления этой проблемы, В принципе, вам нужно, чтобы отправляемый TCP отправлялся вместе с размером сообщения сначала в "заголовке" сортировки, и ваш обработчик чтения должен интерпретировать заголовок, чтобы выделить буфер фиксированного размера, подходящий для чтения фактических данных.
Что касается использования shared_from_this()
в async_read
и async_write
, причина в том, что он гарантирует, что метод, обернутый boost::bind
, всегда будет ссылаться на живой объект. Рассмотрим следующую ситуацию:
- Ваш метод
handle_accept
вызываетasync_read
и отправляет обработчик "в реактор" - в основном вы попросилиio_service
вызыватьConnection::handle_user_read
, когда он заканчивает чтение данных из сокета.io_service
хранит этот функтор и продолжает цикл, ожидая завершения операции асинхронного чтения. - После вашего вызова
async_read
объектConnection
по какой-либо причине освобождается (завершение программы, условие ошибки и т.д.). - Предположим, что
io_service
теперь определяет, что асинхронное чтение завершено, после того как объектConnection
был освобожден, но до того, какio_service
будет уничтожен (это может произойти, например, еслиio_service::run
работает в отдельная резьба, как это типично). Теперьio_service
пытается вызвать обработчик и имеет недопустимую ссылку на объектConnection
.
Решение состоит в том, чтобы выделить Connection
через shared_ptr
и использовать shared_from_this()
вместо this
при отправке обработчика "в реактор" - это позволяет io_service
хранить общую ссылку на объект, и shared_ptr
гарантирует, что он не будет освобожден до истечения последней ссылки.
Итак, ваш код должен выглядеть примерно так:
class Connection : public boost::enable_shared_from_this<Connection>
{
public:
Connection(tcp::acceptor &acceptor) :
acceptor_(acceptor),
socket_(acceptor.get_io_service(), tcp::v4())
{ }
void start()
{
acceptor_.get_io_service().post(
boost::bind(&Connection::start_accept, shared_from_this()));
}
private:
void start_accept()
{
acceptor_.async_accept(socket_,
boost::bind(&Connection::handle_accept, shared_from_this(),
placeholders::error));
}
void handle_accept(const boost::system::error_code& err)
{
if (err)
{
disconnect();
}
else
{
async_read(socket_, boost::asio::buffer(user_),
boost::bind(&Connection::handle_user_read, shared_from_this(),
placeholders::error, placeholders::bytes_transferred));
}
}
//...
};
Обратите внимание, что теперь вы должны убедиться, что каждый Connection
объект распределяется через shared_ptr
, например:
boost::shared_ptr<Connection> new_conn(new Connection(...));
Надеюсь, это поможет!