Netty中最簡(jiǎn)單的粘包解析方法分享
前言
黏包 是指網(wǎng)絡(luò)上有多條數(shù)據(jù)發(fā)送給服務(wù)端, 但是由于某種原因這些數(shù)據(jù)在被接受的時(shí)候進(jìn)行了重新組合, 這就是黏包, 本篇文章用來(lái)演示一種最簡(jiǎn)單的黏包解析方法, 適用于初初初級(jí)選手
正常來(lái)講客戶端發(fā)送給服務(wù)端的消息, 都是存在專門的通訊協(xié)議的, 為了避免黏包現(xiàn)象, 我們通常有幾種方式去制定相應(yīng)的規(guī)則: 消息長(zhǎng)度固定, 特定分隔符, 消息長(zhǎng)度固定+特定分隔符等
本文是采用了 特定分隔符 的方式, 每條數(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)生黏包問(wèn)題嗎?
本文不會(huì)對(duì) tcp 和 udp 進(jìn)行詳細(xì)的講解, 感興趣的可以自行百度或者掘金
tcp
先說(shuō)結(jié)論: tcp 會(huì)產(chǎn)生黏包問(wè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算法對(duì)多個(gè)數(shù)據(jù)包進(jìn)行合并再發(fā)送到服務(wù)器上. 這種情況下服務(wù)器在接收到消息的時(shí)候無(wú)法區(qū)分哪些數(shù)據(jù)包是分開的, 所以產(chǎn)生了黏包
還有一種可能是: 服務(wù)器在接收到數(shù)據(jù)之后, 將數(shù)據(jù)放入到緩沖區(qū)中, 如果消息沒(méi)有被及時(shí)的從緩沖區(qū)取走, 下次在取數(shù)據(jù)的時(shí)候就會(huì)出現(xiàn)一次取到多個(gè)數(shù)據(jù)包的情況, 造成黏包現(xiàn)象
tcp三次握手:
- 客戶端向服務(wù)端發(fā)送建立通道請(qǐng)求
- 服務(wù)端向客戶端發(fā)送允許客戶端建立一個(gè)單向的數(shù)據(jù)通道; 服務(wù)端向客戶端發(fā)送建立通道請(qǐng)求
- 客戶端向服務(wù)端發(fā)送允許服務(wù)端建立一個(gè)單向的數(shù)據(jù)通道
此時(shí)數(shù)據(jù)通道是雙向的, 允許客戶端、服務(wù)端互相發(fā)送消息
Nagle算法:
- 如果包長(zhǎng)度達(dá)到 MSS, 則允許發(fā)送
- 如果該包中含有 FIN, 則允許發(fā)送
- 設(shè)置了 TCP_NODELAY 選項(xiàng), 若所有發(fā)出去的小數(shù)據(jù)包(長(zhǎng)度小于 MSS )均被確認(rèn), 則允許發(fā)送
- 若上述條件均未滿足, 但發(fā)送了超時(shí)(一般為 200ms ), 則立即發(fā)送
udp
upd 不存在黏包問(wèn)題
udp本身是無(wú)連接的不可靠傳輸協(xié)議, 不會(huì)對(duì)數(shù)據(jù)包進(jìn)行合并發(fā)送, 也就沒(méi)有Nagle算法, 不會(huì)存在數(shù)據(jù)合并的情況, 每一個(gè)數(shù)據(jù)包都是完整的, 所以不存在黏包現(xiàn)象
最簡(jiǎn)單的黏包解析
黏包解析也很簡(jiǎ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)前的長(zhǎ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ù)包長(zhǎng)度
int length = i + 1 - buffer.position();
// 根據(jù)當(dāng)前數(shù)據(jù)包長(zhǎng)度, 動(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中最簡(jiǎn)單的粘包解析方法分享的文章就介紹到這了,更多相關(guān)粘包解析內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
java實(shí)現(xiàn)合并兩個(gè)已經(jīng)排序的列表實(shí)例代碼
這篇文章主要介紹了java實(shí)現(xiàn)合并兩個(gè)已經(jīng)排序的列表實(shí)例代碼,有需要的朋友可以參考一下2013-12-12
Mybatis中一條SQL使用兩個(gè)foreach的問(wèn)題及解決
這篇文章主要介紹了Mybatis中一條SQL使用兩個(gè)foreach的問(wèn)題及解決,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-02-02
Java 17 更新后的 strictfp 關(guān)鍵字
strictfp 可能是最沒(méi)有存在感的關(guān)鍵字了,很多人寫了多年 Java 甚至都不知道它的存在,strictfp,字面意思就是嚴(yán)格的浮點(diǎn)型。這玩意兒居然還有個(gè)關(guān)鍵字,可見(jiàn)其地位還是很高的。下面文章小編就帶大家詳細(xì)介紹其關(guān)鍵字,需要的朋友可以參考一下2021-09-09
springboot集成kafka消費(fèi)手動(dòng)啟動(dòng)停止操作
這篇文章主要介紹了springboot集成kafka消費(fèi)手動(dòng)啟動(dòng)停止操作,本文給大家介紹項(xiàng)目場(chǎng)景及解決分析,結(jié)合實(shí)例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下2022-09-09
Spring MVC Interceptor 實(shí)現(xiàn)性能監(jiān)控的功能代碼
本篇文章主要介紹了Spring MVC Interceptor 實(shí)現(xiàn)性能監(jiān)控的功能代碼,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-09-09

