boost :: asio + std :: future – Нарушение прав доступа после закрытия сокета

Я пишу простой клиент tcp для отправки и получения отдельных строк текста. Асинхронные операции обрабатываются std :: future, чтобы облегчить блокировку запросов с помощью тайм-аутов. К сожалению, мое тестовое приложение вылетает с нарушением прав доступа при уничтожении объекта сервера. Вот мой код:

TCPClient.hpp

#ifndef __TCPCLIENT_H__ #define __TCPCLIENT_H__ #include  #include  #include  #include  #include  #include  #include  #include  #include  using namespace boost::asio; class TCPClient { public: TCPClient(); ~TCPClient(); void connect(const std::string& address, const std::string& port); void disconnect(); std::string sendMessage(const std::string& msg); private: boost::asio::io_service ioservice; boost::asio::io_service::work work; std::thread t; std::unique_ptr socket; }; inline TCPClient::TCPClient() : ioservice(), work(ioservice) { t = std::thread([&]() { try { ioservice.run(); } catch (const boost::system::system_error& e) { std::cerr << e.what() << std::endl; } }); } inline TCPClient::~TCPClient() { disconnect(); ioservice.stop(); if (t.joinable()) t.join(); } inline void TCPClient::connect(const std::string& address, const std::string& port) { socket.reset(new ip::tcp::socket(ioservice)); ip::tcp::resolver::query query(address, port); std::future conn_result = async_connect(*socket, ip::tcp::resolver(ioservice).resolve(query), use_future); if (conn_result.wait_for(std::chrono::seconds(6)) != std::future_status::timeout) { conn_result.get(); // throws boost::system::system_error if the operation fails } else { //socket->close(); // throw timeout_error("Timeout"); throw std::exception("timeout"); } } inline void TCPClient::disconnect() { if (socket) { try { socket->shutdown(ip::tcp::socket::shutdown_both); std::cout << "socket points to " << std::addressof(*socket) <close(); } catch (const boost::system::system_error& e) { // ignore std::cerr << "ignored error " << e.what() << std::endl; } } } inline std::string TCPClient::sendMessage(const std::string& msg) { auto time_over = std::chrono::system_clock::now() + std::chrono::seconds(4); /* // Doesn't affect the error std::future write_fut = boost::asio::async_write(*socket, boost::asio::buffer(msg), boost::asio::use_future); try { write_fut.get(); } catch (const boost::system::system_error& e) { std::cerr << e.what() << std::endl; } */ boost::asio::streambuf response; std::future read_fut = boost::asio::async_read_until(*socket, response, '\n', boost::asio::use_future); if (read_fut.wait_until(time_over) != std::future_status::timeout) { std::cout << "read " << read_fut.get() << " bytes" << std::endl; return std::string(std::istreambuf_iterator(&response), std::istreambuf_iterator()); } else { std::cout << "socket points to " << std::addressof(*socket) << std::endl; throw std::exception("timeout"); } } #endif 

main.cpp

 #include  #include "TCPClient.hpp" int main(int argc, char* argv[]) { TCPClient client; try { client.connect("localhost", "27015"); std::cout << "Response: " << client.sendMessage("Hello!") << std::endl; } catch (const boost::system::system_error& e) { std::cerr << e.what() << std::endl; } catch (const std::exception& e) { std::cerr << e.what() << std::endl; } system("pause"); return 0; } 

Вывод – это «тайм-аут», как ожидалось (тестовый сервер не отправляет данные по назначению), но ioservice.run() немедленно сбой (нарушение прав доступа) после закрытия сокета в TCPClient::disconnect() . Я здесь что-то не так понимаю?

Компилятор MSVC 12.0.31101.00 Обновление 4 (Visual Studio 2013)

recvmsg принимает в буфер ( streambuf ), который был освобожден после выброса исключения в TCPClient::sendMessage (строка 105, конец области).

Вы забыли отменить асинхронную операцию ( async_read_until ), запущенную в строке 97. Исправить:

 else { socket->cancel(); // ADDED std::cout << "socket points to " << std::addressof(*socket) << std::endl; throw std::runtime_error("timeout"); } 

Или даже, просто

  socket.reset(); // ADDED 

То же самое касается других путей тайм-аута.

Другой ответ касается того, что пошло не так.

На более высоком уровне, тем не менее, вы используете фьючерсы, чтобы немедленно дождаться их возвращения.

Мне показалось, что на самом деле это не асинхронность, и вы должны уметь:

  • без нарезания резьбы и соединения
  • без .stop()
  • без work и work.reset()
  • без явного конструктора или деструктора
  • без unique_ptr и управления жизненным циклом, которое пришло с ним
  • без future<> , а также .get() помощью .get() и future_status которые идут с ним

В общем, вы можете сделать намного проще, например, используя простую вспомогательную функцию, например:

 class TCPClient { public: void disconnect(); void connect(const std::string& address, const std::string& port); std::string sendMessage(const std::string& msg); private: using error_code = boost::system::error_code; template void await_operation(AllowTime const& deadline_or_duration) { using namespace boost::asio; ioservice.reset(); { high_resolution_timer tm(ioservice, deadline_or_duration); tm.async_wait([this](error_code ec) { if (ec != error::operation_aborted) socket.cancel(); }); ioservice.run_one(); } ioservice.run(); } boost::asio::io_service ioservice { }; boost::asio::ip::tcp::socket socket { ioservice }; }; 

Например, connect(...) :

 socket.reset(new ip::tcp::socket(ioservice)); ip::tcp::resolver::query query(address, port); std::future conn_result = async_connect(*socket, ip::tcp::resolver(ioservice).resolve(query), use_future); if (conn_result.wait_for(std::chrono::seconds(6)) != std::future_status::timeout) { conn_result.get(); // throws boost::system::system_error if the operation fails } else { socket->cancel(); // throw timeout_error("Timeout"); throw std::runtime_error("timeout"); } 

Теперь это становится:

 async_connect(socket, ip::tcp::resolver(ioservice).resolve({address, port}), [&](error_code ec, ip::tcp::resolver::iterator it) { if (ec) throw std::runtime_error(ec.message()); }); await_operation(std::chrono::seconds(6)); 

Как мудрый, sendMessage становится:

 streambuf response; async_read_until(socket, response, '\n', [&](error_code ec, size_t bytes_read) { if (ec) throw std::runtime_error(ec.message()); std::cout << "read " << bytes_read << " bytes" << std::endl; }); await_operation(std::chrono::system_clock::now() + std::chrono::seconds(4)); return {std::istreambuf_iterator(&response), {}}; 

Обратите внимание, что они значительно проще. Обратите также внимание, что теперь исправляются правильные сообщения об исключениях в зависимости от причины сбоев.

Полная демонстрация

Live On Coliru

 #ifndef __TCPCLIENT_H__ #define __TCPCLIENT_H__ #include  #include  #include  class TCPClient { public: void disconnect(); void connect(const std::string& address, const std::string& port); std::string sendMessage(const std::string& msg); private: using error_code = boost::system::error_code; template void await_operation(AllowTime const& deadline_or_duration) { using namespace boost::asio; ioservice.reset(); { high_resolution_timer tm(ioservice, deadline_or_duration); tm.async_wait([this](error_code ec) { if (ec != error::operation_aborted) socket.cancel(); }); ioservice.run_one(); } ioservice.run(); } boost::asio::io_service ioservice { }; boost::asio::ip::tcp::socket socket { ioservice }; }; inline void TCPClient::connect(const std::string& address, const std::string& port) { using namespace boost::asio; async_connect(socket, ip::tcp::resolver(ioservice).resolve({address, port}), [&](error_code ec, ip::tcp::resolver::iterator it) { if (ec) throw std::runtime_error(ec.message()); }); await_operation(std::chrono::seconds(6)); } inline void TCPClient::disconnect() { using namespace boost::asio; if (socket.is_open()) { try { socket.shutdown(ip::tcp::socket::shutdown_both); socket.close(); } catch (const boost::system::system_error& e) { // ignore std::cerr << "ignored error " << e.what() << std::endl; } } } inline std::string TCPClient::sendMessage(const std::string& msg) { using namespace boost::asio; streambuf response; async_read_until(socket, response, '\n', [&](error_code ec, size_t bytes_read) { if (ec) throw std::runtime_error(ec.message()); std::cout << "read " << bytes_read << " bytes" << std::endl; }); await_operation(std::chrono::system_clock::now() + std::chrono::seconds(4)); return {std::istreambuf_iterator(&response), {}}; } #endif #include  //#include "TCPClient.hpp" int main(/*int argc, char* argv[]*/) { TCPClient client; try { client.connect("127.0.0.1", "27015"); std::cout << "Response: " << client.sendMessage("Hello!") << std::endl; } catch (const boost::system::system_error& e) { std::cerr << e.what() << std::endl; } catch (const std::exception& e) { std::cerr << e.what() << std::endl; } } 

БОНУС

Если вы хотите еще больше удобства, у вас есть обобщенный обработчик обратного вызова, который просто вызывает исключение:

 struct raise { template  void operator()(error_code ec, A...) const { if (ec) throw std::runtime_error(ec.message()); } }; 

Теперь тела становятся еще проще в отсутствие lambda:

 inline void TCPClient::connect(const std::string& address, const std::string& port) { async_connect(socket, ip::tcp::resolver(ioservice).resolve({address, port}), raise()); await_operation(std::chrono::seconds(6)); } inline std::string TCPClient::sendMessage(const std::string& msg) { streambuf response; async_read_until(socket, response, '\n', raise()); await_operation(std::chrono::system_clock::now() + std::chrono::seconds(4)); return {std::istreambuf_iterator(&response), {}}; } 

См. Адаптированную демоверсию: Live On Coliru

  • Пересылка всех конструкторов в C ++ 0x
  • Как включить C ++ 11 в gcc?
  • Что такое «выражение SFINAE»?
  • Является ли диапазон, основанный на цикле полезным для производительности?
  • Как бы вы реализовали свой собственный механизм чтения / записи в C ++ 11?
  • Почему чтение записей структурных полей из std :: istream завершается неудачно, и как я могу это исправить?
  • Может ли std :: vector emplace_back копировать конструкцию из элемента самого вектора?
  • Рекурсивные lambda-функции в C ++ 11
  • Переместить захват в lambda
  • C ++ 0x прерывание streamа
  • Почему нет специализации std :: shared_ptr ?
  • Interesting Posts

    Есть ли ярлык в Eclipse для повторного запуска последней запущенной программы?

    django – упорядочение запроса по вычисленному полю

    В представлении на основе Razor не отображаются ссылочные сборки

    Блок UIButton эквивалентен addTarget: действие: forControlEvents: метод?

    Заменить встроенные команды с помощью пользовательских команд для CMD.exe

    Как заставить «вы действительно хотите отключить?» Диалог в Windows 7?

    Почему считать плохую практику опустить фигурные скобки?

    Как сортировать вкладки в отдельные окна firefox на основе имени домена?

    AngularJS $ http, аутентификация CORS и http

    Не удается установить Windows Server 2003

    CheckboxList в MVC3 Просмотр и получение отмеченных элементов, переданных controllerу

    Удаление дублированных строк

    Как создать временный файл с определенным расширением с .NET?

    Выбор текста в фокусе с использованием jQuery, не работающего в Safari и Chrome

    R – gsub заменяет обратную косую черту

    Давайте будем гением компьютера.