netty 解決TCP粘包與拆包問題(一)


1.什么是TCP粘包與拆包

首先TCP是一個"流"協議,猶如河中水一樣連成一片,沒有嚴格的分界線。當我們在發送數據的時候就會出現多發送與少發送問題,也就是TCP粘包與拆包。得不到我們想要的效果。

所謂粘包:當你把A,B兩個數據從甲發送到乙,本想A與B單獨發送,但是你卻把AB一起發送了,此時AB粘在一起,就是粘包了

所謂拆包: 如果發送數據的時候,你把A、B拆成了幾份發,就是拆包了。當然數據不是你主動拆的,是TCP流自動拆的

 

2.TCP粘包與拆包產生原因

1.進行了MSS大小的TCP分段
2.以太網幀的plyload大與MTU進行了IP分片
3.應用程序write寫入的字節大小大於套接口發送的緩沖區大小

 

3.解決方法

1.消息定長,比如把報文消息固定為500字節,不夠用空格補位

2.在包尾增加回車換行符進行分割,例如FTP協議

3.將消息分為消息頭和消息體,消息頭中包含表示消息總長度的字段

4.更復雜的應用層協議

 

4.netty 普通解決方法

 

這個是服務端代碼

復制代碼
 1 package com.ming.netty.nio;  2  3 import java.net.InetSocketAddress;  4  5 import io.netty.bootstrap.ServerBootstrap;  6 import io.netty.channel.ChannelFuture;  7 import io.netty.channel.ChannelInitializer;  8 import io.netty.channel.ChannelOption;  9 import io.netty.channel.EventLoopGroup; 10 import io.netty.channel.nio.NioEventLoopGroup; 11 import io.netty.channel.socket.SocketChannel; 12 import io.netty.channel.socket.nio.NioServerSocketChannel; 13 import io.netty.handler.codec.LineBasedFrameDecoder; 14 import io.netty.handler.codec.string.StringDecoder; 15 16 public class TimeServer { 17 18 19 public static void main(String[] args) throws Exception{ 20 new TimeServer().bind("192.168.1.102", 8400); 21  } 22 23 24 public void bind(String addr,int port) { 25 //配置服務端的nio線程組 26 EventLoopGroup boosGroup=new NioEventLoopGroup(); 27 EventLoopGroup workerGroup=new NioEventLoopGroup(); 28 try { 29 ServerBootstrap b=new ServerBootstrap(); 30  b.group(boosGroup,workerGroup); 31 b.channel(NioServerSocketChannel.class).option(ChannelOption.SO_BACKLOG,1024) 32 .childHandler(new ChildChannelHandler()); 33 //綁定端口,同步等待成功 34 ChannelFuture f=b.bind(new InetSocketAddress(addr, port)).sync(); 35 System.out.println("啟動服務器:"+f.channel().localAddress()); 36 //等等服務器端監聽端口關閉 37  f.channel().closeFuture().sync(); 38 } catch (Exception e) { 39 // TODO: handle exception 40 }finally{ 41  boosGroup.shutdownGracefully(); 42  workerGroup.shutdownGracefully(); 43  } 44  } 45 46 private class ChildChannelHandler extends ChannelInitializer<SocketChannel>{ 47 48  @Override 49 protected void initChannel(SocketChannel ch) throws Exception { 50  System.out.println(ch.remoteAddress()); 51 ch.pipeline().addLast(new LineBasedFrameDecoder(1024)); 52 ch.pipeline().addLast(new StringDecoder());//增加解碼器 53 ch.pipeline().addLast(new TimeServerHandler()); 54 55  } 56 57  } 58 59 60 } 61 package com.ming.netty.nio; 62 63 import java.nio.ByteBuffer; 64 import java.text.SimpleDateFormat; 65 import java.util.Date; 66 67 import io.netty.buffer.ByteBuf; 68 import io.netty.buffer.Unpooled; 69 import io.netty.channel.ChannelHandlerAdapter; 70 import io.netty.channel.ChannelHandlerContext; 71 72 public class TimeServerHandler extends ChannelHandlerAdapter { 73 74 private int counter; 75 76  @Override 77 public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { 78 String body=(String)msg; 79 System.out.println("服務端收到:"+body+",次數:"+ ++counter); 80 SimpleDateFormat dateFormat=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); 81 String time=dateFormat.format(new Date()); 82 String res="來自與服務端的回應,時間:"+ time; 83 ByteBuf resp=Unpooled.copiedBuffer(res.getBytes()); 84 ctx.writeAndFlush(resp); 85 86 } 87 88 89 90 @Override 91 public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { 92 ctx.close(); 93 } 94 95 96 97 98 }
復制代碼

 

這個是客服端的代碼

復制代碼
 1 package com.ming.netty.nio;  2  3 import io.netty.bootstrap.Bootstrap;  4 import io.netty.channel.ChannelFuture;  5 import io.netty.channel.ChannelInitializer;  6 import io.netty.channel.ChannelOption;  7 import io.netty.channel.EventLoopGroup;  8 import io.netty.channel.nio.NioEventLoopGroup;  9 import io.netty.channel.socket.SocketChannel; 10 import io.netty.channel.socket.nio.NioSocketChannel; 11 import io.netty.handler.codec.LineBasedFrameDecoder; 12 import io.netty.handler.codec.string.StringDecoder; 13 14 /** 15  * netty 客戶端模擬 16  * @author mingge 17  * 18 */ 19 public class TimeClient { 20 21 22 public static void main(String[] args) throws Exception{ 23 new TimeClient().connect("192.168.1.102", 8400); 24  } 25 26 public void connect(String addr,int port) throws Exception{ 27 EventLoopGroup group=new NioEventLoopGroup(); 28 try { 29 Bootstrap b=new Bootstrap(); 30 b.group(group).channel(NioSocketChannel.class) 31 .option(ChannelOption.TCP_NODELAY, true) 32 .handler(new ChannelInitializer<SocketChannel>() { 33 public void initChannel(SocketChannel ch) throws Exception{ 34 ch.pipeline().addLast(new LineBasedFrameDecoder(1024)); 35 ch.pipeline().addLast(new StringDecoder()); 36 ch.pipeline().addLast(new TimeClientHandler()); 37  } 38  }); 39 ChannelFuture f=b.connect(addr,port).sync(); 40 System.out.println("連接服務器:"+f.channel().remoteAddress()+",本地地址:"+f.channel().localAddress()); 41 f.channel().closeFuture().sync();//等待客戶端關閉連接 42 } catch (Exception e) { 43  e.printStackTrace(); 44 }finally{ 45 46  group.shutdownGracefully(); 47  } 48  } 49 } 50 package com.ming.netty.nio; 51 52 import io.netty.buffer.ByteBuf; 53 import io.netty.buffer.Unpooled; 54 import io.netty.channel.ChannelHandlerAdapter; 55 import io.netty.channel.ChannelHandlerContext; 56 57 public class TimeClientHandler extends ChannelHandlerAdapter { 58 59 private int counter; 60 61 byte[] req; 62 63 public TimeClientHandler() { 64 req=("我是請求數據哦"+System.getProperty("line.separator")).getBytes(); 65  } 66 67  @Override 68 public void channelActive(ChannelHandlerContext ctx) throws Exception { 69 ByteBuf message=null; 70 for(int i=0;i<100;i++){ 71 message=Unpooled.buffer(req.length); 72  message.writeBytes(req); 73  ctx.writeAndFlush(message); 74  } 75 76  } 77 78 @Override 79 public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { 80 ByteBuf buf=(ByteBuf)msg; 81 byte[] req=new byte[buf.readableBytes()]; 82 buf.readBytes(req); 83 String body=new String(req,"GBK"); 84 System.out.println("body:"+body+",響應次數:"+(++counter)); 85 } 86 87 @Override 88 public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { 89 //釋放資源 90 ctx.close(); 91 } 92 93 94 }
復制代碼

 

這次代碼就是比上次的代碼多了:LineBasedFrameDecoder,與StringDecoder的寫法.

LineBasedFrameDecoder的原理是它依次遍歷ByteBuf中的可讀字節,判斷是否有"\n"或"\r\n",如果有就以此為結束。它是以換行符為結束標志的解碼器

StringDecoder的原理就是將接收到的對象轉換為字符串,然后接着調用后面的handler。

LineBasedFrameDecoder+StringDecoder組合就是設計按行切換的文本解碼器,被設計來支持TCP的粘包與拆包


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM