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