boost asio async_read (), кажется, пропускает некоторые нули - PullRequest
0 голосов
/ 08 ноября 2018

Я немного схожу с ума от простого разговора TCP asio boost.

У меня есть сервер и клиент.Я использую сообщения с префиксом длины.Клиент отправляет «один», а сервер отвечает «два».Вот что я вижу:

Клиент отправляет, а сервер получает 00 00 00 03 6F 6E 65 (== 0x0003 один).

Сервер отвечает отправкой 00 00 00 03 74 77 6F (==0x0003 два).

Теперь вот, где это очень странно (код ниже).Если клиент читает четыре байта, я ожидаю, что он получит 00 00 00 03.Если он будет читать семь, я ожидаю увидеть 00 00 00 03 74 77 6F.(На самом деле, он будет читать четыре (длина заголовка), а затем три (тело).)

Но то, что я на самом деле вижу, так это то, что если я читаю семь сразу, я вижу 00 00 00 03 74 77 6F,если я прошу только четыре, я вижу 74 77 6F 03.Это не имеет никакого смысла для меня.

Вот код, который я использую для его получения (за исключением некоторых операторов печати и т. Д.):

const int kTcpHeaderSize = 4;
const int kTcpMessageSize = 2048;
std::array<char, kTcpMessageSize + kTcpHeaderSize> receive_buffer_;

void TcpConnection::ReceiveHeader() {
    boost::asio::async_read(
        socket_, boost::asio::buffer(receive_buffer_, kTcpHeaderSize),
        [this](boost::system::error_code error_code,
               std::size_t received_length) {
            if (error_code) {
                LOG_WARNING << "Header read error: " << error_code;
                socket_.close();  // TODO: Recover better.
                return;
            }
            if (received_length != kTcpHeaderSize) {
                LOG_ERROR << "Header length " << received_length
                          << " != " << kTcpHeaderSize;
                socket_.close();  // TODO: Recover better.
                return;
            }
            uint32_t read_length_network;
            memcpy(&read_length_network, receive_buffer_.data(),
                   kTcpHeaderSize);
            uint32_t read_length = ntohl(read_length_network);
            // Error: read_length is in the billions.
            ReceiveBody(read_length);
        });
}

Обратите внимание, что kTcpHeaderSize4. Если я изменю его на 7 (что не имеет смысла, но только для эксперимента), я вижу поток из 7 байтов, который я ожидаю.Когда это 4, я вижу поток, который не является первыми четырьмя байтами того, что я ожидаю.

Любые указатели, что я делаю неправильно?

Ответы [ 2 ]

0 голосов
/ 25 декабря 2018

Предложение @sergiopm об использовании transfer_all было хорошим, и я уверен, что оно помогло. Другая проблема связана с временем жизни буфера в асинхронных функциях отправки / получения. Видимо, я немного запутался в том, как долго будут жить некоторые вещи и как долго они мне нужны, поэтому я время от времени переписывал вещи. Возможно, это было важнее, чем transfer_all, но я все еще рад отдать должное @sergiopm за помощь в продвижении.

Намерение было просто иметь простой tcp-клиент или сервер, который я могу объявить, передать ему обратный вызов, а затем продолжить свой путь, зная, что я могу обратить внимание только на эти обратные вызовы.

Я почти уверен, что что-то подобное должно существовать (тысячи раз). Не стесняйтесь комментировать ниже, как для меня, так и для тех, кто придет позже, если вы считаете, что для этой задачи есть лучшие библиотеки, чем asio (то есть, это потребовало бы значительно меньше кода с моей стороны). Принципиальное ограничение заключается в том, что из-за множества языков и сервисов нам необходимо иметь проводной протокол. В противном случае мы попадаем в такие вещи, как «есть ли в библиотеке X модуль для языка Y?».

Кроме того, мне интересно, что практически каждый пример, который я нашел, выполняет кодирование с префиксом длины, а не начало / конец кодирования пакетов. Префикс длины действительно прост в реализации, но, если я не ошибаюсь, он страдает от повторной синхронизации ада: если поток прерывается («Я собираюсь отправить вам 100 байтов, вот первые 50, но потом я умер») ) мне не ясно, что нет сценариев, когда я не могу выполнить повторную синхронизацию должным образом.

В любом случае, я многому научился по пути, я рекомендую упражнение.

0 голосов
/ 20 ноября 2018

Из того, что я вижу в вашем коде, он должен работать в соответствии с async_read документацией :

Асинхронная операция будет продолжаться до тех пор, пока не будет выполнено одно из следующих условий:

  • Поставленные буферы заполнены. То есть переданные байты равны сумме размеров буфера.
  • Произошла ошибка.

Однако см. Замечание внизу:

Эта перегрузка эквивалентна вызову:

boost::asio::async_read( s, buffers, boost::asio::transfer_all(), handler);

Похоже, что условие Transfer_all может быть проверено только.

Попробуйте использовать условие Transfer_exactly и, если оно работает, сообщите о проблеме на https://github.com/boostorg/asio/issues.

...