您好,登錄后才能下訂單哦!
TCP粘拆包與Netty代碼的示例分析,相信很多沒(méi)有經(jīng)驗(yàn)的人對(duì)此束手無(wú)策,為此本文總結(jié)了問(wèn)題出現(xiàn)的原因和解決方法,通過(guò)這篇文章希望你能解決這個(gè)問(wèn)題。
TCP是個(gè)“流”協(xié)議,所謂流,就是沒(méi)有界限的一串?dāng)?shù)據(jù)??梢韵胂牒永锏牧魉沁B成一片的,其間并沒(méi)有分界線。TCP底層并不了解上層業(yè)務(wù)數(shù)據(jù)的具體含義,它會(huì)根據(jù)TCP緩沖區(qū)的實(shí)際情況進(jìn)行包的劃分,所以在業(yè)務(wù)上認(rèn)為,一個(gè)完整的包可能會(huì)被TCP拆分成多個(gè)包進(jìn)行發(fā)送,也有可能把多個(gè)小的包封裝成一個(gè)大的數(shù)據(jù)包發(fā)送,這就是所謂的TCP粘包和拆包問(wèn)題。
應(yīng)用程序?qū)懭氲臄?shù)據(jù)大于套接字緩沖區(qū)大小,這將會(huì)發(fā)生拆包。
應(yīng)用程序?qū)懭霐?shù)據(jù)小于套接字緩沖區(qū)大小,網(wǎng)卡將應(yīng)用多次寫(xiě)入的數(shù)據(jù)發(fā)送到網(wǎng)絡(luò)上,這將會(huì)發(fā)生粘包。
進(jìn)行MSS(最大報(bào)文長(zhǎng)度)大小的TCP分段,當(dāng)TCP報(bào)文長(zhǎng)度-TCP頭部長(zhǎng)度>MSS的時(shí)候?qū)l(fā)生拆包。
接收方法不及時(shí)讀取套接字緩沖區(qū)數(shù)據(jù),這將發(fā)生粘包。
第一種情況:接收端正常收到兩個(gè)數(shù)據(jù)包,即沒(méi)有發(fā)生拆包和粘包的現(xiàn)象,此種情況不在本文的討論范圍內(nèi)。
第二種情況:接收端只收到一個(gè)數(shù)據(jù)包,由于TCP是不會(huì)出現(xiàn)丟包的,所以這一個(gè)數(shù)據(jù)包中包含了發(fā)送端發(fā)送的兩個(gè)數(shù)據(jù)包的信息,這種現(xiàn)象即為粘包。這種情況由于接收端不知道這兩個(gè)數(shù)據(jù)包的界限,所以對(duì)于接收端來(lái)說(shuō)很難處理。
第三種情況:這種情況有兩種表現(xiàn)形式,如下圖。接收端收到了兩個(gè)數(shù)據(jù)包,但是這兩個(gè)數(shù)據(jù)包要么是不完整的,要么就是多出來(lái)一塊,這種情況即發(fā)生了拆包和粘包。這兩種情況如果不加特殊處理,對(duì)于接收端同樣是不好處理的。
發(fā)送端給每個(gè)數(shù)據(jù)包添加包首部,首部中應(yīng)該至少包含數(shù)據(jù)包的長(zhǎng)度,這樣接收端在接收到數(shù)據(jù)后,通過(guò)讀取包首部的長(zhǎng)度字段,便知道每一個(gè)數(shù)據(jù)包的實(shí)際長(zhǎng)度了。
發(fā)送端將每個(gè)數(shù)據(jù)包封裝為固定長(zhǎng)度(不夠的可以通過(guò)補(bǔ)0填充),這樣接收端每次從接收緩沖區(qū)中讀取固定長(zhǎng)度的數(shù)據(jù)就自然而然的把每個(gè)數(shù)據(jù)包拆分開(kāi)來(lái)。
可以在數(shù)據(jù)包之間設(shè)置邊界,添加特殊符號(hào)(如:回車符),這樣,接收端通過(guò)這個(gè)邊界就可以將不同的數(shù)據(jù)包拆分開(kāi)。
Netty封裝了JDK的NIO,是一個(gè)異步事件驅(qū)動(dòng)的網(wǎng)絡(luò)應(yīng)用框架,用于快速開(kāi)發(fā)可維護(hù)的高性能服務(wù)器和客戶端。一般開(kāi)發(fā)中并不會(huì)用JDK原生NIO,原因如下:
使用JDK自帶的NIO需要了解太多的概念,編程復(fù)雜,一不小心bug橫飛
Netty底層IO模型隨意切換,而這一切只需要做微小的改動(dòng),改改參數(shù),Netty可以直接從NIO模型變身為IO模型
Netty自帶的拆包解包,異常檢測(cè)等機(jī)制讓你從NIO的繁重細(xì)節(jié)中脫離出來(lái),讓你只需要關(guān)心業(yè)務(wù)邏輯
Netty解決了JDK的很多包括空輪詢?cè)趦?nèi)的bug
Netty底層對(duì)線程,selector做了很多細(xì)小的優(yōu)化,精心設(shè)計(jì)的reactor線程模型做到非常高效的并發(fā)處理
自帶各種協(xié)議棧讓你處理任何一種通用協(xié)議都幾乎不用親自動(dòng)手
Netty社區(qū)活躍,遇到問(wèn)題隨時(shí)郵件列表或者issue
Netty已經(jīng)歷各大rpc框架,消息中間件,分布式通信中間件線上的廣泛驗(yàn)證,健壯性無(wú)比強(qiáng)大
所以,本文選擇演示Netty的編解碼代碼。
在Netty中,我們定義MessageToByteEncoder<T>的繼承類,重寫(xiě)其encode函數(shù),來(lái)自定義編碼器。
public class SocketEncoder extends MessageToByteEncoder<Packet> { @Override protected void encode(ChannelHandlerContext channelHandlerContext, NetPacket msg, ByteBuf byteBuf) throws Exception { byte body[] = msg.getBody(); int packetLen = body.length; // 先設(shè)置包長(zhǎng)度,然后寫(xiě)入二進(jìn)制數(shù)據(jù) byteBuf.writeInt(packetLen); byteBuf.writeBytes(body); }}
在Netty中,我們定義ByteToMessageDecoder的繼承類,重寫(xiě)其decode函數(shù),用來(lái)自定義解碼器。
public class SocketDecoder extends ByteToMessageDecoder {
@Override
void decode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List<Object> list) throws Exception {
int bufLen = byteBuf.readableBytes();
// 解決粘包問(wèn)題(不夠一個(gè)包頭的長(zhǎng)度)
// 4字節(jié)是報(bào)文中使用了一個(gè)int表示了報(bào)文長(zhǎng)度
if (bufLen < 4) {
return;
}
// 標(biāo)記一下當(dāng)前的readIndex的位置
byteBuf.markReaderIndex();
int packetLength = byteBuf.readInt();
// 讀到的消息體長(zhǎng)度如果小于我們傳送過(guò)來(lái)的消息長(zhǎng)度,則resetReaderIndex。重置讀索引,繼續(xù)接收
if (byteBuf.readableBytes() < packetLength) {
// 配合markReaderIndex使用的。把readIndex重置到mark的地方
byteBuf.resetReaderIndex();
return;
}
NetPacket netPacket = new NetPacket();
netPacket.setPacketLen(packetLength);
// 傳送過(guò)來(lái)數(shù)據(jù)的長(zhǎng)度,滿足我們的要求了
byte body[] = new byte[packetLength];
byteBuf.readBytes(body);
netPacket.setBody(body);
list.add(netPacket);
}
}
看完上述內(nèi)容,你們掌握TCP粘拆包與Netty代碼的示例分析的方法了嗎?如果還想學(xué)到更多技能或想了解更多相關(guān)內(nèi)容,歡迎關(guān)注億速云行業(yè)資訊頻道,感謝各位的閱讀!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。