Netty之粘包分包
粘包現(xiàn)象
客戶端在一個(gè)for循環(huán)內(nèi)連續(xù)發(fā)送1000個(gè)hello給Netty服務(wù)器端,??
1 Socket socket = new Socket("127.0.0.1", 10101); 2 for(int i = 0; i < 1000; i++){ 3 socket.getOutputStream().write(“hello”.getBytes()); 4 } 5 socket.close();?而在服務(wù)器端接受到的信息并不是預(yù)期的1000個(gè)獨(dú)立的Hello字符串.
實(shí)際上是無(wú)序的hello字符串混合在一起, 如圖所示. 這種現(xiàn)象我們稱之為粘包.
為什么會(huì)出現(xiàn)這種現(xiàn)象呢??TCP是個(gè)”流”協(xié)議,流其實(shí)就是沒(méi)有界限的一串?dāng)?shù)據(jù)。?
TCP底層中并不了解上層業(yè)務(wù)數(shù)據(jù)的具體含義,它會(huì)根據(jù)TCP緩沖區(qū)的實(shí)際情況進(jìn)行包劃分,
所以在TCP中就有可能一個(gè)完整地包會(huì)被TCP拆分成多個(gè)包,也有可能吧多個(gè)小的包封裝成一個(gè)大的數(shù)據(jù)包發(fā)送。
分包處理
顧名思義, 我們要對(duì)傳輸?shù)臄?shù)據(jù)進(jìn)行分包. 一個(gè)簡(jiǎn)單的處理邏輯是在發(fā)送數(shù)據(jù)包之前, 先用四個(gè)字節(jié)占位, 表示數(shù)據(jù)包的長(zhǎng)度.?
數(shù)據(jù)包結(jié)構(gòu)為:
|? ? 長(zhǎng)度(4字節(jié))? ? |? ? 數(shù)據(jù)? ? |
?
1 Socket socket = new Socket("127.0.0.1", 10101); 2 String message = "hello"; 3 byte[] bytes = message.getBytes(); 4 ByteBuffer buffer = ByteBuffer.allocate(4 + bytes.length); 5 // 消息長(zhǎng)度 6 buffer.putInt(bytes.length); 7 // 消息正文 8 buffer.put(bytes); 9 byte[] array = buffer.array(); 10 for(int i = 0; i < 1000; i++){ 11 socket.getOutputStream().write(array); 12 } 13 socket.close();?
服務(wù)器端代碼, 我們需要借助于FrameDecoder類來(lái)分包.
1 public class MyDecoder extends FrameDecoder { 2 3 @Override 4 protected Object decode(ChannelHandlerContext ctx, Channel channel, ChannelBuffer buffer) throws Exception { 5 6 if(buffer.readableBytes() > 4){ 7 //標(biāo)記 8 buffer.markReaderIndex(); 9 //長(zhǎng)度 10 int length = buffer.readInt(); 11 12 if(buffer.readableBytes() < length){ 13 buffer.resetReaderIndex(); 14 //緩存當(dāng)前剩余的buffer數(shù)據(jù),等待剩下數(shù)據(jù)包到來(lái) 15 return null; 16 } 17 18 //讀數(shù)據(jù) 19 byte[] bytes = new byte[length]; 20 buffer.readBytes(bytes); 21 //往下傳遞對(duì)象 22 return new String(bytes); 23 } 24 //緩存當(dāng)前剩余的buffer數(shù)據(jù),等待剩下數(shù)據(jù)包到來(lái) 25 return null; 26 } 27 28 }?
如此一來(lái), 我們?cè)俅卧诜?wù)器端接受到的消息就是按序打印的hello了.
這邊可能有個(gè)疑問(wèn), 為什么MyDecoder中數(shù)據(jù)沒(méi)有讀取完畢, 需要return null,
正常的pipeline在數(shù)據(jù)處理完都是要sendUpstream, 給下一個(gè)pipeline的.
這個(gè)需要看下FrameDecoder.messageReceived?的源碼. 他在其中緩存了一個(gè)cumulation對(duì)象,?
如果return了null, 他會(huì)繼續(xù)往緩存里寫數(shù)據(jù)來(lái)實(shí)現(xiàn)分包
1 public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception { 2 Object m = e.getMessage(); 3 if (!(m instanceof ChannelBuffer)) { 4 // 數(shù)據(jù)讀完了, 轉(zhuǎn)下一個(gè)pipeline 5 ctx.sendUpstream(e); 6 } else { 7 ChannelBuffer input = (ChannelBuffer)m; 8 if (input.readable()) { 9 if (this.cumulation == null) { 10 try { 11 this.callDecode(ctx, e.getChannel(), input, e.getRemoteAddress()); 12 } finally { 13 this.updateCumulation(ctx, input); 14 } 15 } else { 16 // 緩存上一次沒(méi)讀完整的數(shù)據(jù) 17 input = this.appendToCumulation(input); 18 19 try { 20 this.callDecode(ctx, e.getChannel(), input, e.getRemoteAddress()); 21 } finally { 22 this.updateCumulation(ctx, input); 23 } 24 } 25 26 } 27 } 28 }?
那么是不是這樣就萬(wàn)事大吉了呢?
Socket字節(jié)流攻擊
在上述代碼中, 我們會(huì)在服務(wù)器端為客戶端發(fā)送的數(shù)據(jù)包長(zhǎng)度, 預(yù)先分配byte數(shù)組.
如果遇到惡意攻擊, 傳入的數(shù)據(jù)長(zhǎng)度與內(nèi)容 不匹配. 例如聲明數(shù)據(jù)長(zhǎng)度為Integer.MAX_VALUE.
這樣會(huì)消耗大量的服務(wù)器資源生成byte[], 顯然是不合理的.
因此我們還要加個(gè)最大長(zhǎng)度限制.
1 if(buffer.readableBytes() > 2048){ 2 buffer.skipBytes(buffer.readableBytes()); 3 }新的麻煩也隨之而來(lái), 雖然可以跳過(guò)指定長(zhǎng)度, 但是數(shù)據(jù)包本身就亂掉了.
因?yàn)殚L(zhǎng)度和內(nèi)容不匹配, 跳過(guò)一個(gè)長(zhǎng)度后, 不知道下一段數(shù)據(jù)的開(kāi)頭在哪里了.
因此我們自定義數(shù)據(jù)包里面, 不僅要引入數(shù)據(jù)包長(zhǎng)度, 還要引入一個(gè)包頭來(lái)劃分各個(gè)包的范圍.
包頭用任意一段特殊字符標(biāo)記即可, 例如$$$.
1 // 防止socket字節(jié)流攻擊 2 if(buffer.readableBytes() > 2048){ 3 buffer.skipBytes(buffer.readableBytes()); 4 } 5 // 記錄包頭開(kāi)始的index 6 int beginReader = buffer.readerIndex(); 7 8 while(true) { 9 if(buffer.readInt() == ConstantValue.FLAG) { 10 break; 11 } 12 }?
新的數(shù)據(jù)包結(jié)構(gòu)為:
|? ? 包頭(4字節(jié))? ? |? ? 長(zhǎng)度(4字節(jié))? ? |? ? 數(shù)據(jù)? ? |
Netty自帶拆包類
自己實(shí)現(xiàn)拆包雖然可以細(xì)粒度控制, 但是也會(huì)有些不方便, 可以直接調(diào)用Netty提供的一些內(nèi)置拆包類.
- FixedLengthFrameDecoder?按照特定長(zhǎng)度組包
- DelimiterBasedFrameDecoder 按照指定分隔符組包, 例如本文中的$$$
- LineBasedFrameDecoder?按照換行符進(jìn)行組包, \r \n等等
- ......
轉(zhuǎn)載于:https://www.cnblogs.com/xdecode/p/7913509.html
總結(jié)
以上是生活随笔為你收集整理的Netty之粘包分包的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: Java 成员变量和局部变量
- 下一篇: 【bzoj 3173】[Tjoi2013