Основные понятия
Поскольку TCP является потоковым протоколом, если вы создаёте собственный протокол (например, данные заголовка, команды, длина данных, содержимое данных и контрольная сумма), то при отправке данных по TCP необходимо учитывать проблемы с упаковкой и распаковкой.
В настоящее время ASIO широко используется, и я считаю, что поддержка и дизайн ASIO для упаковки и распаковки TCP довольно хороши. Если вы используете ASIO для распаковки TCP, вы обнаружите, что это действительно удобно.
На основе ASIO я создал сетевую структуру ASIO2, которая содержит полную поддержку функции распаковки данных TCP (конечно, внутренняя часть всё ещё использует сам ASIO). Данные автоматически распаковываются в соответствии с указанным отдельным символом, строкой или пользовательским протоколом. Недавно я обнаружил, что некоторые люди спрашивают о том, как использовать функцию автоматической распаковки ASIO match condition. В интернете также нет подробных инструкций или материалов по использованию match condition, поэтому здесь я предоставлю некоторые идеи. Подробные коды и инструкции следующие:
// Предположим, что формат протокола следующий: данные заголовка, длина данных и данные содержимого
// Тип следующий:
// int head; // данные заголовка, предположим, что значение данных заголовка фиксировано как 0x01020304
// int length; // длина данных
// ... // содержимое данных, длина которого является переменной
// Далее показано, как использовать match condition ASIO для анализа
using iterator = asio::buffers_iterator<asio::streambuf::const_buffers_type>;
std::pair<iterator, bool> match_role(iterator begin, iterator end) {
// Когда ASIO получает данные, он сохраняет их в своём буфере, а затем вызывает вашу функцию match_role.
// Параметр begin указывает на начальную позицию данных, то есть начало данных.
// Параметр end указывает на конечную позицию данных, смещённую на один байт назад от конца данных, аналогично \0 в конце строки char*.
// Обратите внимание, что хотя end является концом данных, это не обязательно конец всего пакета данных.
// Здесь я приведу пример, чтобы подробно объяснить:
// 1. Предположим, вы создали полный пакет данных следующим образом:
// 0x01 02 03 04 00 00 00 01 41
// <---- заголовок -----> <-- длина --> <- данные -> 41 - это символ A
// 2. Отправьте этот пакет данных.
// 3. Начните получать данные на другом конце.
// Мы используем крайний случай в качестве примера: предположим, что на первом этапе мы получаем только один байт 0x01, ASIO сохранит этот байт в своём буфере.
// Буфер теперь содержит один байт: 0x01. В этом случае begin - это указатель адреса данных 0x01, а end - это позиция, смещённая на один байт после 0x01;
// На втором этапе мы снова получаем один байт 0x02, ASIO добавит этот байт к буферу. Теперь в буфере два байта: 0x01 и 0x02.
// В этом случае begin по-прежнему является указателем адреса данных 0x01, а end - позицией, смещённой на один байт после 0x02. И так далее... Помните, что begin всегда является начальной позицией данных.
// Тогда что такое итератор? Проще говоря, вы можете рассматривать его как char* и использовать ++ для увеличения и * для получения значения.
// Затем давайте посмотрим, как анализировать:
iterator i = begin; // Сначала сохраните begin в переменной i, которую можно просто понять как char * i = begin.
// Используйте end для вычитания begin, чтобы получить длину текущих данных в буфере. Сначала посмотрите, достаточно ли этой длины для минимального размера, который равен длине типа head плюс длина типа length, всего 8 байтов.
if (end - begin < 4 + 4) {
// Возвращаемое значение представляет собой пару. Первый параметр должен быть итератором, который можно понимать как char*, указывающий на конец данных, удовлетворяющих вашим требованиям.
// Второй параметр пары указывает, соответствуют ли текущие данные в буфере вашим требованиям. Если да, то true, иначе false.
// Если второй параметр пары имеет значение false, это означает, что сообщите ASIO, что данных в текущем буфере недостаточно, и продолжайте получать.
// Когда данные будут получены снова, ASIO снова вызовет функцию match_role здесь. «Повторный вызов» первого параметра begin будет значением адреса, которое вы указали в паре первого параметра.
// Если второй параметр пары истинен, это означает, что данные в текущем буфере удовлетворяют требованиям. Произойдёт следующее:
// После того, как эта пара будет возвращена в ASIO, ASIO выполнит две операции: 1) передаст данные от «begin до первого параметра пары» через функцию обратного вызова вам; 2) после обработки этих данных в вашей функции обратного вызова ASIO очистит эти данные.
// Затем, когда функция match_role вызывается снова, begin будет указывать на позицию данных, которые вы передали через первый параметр пары в прошлый раз.
// Поскольку длины недостаточно, мы устанавливаем первый параметр пары равным begin, указывая, что следующий анализ начинается с позиции begin. Второй параметр пары устанавливается в false, указывая на то, что данные текущего буфера не удовлетворяют требованиям и должны продолжать получать.
return std::pair(begin, false);
}
// Минимальная длина достаточна, затем проверьте, соответствует ли заголовок данных 0x01020304.
int head = *(reinterpret_cast<const int*>(i.operator->()));
if (head != 0x01020304) {
// Если head не равно 0x01020304, это означает, что данные недействительны. Это может означать, что клиент, возможно, вообще не ваш клиент.
// Если это ваш клиент и данные отправляются в соответствии с протоколом, такая ситуация невозможна. Поэтому лучше всего разорвать это соединение напрямую. Как разорвать соединение? Посмотрите на объяснение позже.
return std::pair(begin, true);
}
// Минимальный размер и заголовок данных верны, начните проверять длину содержимого данных.
i += 4; // i смещается на 4 байта вперёд, достигая позиции length.
int length = *(reinterpret_cast<const int*>(i.operator->())); // Получить длину содержимого данных этого пакета.
// end - begin равно общей длине, минус 8 представляет фактическую длину содержимого данных.
// Если длина содержимого меньше полученной длины, необходимо продолжить получение, поэтому верните пару (begin, false).
if (end - begin - 8 < length)
return std::pair(begin, false);
// Теперь минимальная длина, заголовок данных и длина содержимого данных все в порядке.
i += 4; // i снова смещается вперёд на 4 байта, достигая фактического содержимого данных.
// Значение пары (i + length) - это конец данных этого пакета, true означает, что данные соответствуют требованиям, и ASIO вернёт все данные от begin до i + length вам.
// При следующем вызове функции match_role ASIO передаст begin как позицию, смещённую вперёд на один байт от i + length.
return std::pair(i + length, true);
}
// Ниже приводится пример из исходного кода ASIO2 demo. Здесь используется цикл while, но на самом деле использование цикла while не имеет никакого значения, потому что вы знаете, что этот цикл while может быть выполнен только один раз.
// Я не знаю, почему я всё ещё пишу цикл while. Я думаю, что это может быть связано с каким-то примером, который я видел раньше, но я не помню его.
// В любом случае, я решил оставить этот пример здесь, чтобы дать вам больше информации.
Обратите внимание, что в этом тексте могут быть неточности перевода, связанные с неоднозначностью некоторых формулировок.
Вы можете оставить комментарий после Вход в систему
Неприемлемый контент может быть отображен здесь и не будет показан на странице. Вы можете проверить и изменить его с помощью соответствующей функции редактирования.
Если вы подтверждаете, что содержание не содержит непристойной лексики/перенаправления на рекламу/насилия/вульгарной порнографии/нарушений/пиратства/ложного/незначительного или незаконного контента, связанного с национальными законами и предписаниями, вы можете нажать «Отправить» для подачи апелляции, и мы обработаем ее как можно скорее.
Опубликовать ( 0 )