При передаче по сети липкие пакеты и половинные пакеты должны быть наиболее распространенными проблемами.Как наиболее часто используемая сетевая структура NIO Netty в Java, как она решает эту проблему? Давайте посмотрим сегодня.
определение
При передаче TCP, когда клиент отправляет данные, он фактически записывает данные в кэш TCP, и в это время также будут генерироваться липкие пакеты и полупакеты.
Клиент отправляет два сообщения на серверABC
а такжеDEF
, Сколько ситуаций получит сервер? Возможно, что все сообщения получены одновременноABCDEF
, может быть получено три сообщенияAB
,CD
,EF
.
Как упоминалось выше, все сообщения принимаются сразуABCDEF
, похожие на липкие пакеты. Если размер пакетов, отправляемых клиентом, меньше, чем емкость кеша TCP, а в кеше TCP может храниться несколько пакетов, то при одном обмене данными между клиентом и сервером может передаваться несколько пакетов, а сервер может хранить несколько пакетов. из кэша TCP.Чтение нескольких пакетов, это явление называется粘包
.
Последний, упомянутый выше, получил три сообщенияAB
,CD
,EF
, похоже на половину пачки. Если размер пакета, отправляемого клиентом, больше, чем емкость кеша TCP, пакет будет разделен на несколько пакетов и отправлен на сервер через Socket несколько раз.Сервер получает данные, полученные из кеша в первый раз. .является частью всего пакета, который генерируется в это время半包
(Половина пакета не означает, что была получена только половина полного пакета, это означает, что была получена часть полного пакета).
причина
На самом деле, из приведенного выше определения мы, вероятно, можем узнать причину.
Основные причины липких пакетов:
- Отправитель записывает данные каждый раз, когда
- Получатель не считывает данные буфера сокета своевременно
Основные причины полупакета:
- Отправитель записывает данные каждый раз > Размер буфера сокета
- Отправляемые данные больше, чем MTU (максимальная единица передачи) протокола, поэтому их необходимо распаковать.
На самом деле, мы можем посмотреть на проблему под другим углом:
- от
收发
С точки зрения, передача может быть принята несколько раз, и несколько передач могут быть приняты одновременно. - от
传输
С точки зрения, одна передача может занимать несколько пакетов передачи, и несколько передач могут совместно использовать один пакет передачи.
Первопричина на самом деле
TCP — это протокол потоковой передачи, и сообщения не имеют границ.
(PS: UDP также может передавать несколько пакетов за раз или пакет несколько раз, но каждое сообщение ограничено, поэтому не будет проблем с липкими пакетами и полупакетами.)
Решение
Как упоминалось выше, причина, по которой UDP не имеет проблемы с прилипшими пакетами и половинными пакетами, в основном заключается в том, что сообщение имеет границы, поэтому мы также можем принять аналогичную идею.
изменить на короткую ссылку
Измените соединение TCP на короткое соединение, один запрос для короткого соединения. В этом случае сообщение между установлением соединения и разрывом соединения является передаваемой информацией, и сообщение также создает границу.
Этот метод очень прост и не требует особых изменений в нашем приложении. Но недостаток также очевиден, низкая эффективность, TCP-подключение и отключение будут включать трехстороннее рукопожатие и четырехстороннее рукопожатие, каждое сообщение будет включать эти процессы, что является пустой тратой производительности.
Поэтому этот метод не рекомендуется.
инкапсуляция в кадр
Фрейминг инкапсулирован, то есть исходной единицей отправки сообщения является размер буфера, но теперь он заменен фреймом, так что мы можем настроить границу. Обычно есть 4 способа:
Фиксированная длина
Таким образом, граница сообщения имеет фиксированную длину.
Преимущество в том, что реализация очень проста, а недостаток в том, что существует огромная трата места.Если большинство отправляемых сообщений относительно короткие, будет потрачено много места.
Поэтому этот метод обычно не рекомендуется.
Разделитель
Таким образом, граница сообщения является самим разделителем.
Преимущество в том, что пространство больше не тратится впустую, а реализация относительно проста. Недостатком является то, что его необходимо экранировать, когда разделитель появляется в самом содержимом, поэтому независимо от того, отправляется оно или получено, необходимо сканировать все содержимое.
Поэтому этот метод не очень эффективен, но его можно использовать.
Специальное поле длины
В этом он аналогичен Content-Length в Http-запросе, есть специальное поле для хранения длины сообщения. Как сервер, при получении сообщения он сначала анализирует поле фиксированной длины (поле длины), чтобы получить общую длину сообщения, а затем считывает последующее содержимое.
Преимущество в том, что пользовательские данные точно расположены, и контент не нужно экранировать. Недостатком является то, что длина теоретически ограничена, а максимально возможную длину необходимо ограничить заранее, чтобы определить количество байтов, занимаемых длиной.
Поэтому этот метод настоятельно рекомендуется.
другие методы
Другие методы отличаются, например, JSON можно рассматривать как использование{}
Является ли он парным. Эти преимущества и недостатки должны быть взвешены каждым в их соответствующих сценариях.
Реализация в нетти
Netty поддерживает первые три упомянутых выше метода инкапсуляции и кадрирования.
Способ | расшифровка | кодирование |
---|---|---|
Фиксированная длина | FixedLengthFrameDecoder | Простой |
разделитель | DelimiterBasedFrameDecoder | Простой |
Специальное поле длины | LengthFieldBasedFrameDecoder | LengthFieldPrepender |
Суммировать
Сегодня я в основном расскажу о проблеме липкого пакета и половинного пакета, идеях решения и поддержке в Netty. В следующей статье я сосредоточусь на конкретной реализации в Netty, так что следите за обновлениями.
Если вам интересно, вы можете посетить мой блог или обратить внимание на мой официальный аккаунт и номер заголовка, возможно, будут неожиданные сюрпризы.