Netty中最簡單的粘包解析方法分享
前言
黏包 是指網(wǎng)絡(luò)上有多條數(shù)據(jù)發(fā)送給服務(wù)端, 但是由于某種原因這些數(shù)據(jù)在被接受的時(shí)候進(jìn)行了重新組合, 這就是黏包, 本篇文章用來演示一種最簡單的黏包解析方法, 適用于初初初級(jí)選手
正常來講客戶端發(fā)送給服務(wù)端的消息, 都是存在專門的通訊協(xié)議的, 為了避免黏包現(xiàn)象, 我們通常有幾種方式去制定相應(yīng)的規(guī)則: 消息長度固定, 特定分隔符, 消息長度固定+特定分隔符等
本文是采用了 特定分隔符 的方式, 每條數(shù)據(jù)包都以 \n 結(jié)尾
例如以下三條原始數(shù)據(jù)數(shù)據(jù):
hell\n
ningxuan\n
thanks\n
變成了以下兩個(gè):
hello\nningxuan\nth
anks\n
這就是黏包
黏包產(chǎn)生的原因
在 socket 網(wǎng)絡(luò)編程中, TCP 和 UDP 分別是面向連接和非面相連接的. 但是他們都存在產(chǎn)生黏包問題嗎?
本文不會(huì)對 tcp 和 udp 進(jìn)行詳細(xì)的講解, 感興趣的可以自行百度或者掘金
tcp
先說結(jié)論: tcp 會(huì)產(chǎn)生黏包問題
由于 tcp 協(xié)議本身的機(jī)制(面向連接的可靠性協(xié)議-三次握手機(jī)制) 客戶端與服務(wù)端會(huì)維持一個(gè)連接(Channel), 數(shù)據(jù)在連接不斷開的情況下, 可以將多個(gè)數(shù)據(jù)包持續(xù)不斷的發(fā)送到服務(wù)器上.
但是如果發(fā)送的網(wǎng)絡(luò)數(shù)據(jù)包太小, tcp就會(huì)啟用Nagle算法對多個(gè)數(shù)據(jù)包進(jìn)行合并再發(fā)送到服務(wù)器上. 這種情況下服務(wù)器在接收到消息的時(shí)候無法區(qū)分哪些數(shù)據(jù)包是分開的, 所以產(chǎn)生了黏包
還有一種可能是: 服務(wù)器在接收到數(shù)據(jù)之后, 將數(shù)據(jù)放入到緩沖區(qū)中, 如果消息沒有被及時(shí)的從緩沖區(qū)取走, 下次在取數(shù)據(jù)的時(shí)候就會(huì)出現(xiàn)一次取到多個(gè)數(shù)據(jù)包的情況, 造成黏包現(xiàn)象
tcp三次握手:
- 客戶端向服務(wù)端發(fā)送建立通道請求
- 服務(wù)端向客戶端發(fā)送允許客戶端建立一個(gè)單向的數(shù)據(jù)通道; 服務(wù)端向客戶端發(fā)送建立通道請求
- 客戶端向服務(wù)端發(fā)送允許服務(wù)端建立一個(gè)單向的數(shù)據(jù)通道
此時(shí)數(shù)據(jù)通道是雙向的, 允許客戶端、服務(wù)端互相發(fā)送消息
Nagle算法:
- 如果包長度達(dá)到 MSS, 則允許發(fā)送
- 如果該包中含有 FIN, 則允許發(fā)送
- 設(shè)置了 TCP_NODELAY 選項(xiàng), 若所有發(fā)出去的小數(shù)據(jù)包(長度小于 MSS )均被確認(rèn), 則允許發(fā)送
- 若上述條件均未滿足, 但發(fā)送了超時(shí)(一般為 200ms ), 則立即發(fā)送
udp
upd 不存在黏包問題
udp本身是無連接的不可靠傳輸協(xié)議, 不會(huì)對數(shù)據(jù)包進(jìn)行合并發(fā)送, 也就沒有Nagle算法, 不會(huì)存在數(shù)據(jù)合并的情況, 每一個(gè)數(shù)據(jù)包都是完整的, 所以不存在黏包現(xiàn)象
最簡單的黏包解析
黏包解析也很簡單:
- 遍歷當(dāng)前的 ByteBuffer 緩沖區(qū)
- 判斷元素為 '\n' 的下標(biāo)
- 生成新的 ByteBuffer 緩沖區(qū)
- 將起始下標(biāo)到標(biāo)記下標(biāo)的字符寫到新的緩沖區(qū)
具體代碼如下所示:
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(); // 根據(jù) buffer 當(dāng)前的長度進(jìn)行遍歷 for (int i = 0; i < buffer.limit(); i++) { // 判斷當(dāng)前下標(biāo)元素是不是數(shù)據(jù)包切割符 \n if (buffer.get(i) == '\n'){ // 注意這個(gè)時(shí)候 buffer 的 position 屬性一直為 0 // 計(jì)算當(dāng)前數(shù)據(jù)包長度 int length = i + 1 - buffer.position(); // 根據(jù)當(dāng)前數(shù)據(jù)包長度, 動(dòng)態(tài)生成新的 緩沖區(qū) ByteBuffer target = ByteBuffer.allocate(length); for (int j = 0; j < length; j++) { target.put(buffer.get()); // 注意這個(gè)時(shí)候 buffer 的 position 屬性在 ++ } // 打印 target 當(dāng)前的元素和屬性 ByteBufferUtils.selectAll(target); } } buffer.compact(); } }
到此這篇關(guān)于Netty中最簡單的粘包解析方法分享的文章就介紹到這了,更多相關(guān)粘包解析內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
java 較大數(shù)據(jù)量取差集,list.removeAll性能優(yōu)化詳解
這篇文章主要介紹了java 較大數(shù)據(jù)量取差集,list.removeAll性能優(yōu)化詳解,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-09-09詳解json string轉(zhuǎn)換為java bean及實(shí)例代碼
這篇文章主要介紹了詳解json string轉(zhuǎn)換為java bean及實(shí)例代碼的相關(guān)資料,這里提供實(shí)例代碼幫助大家理解,需要的朋友可以參考下2017-07-07JAVA實(shí)現(xiàn)基于皮爾遜相關(guān)系數(shù)的相似度詳解
這篇文章主要介紹了JAVA實(shí)現(xiàn)基于皮爾遜相關(guān)系數(shù)的相似度詳解,具有一定參考價(jià)值,需要的朋友可以了解下。2017-11-11Java8?stream流分組groupingBy的使用方法代碼
對于java8的新特性groupingBy方法,相信有很多人都在工作中用過,這篇文章主要給大家介紹了關(guān)于Java8?stream流分組groupingBy的使用方法,需要的朋友可以參考下2024-01-01解決IDEA2020 創(chuàng)建maven項(xiàng)目沒有src/main/java目錄和webapp目錄問題
這篇文章主要介紹了IDEA2020 創(chuàng)建maven項(xiàng)目沒有src/main/java目錄和webapp目錄問題解決方法,本文通過圖文并茂的形式給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-10-10使用Java編寫控制JDBC連接、執(zhí)行及關(guān)閉的工具類
這篇文章主要介紹了如何使用Java來編寫控制JDBC連接、執(zhí)行及關(guān)閉的程序,包括一個(gè)針對各種數(shù)據(jù)庫通用的釋放資源的工具類的寫法,需要的朋友可以參考下2016-03-03Java找出兩個(gè)大數(shù)據(jù)量List集合中的不同元素的方法總結(jié)
本文將帶大家了解如何快速的找出兩個(gè)相似度非常高的List集合里的不同元素。主要通過Java API、List集合雙層遍歷比較不同、借助Map集合查找三種方式,需要的可以參考一下2022-10-10