Приведенный ниже фрагмент кода всегда закрывает соединение сразу, когда клиент подключается к серверу, что совершенно не соответствует моим ожиданиям.
#include <iostream>
#include <memory>
#include <system_error>
#include "tcp_server.hpp"
TcpServer::TcpServer(asio::io_context& io_context,
const unsigned int& port):m_acceptor(io_context, tcp::endpoint(tcp::v4(), port))
{
do_accept();
}
void TcpServer::do_accept()
{
m_acceptor.async_accept(make_strand(m_acceptor.get_executor()),
[this](const std::error_code& error, tcp::socket socket)
{
if (!error)
{
std::make_shared<Conversation>(std::move(socket))->do_start();
}
else
{
std::cerr << "Error accepting connection: " << error.message() << std::endl;
}
// Start accepting the next connection
do_accept();
});
}
Conversation::Conversation(tcp::socket socket) : m_socket(std::move(socket))
{
std::cout << "creating conversation:" << this << std::endl;
}
void Conversation::do_read()
{
std::cout << "do read" << std::endl;
auto conversation_ptr{shared_from_this()};
m_socket.async_read_some(asio::buffer(m_data),
[this, conversation_ptr](std::error_code error, size_t length) {
if (!error)
{
do_write(std::string("reply:") + std::string{m_data.data(), length});
do_read();
std::cout << "data read: " << m_data.data() << std::endl;
}
else
{
std::cout << "error in reading data: " << error.message() << std::endl;
}
});
}
void Conversation::do_start()
{
do_read();
}
void Conversation::do_write(std::string str)
{
auto conversation_ptr{shared_from_this()};
m_socket.async_send(asio::buffer(str), [this, conversation_ptr](std::error_code error, size_t length ){
if (!error)
{
std::cout << "data write successfully" << std::endl;
}
else
{
std::cout << "error in writing data: " << error.message() << std::endl;
}
});
}
Conversation::~Conversation()
{
std::cout << "destroying conversation:" << this << std::endl;
}
int main()
{
asio::io_context io_context;
TcpServer server{io_context, 12345};
io_context.run();
}
Поскольку auto conversation_ptr{shared_from_this()};
создает экземпляр std::initializer_list<std::shared_ptr<Conversation>>
, а полный обработчик захватывает conversation_ptr
, я думаю, что экземпляр Conversation
будет сохраняться при вызове полного обработчика, если какое-то сообщение отправляется от клиента. Однако выходные данные сервера всегда примерно такие: ниже.
./tcp_server
creating conversation:0x1cad8d0
do read
destroying conversation:0x1cad8d0
error in reading data: Operation aborted.
^C
Между тем, вот вывод telnet
ниже, который указывает, что сервер, отличный от клиента, закрывает соединение.
telnet 0.0.0.0 12345
Trying 0.0.0.0...
Connected to 0.0.0.0.
Escape character is '^]'.
Connection closed by foreign host.
Более того, это хорошо работает, если auto conversation_ptr{shared_from_this()};
заменить на auto conversation_ptr(shared_from_this());
. Какой сюрприз!
Используемый компилятор — clang++ 3.4.
Ubuntu clang version 3.4-1ubuntu3 (tags/RELEASE_34/final) (based on LLVM 3.4)
Target: x86_64-pc-linux-gnu
Thread model: posix
Используемая библиотека asio — V1.29.0.
#define ASIO_VERSION 102900 // 1.29.0
Платформа — Ubuntu14.04.
Distributor ID: Ubuntu
Description: Ubuntu 14.04.6 LTS
Release: 14.04
Codename: trusty
ПРИМЕЧАНИЕ. Согласно этому посту , определение max_align_t
было добавлено для компиляции фрагмента кода с помощью clang++ 3.4.
Между прочим, на высоком уровне, по моему мнению, такое жонглирование ссылками-спасителями в функторах для поддержания жизни является плохим замыслом. Настройка и удаление беседы имеет побочные эффекты; он заслуживает явной и целенаправленной жизни, а не возникающей.
@Sneftel 1. Поскольку в вышеупомянутом фрагменте кода наблюдается странное поведение, сейчас я не могу воспроизвести его с помощью меньшего кода. 2. Ссылки на спасителей в функторах для поддержания жизни — плохой дизайн. Мы не можем заранее знать, когда соединение закрыто или возникла какая-то ошибка.
@Джон Он просил не меньший код, а более полный код :)
@Fareanor Спасибо за объяснение. Извините за мой плохой английский. Код в SO является полным, поскольку он может воспроизвести проблему, не требуя никакой дополнительной информации или кода. Если я ошибаюсь, пожалуйста, дайте мне знать.
std::initializer_list
содержит ссылку на временный массив. Это продленное время жизни, как и у обычной const
ссылки, т. е. временная ссылка заканчивает свое время жизни, когда исходная initializer_list
заканчивает свое время жизни.
Таким образом, захваченная копия initializer_list
становится висящей после вызова лямбды: shared_ptr
, на который она ссылалась, уже уничтожен. Причина, по которой auto conversation_ptr(shared_from_this());
работает, заключается в том, что вы захватываете shared_ptr
напрямую, а лямбда хранит копию.
Кроме того, auto conversation_ptr{shared_from_this()};
делает std::initializer_list<std::shared_ptr<Conversation>>
только до N3922. После этого он становится std::shared_ptr<Conversation>
. Начиная с Clang 3.6, вы начали получать это предупреждение:
warning: direct list initialization of a variable with a deduced type will change meaning in a future version of Clang; insert an '=' to avoid a change in behavior [-Wfuture-compat]
И наконец был реализован в Clang 3.8, поэтому я подозреваю, что этот код был написан для более нового компилятора.
std::initializer_list содержит ссылку на временный массив? Я не могу найти такого похожего утверждения здесь и там.
@Джон en.cppreference.com/w/cpp/language/… (также en.cppreference.com/w/cpp/utility/initializer_list/… говорит, что это прокси-объект, т. е. просто указатель на что-то еще)
Спасибо за подробное объяснение. Да, там говорится, что каждый элемент резервного массива инициализируется копированием с помощью соответствующего элемента списка инициализаторов, а объект std::initializer_list<E> создается для ссылки на этот массив. Следовательно, std::initializer_list<E>
является ссылкой на массив, отличный от ссылок на каждый элемент массива. Например, auto nums = {1,2,3};
создаём std::initializer_list<int>
с именем nums
, который ссылается на массив выпечки, отличный от элементов. Я прав? Если я ошибаюсь, пожалуйста, дайте мне знать.
И наконец был реализован в Clang 3.8, поэтому я подозреваю, что этот код был написан для более нового компилятора. Извините, я не могу понять, что вы имеете в виду под текстом, выделенным жирным шрифтом.
@Джон, я не совсем понимаю, что ты имеешь в виду (поддерживающий массив - это элементы). Другой способ сказать это с std::inititalizer_list<int> nums = {1, 2, 3};
, это как const int __temp[] = { 1, 2, 3 }; std::inititalizer_list<int> nums /* nums.data() == __temp; nums.size() == 3 */;
@John В новом компиляторе auto variable{ E }
не является initializer_list
, а того же типа, что и E
, поэтому у вас не возникнет этой проблемы
Спасибо Артьеру.
Читателям, которые прочитают этот пост позже, как разные версии компиляторов работают с одним и тем же фрагментом кода, поможет полностью понять это.
Более полный минимально воспроизводимый пример был бы хорошим улучшением этого поста. Вы просите нас принять на веру множество предположений об определении
Conversation
.