Двусторонняя связь Asio с постоянным сокетом

#c #boost-asio

#c #boost-asio

Вопрос:

У меня есть это требование, когда мое приложение должно подключаться к другому приложению через сокеты и должно поддерживать постоянное соединение в течение длительного времени. Мое приложение будет TCP-клиентом, а другое — TCP-сервером. Мое приложение будет отправлять команды, и сервер ответит соответствующим образом.

Проблема, с которой я сейчас сталкиваюсь, заключается в том, как прочитать все данные с сервера в виде строки и вернуть для приложения, которое выдаст следующую команду. Чтение синхронно (с asio::read ) выглядело хорошим вариантом до тех пор, пока я не заметил, что сокет зависает, пока я не завершу работу сервера. Просматривая документацию, я обнаружил, что библиотека работает правильно.

его функция используется для чтения определенного количества байтов данных из потока. Вызов будет блокироваться до тех пор, пока не будет выполнено одно из следующих условий: 1. Предоставленные буферы заполнены. То есть передаваемые байты равны сумме размеров буфера. 2. Произошла ошибка.

Проблема в том, что я не знаю правильного размера буфера, поскольку ответ от сервера меняется. Поэтому, если я помещаю слишком маленький буфер, он возвращает нормально, но некоторые данные отсутствуют. Если я установлю слишком большой, он будет зависать вечно, пока сервер не завершит работу.

Итак, я подумал, что буду выполнять асинхронное чтение. Это работает только один раз, и я не знаю, как заставить его извлекать данные до тех пор, пока не будут прочитаны все данные.

вот соответствующий асинхронный код

 #define ASIO_STANDALONE 1

#include <iostream>
#include <asio.hpp>

int main()
{
    asio::io_context context;
    size_t reply_length;
    size_t length = 1024;
    std::vector<char> buffer;

    //create socket
    asio::ip::tcp::socket socket(context);
    socket.connect(asio::ip::tcp::endpoint(asio::ip::address::from_string("127.0.0.1"), 8088));

    std::string dataOut = "list --files"; //some command to write
    std::error_code error;

    asio::write(socket, asio::buffer(dataOut), error);
    if (!error)
    {
        std::cout << "Receiving...!" << std::endl;
        buffer.resize(length);
        asio::async_read(socket, asio::buffer(buffer), [amp;buffer, amp;context](const asio::error_code amp;ec, std::size_t bytes_transferred) {
            std::copy(buffer.begin(), buffer.end(), std::ostream_iterator<char>(std::cout, ""));
            std::cout << "nRead total of:" << bytes_transferred << "n";
            context.run();
        });
    }
    else
    {
        std::cout << "send failed: " << error.message() << std::endl;
    }

    context.run();
}
  

Поиск не сильно помог решить мою проблему.

Итак, мой вопрос в том, как я могу прочитать все данные в постоянном сокете с помощью asio? Я не использую boost.

Ответ №1:

Вам нужно выполнять циклические async_read вызовы. Если вы не хотите, чтобы ваш клиент зависал при операции чтения, вы можете определить наименьший возможный буфер, т.е. 1 байт.

Определите функцию, которая принимает сокет, буфер и два дополнительных параметра в соответствии с сигнатурой обработчика async_read, и эта функция вызывает себя с async_read , чтобы выполнить цикл async_read вызовов — она считывает, пока не возникнет какая-либо ошибка:

 void onRead (
    asio::ip::tcp::socketamp;    socket,
    std::array<char,1>amp;       buf,
    const system::error_codeamp; ec, 
    std::size_t               bytes)
{
    if (ec)
    {
        if (ec == asio::error::eof amp;amp; bytes == 1)
            std::cout << buf[0];
        return;
    }

    std::cout << buf[0];   
    asio::async_read(socket,asio::buffer(buf), 
        std::bind(onRead, std::ref(socket), std::ref(buf),
          std::placeholders::_1, // error code
          std::placeholders::_2)); // transferred bytes
}
  

и изменения в main :

 std::array<char,1> buf;
asio::write(socket, asio::buffer(dataOut), error);

if (!error)
{
    std::cout << "Receiving...!" << std::endl;               

    asio::async_read(socket, asio::buffer(buf), 
        std::bind(onRead, std::ref(socket), std::ref(buf),
         std::placeholders::_1,
         std::placeholders::_2));

    context.run();
}
else
{
    std::cout << "send failed: " << error.message() << std::endl;
}
  

(Я использую Boost, поэтому вам следует заменить system::error_code on asio::error_code ).

Комментарии:

1. Отличное объяснение и код. позвольте мне поиграть, чтобы убедиться, что я все понимаю. Я вернусь!

2. @StefanoMtangoo нет проблем, рад помочь 🙂