Netty中最簡單的粘包解析方法分享

前言

黏包 是指網絡上有多條數據發送給服務端, 但是由於某種原因這些數據在被接受的時候進行瞭重新組合, 這就是黏包, 本篇文章用來演示一種最簡單的黏包解析方法, 適用於初初初級選手

正常來講客戶端發送給服務端的消息, 都是存在專門的通訊協議的, 為瞭避免黏包現象, 我們通常有幾種方式去制定相應的規則: 消息長度固定, 特定分隔符, 消息長度固定+特定分隔符

本文是采用瞭 特定分隔符 的方式, 每條數據包都以 \n 結尾

例如以下三條原始數據數據:

hell\n
ningxuan\n
thanks\n

變成瞭以下兩個:

hello\nningxuan\nth
anks\n

這就是黏包

黏包產生的原因

socket 網絡編程中, TCPUDP 分別是面向連接和非面相連接的. 但是他們都存在產生黏包問題嗎?

本文不會對 tcp 和 udp 進行詳細的講解, 感興趣的可以自行百度或者掘金

tcp

先說結論: tcp 會產生黏包問題

由於 tcp 協議本身的機制(面向連接的可靠性協議-三次握手機制) 客戶端與服務端會維持一個連接(Channel), 數據在連接不斷開的情況下, 可以將多個數據包持續不斷的發送到服務器上.

但是如果發送的網絡數據包太小, tcp就會啟用Nagle算法對多個數據包進行合並再發送到服務器上. 這種情況下服務器在接收到消息的時候無法區分哪些數據包是分開的, 所以產生瞭黏包

還有一種可能是: 服務器在接收到數據之後, 將數據放入到緩沖區中, 如果消息沒有被及時的從緩沖區取走, 下次在取數據的時候就會出現一次取到多個數據包的情況, 造成黏包現象

tcp三次握手:

  • 客戶端服務端發送建立通道請求
  • 服務端客戶端發送允許客戶端建立一個單向的數據通道; 服務端向客戶端發送建立通道請求
  • 客戶端服務端發送允許服務端建立一個單向的數據通道

此時數據通道是雙向的, 允許客戶端、服務端互相發送消息

Nagle算法:

  • 如果包長度達到 MSS, 則允許發送
  • 如果該包中含有 FIN, 則允許發送
  • 設置瞭 TCP_NODELAY 選項, 若所有發出去的小數據包(長度小於 MSS )均被確認, 則允許發送
  • 若上述條件均未滿足, 但發送瞭超時(一般為 200ms ), 則立即發送

udp

upd 不存在黏包問題

udp本身是無連接的不可靠傳輸協議, 不會對數據包進行合並發送, 也就沒有Nagle算法, 不會存在數據合並的情況, 每一個數據包都是完整的, 所以不存在黏包現象

最簡單的黏包解析

黏包解析也很簡單:

  • 遍歷當前的 ByteBuffer 緩沖區
  • 判斷元素為 '\n' 的下標
  • 生成新的 ByteBuffer 緩沖區
  • 將起始下標到標記下標的字符寫到新的緩沖區

具體代碼如下所示:

public class ByteBufferTest {
    public static void main(String[] args) {
        ByteBuffer buffer = ByteBuffer.allocate(32);
        buffer.put("hello\nningxuan\nth".getBytes());
        split(buffer);
        buffer.put("anks\n".getBytes());
        split(buffer);
    }

    private static void split(ByteBuffer buffer){
        // 將 buffer 切換為 讀模式
        buffer.flip();
        // 根據 buffer 當前的長度進行遍歷
        for (int i = 0; i < buffer.limit(); i++) {
            // 判斷當前下標元素是不是數據包切割符 \n
            if (buffer.get(i) == '\n'){ // 註意這個時候 buffer 的 position 屬性一直為 0
                // 計算當前數據包長度
                int length = i + 1 - buffer.position();
                // 根據當前數據包長度, 動態生成新的 緩沖區
                ByteBuffer target = ByteBuffer.allocate(length);
                for (int j = 0; j < length; j++) {
                    target.put(buffer.get());   // 註意這個時候 buffer 的 position 屬性在 ++
                }
                // 打印 target 當前的元素和屬性
                ByteBufferUtils.selectAll(target);
            }
        }
        buffer.compact();
    }
}

到此這篇關於Netty中最簡單的粘包解析方法分享的文章就介紹到這瞭,更多相關粘包解析內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!

推薦閱讀: