Netty实践(二):TCP拆包、粘包问题

简介:

什么是TCP拆包、粘包?

在网络通信中,数据在底层都是以字节流形式在流动,那么发送方和接受方理应有一个约定(协议),只有这样接受方才知道需要接受多少数据,哪些数据需要在一起处理;如果没有这个约定,就会出现本应该一起处理的数据,被TCP划分为多个包发给接收方进行处理,如下图:

wKiom1hyNFiS8DVlAAAzdREUGHM079.png




看一个TCP拆包、粘包的实例

客户端Handler:

wKioL1hyOHHCXVUFAABCJ7hliZE421.png


服务端Handler:

wKiom1hyOLjzpcpNAABcEHVaL9U858.png

运行结果:

wKioL1hyOO2hu7BeAAAakzcyQBA400.png

上面的程序本意是CLIENT发送3次消息给SERVER,SERVER端理应处理3次,可是结果SERVER却将3条消息一次处理了。


那么如何解决TCP拆包、粘包问题呢?其实思路不外乎有3种:

第一种:发定长数据

接收方拿固定长度的数据,发送方发送固定长度的数据即可。但是这样的缺点也是显而易见的:如果发送方的数据长度不足,需要补位,浪费空间。

第二种:在包尾部增加特殊字符进行分割

发送方发送数据时,增加特殊字符;在接收方以特殊字符为准进行分割

第三种:自定义协议

类似于HTTP协议中的HEAD信息,比如我们也可以在HEAD中,告诉接收方数据的元信息(数据类型、数据长度等)



Netty如何解决TCP拆包、粘包问题?

《Java通信实战:编写自定义通信协议实现FTP服务》中,涉及到了JAVA SOCKET这方面的处理,大家可以参考。接下来,我们来看Netty这个框架是如何帮助我们解决这个问题的。本篇博客的代码在《Netty实践(一):轻松入门》基础上进行。


方式一:定长消息

Server启动类:

wKiom1hyOyuijnRKAAA7dsHZoPw259.png


Client Handler:

wKioL1hyO1yxWMODAAA5nDq5z_Y711.png


运行结果:

wKiom1hyPnfygbyyAAAg2qklApM237.pngwKiom1hyPoGxkhVxAAAR_4YedkE745.png

利用FixedLengthFrameDecoder,加入到管道流处理中,长度够了接收方才能收到。



方式二:自定义分隔符

Server启动类:

wKiom1hzioeA7jlPAABKa74vH_8193.png


Client Handler:

wKioL1hzitzSTj-EAABBINFILFU201.png


运行结果:

wKioL1hziw_yhakhAAAdzh0MBAk277.png

wKioL1hzizbSHyUFAAAbdBWJdq8033.png


方式三:自定义协议

下面我们将简单实现一个自定义协议:

HEAD信息中包含:数据长度、数据版本

数据内容


MyHead

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public  class  MyHead {
 
     //数据长度
     private  int  length;
 
     //数据版本
     private  int  version;
 
 
     public  MyHead( int  length,  int  version) {
         this .length = length;
         this .version = version;
     }
 
     public  int  getLength() {
         return  length;
     }
 
     public  void  setLength( int  length) {
         this .length = length;
     }
 
     public  int  getVersion() {
         return  version;
     }
 
     public  void  setVersion( int  version) {
         this .version = version;
     }
 
 
}


MyMessage

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public  class  MyMessage {
     //消息head
     private  MyHead head;
     //消息body
     private  String content;
 
     public  MyMessage(MyHead head, String content) {
         this .head = head;
         this .content = content;
     }
 
     public  MyHead getHead() {
         return  head;
     }
 
     public  void  setHead(MyHead head) {
         this .head = head;
     }
 
     public  String getContent() {
         return  content;
     }
 
     public  void  setContent(String content) {
         this .content = content;
     }
 
     @Override
     public  String toString() {
         return  String.format( "[length=%d,version=%d,content=%s]" ,head.getLength(),head.getVersion(),content);
     }
}


编码器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
  * Created by Administrator on 17-1-9.
  * 编码器 将自定义消息转化成ByteBuff
  */
public  class  MyEncoder  extends  MessageToByteEncoder<MyMessage> {
 
     @Override
     protected  void  encode(ChannelHandlerContext channelHandlerContext, MyMessage myMessage, ByteBuf byteBuf)  throws  Exception {
 
         int  length = myMessage.getHead().getLength();
         int  version = myMessage.getHead().getVersion();
         String content = myMessage.getContent();
 
         byteBuf.writeInt(length);
         byteBuf.writeInt(version);
         byteBuf.writeBytes(content.getBytes(Charset.forName( "UTF-8" )));
 
     }
}


解码器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
  * Created by Administrator on 17-1-9.
  * 解码器  将ByteBuf数据转化成自定义消息
  */
public  class  MyDecoder  extends  ByteToMessageDecoder {
 
     @Override
     protected  void  decode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List<Object> list)  throws  Exception {
 
         int  length = byteBuf.readInt();
         int  version = byteBuf.readInt();
 
         byte [] body =  new  byte [length];
         byteBuf.readBytes(body);
 
         String content =  new  String(body, Charset.forName( "UTF-8" ));
 
         MyMessage myMessage =  new  MyMessage( new  MyHead(length,version),content);
 
         list.add(myMessage);
     }
}


Server启动类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
public  class  Main {
 
     public  static  void  main(String[] args) {
 
         EventLoopGroup bossGroup =  new  NioEventLoopGroup();  // (1)
         EventLoopGroup workerGroup =  new  NioEventLoopGroup();  // (2)
         int  port =  8867 ;
         try  {
             ServerBootstrap b =  new  ServerBootstrap();  // (3)
             b.group(bossGroup, workerGroup)
                     .channel(NioServerSocketChannel. class // (4)
                     .childHandler( new  ChannelInitializer<SocketChannel>() {  // (5)
                 @Override
                 public  void  initChannel(SocketChannel ch)  throws  Exception {
 
                     ch.pipeline().addLast( new  MyEncoder())
                     .addLast( new  MyDecoder())
                     .addLast( new  ServerHandler());
                 }
             })
             .option(ChannelOption.SO_BACKLOG,  128 )           // (6)
             .childOption(ChannelOption.SO_KEEPALIVE,  true );  // (7)
 
             // Bind and start to accept incoming connections.
             ChannelFuture f = b.bind(port).sync();  // (8)
 
             // Wait until the server socket is closed.
             // In this example, this does not happen, but you can do that to gracefully
             // shut down your server.
             System.out.println( "start server...." );
             f.channel().closeFuture().sync();
             System.out.println( "stop server...." );
         catch  (InterruptedException e) {
             e.printStackTrace();
         finally  {
             workerGroup.shutdownGracefully();
             bossGroup.shutdownGracefully();
             System.out.println( "exit server...." );
         }
 
     }
}


Server Handler

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public  class  ServerHandler   extends  ChannelHandlerAdapter {
 
     //每当从客户端收到新的数据时,这个方法会在收到消息时被调用
     @Override
     public  void  channelRead(ChannelHandlerContext ctx, Object msg)  throws  Exception {
 
         MyMessage in = (MyMessage) msg;
         try  {
             // Do something with msg
             System.out.println( "server get :"  + in);
 
         finally  {
             //ByteBuf是一个引用计数对象,这个对象必须显示地调用release()方法来释放
             //or ((ByteBuf)msg).release();
             ReferenceCountUtil.release(msg);
         }
 
     }
 
     //exceptionCaught()事件处理方法是当出现Throwable对象才会被调用
     //当Netty由于IO错误或者处理器在处理事件时抛出的异常时
     @Override
     public  void  exceptionCaught(ChannelHandlerContext ctx, Throwable cause)  throws  Exception {
 
         // Close the connection when an exception is raised.
         cause.printStackTrace();
         ctx.close();
 
     }
 
}


Client启动类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public  class  Client {
 
     public  static  void  main(String[] args) {
 
         EventLoopGroup group =  new  NioEventLoopGroup();
 
         try  {
               Bootstrap b =  new  Bootstrap();
               b.group(group)
                .channel(NioSocketChannel. class )
                .handler( new  ChannelInitializer<SocketChannel>() {
                    @Override
                    public  void  initChannel(SocketChannel ch)  throws  Exception {
                        ChannelPipeline p = ch.pipeline();
                        p.addLast( new  MyDecoder());
                        p.addLast( new  MyEncoder());
                        p.addLast( new  ClientHandler());
                    }
                });
 
               // Start the client.
               ChannelFuture f = b.connect( "127.0.0.1" 8867 ).sync();
 
               // Wait until the connection is closed.
               f.channel().closeFuture().sync();
 
           catch  (InterruptedException e) {
             e.printStackTrace();
         finally  {
               // Shut down the event loop to terminate all threads.
               group.shutdownGracefully();
           }
 
     }
 
}


Client Handler

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public  class  ClientHandler  extends  ChannelHandlerAdapter {
 
     @Override
     public  void  channelActive(ChannelHandlerContext ctx)  throws  Exception {
 
         ctx.writeAndFlush( new  MyMessage( new  MyHead( "abcd" .getBytes( "UTF-8" ).length, 1 ), "abcd" ));
 
     }
 
     @Override
     public  void  channelRead(ChannelHandlerContext ctx, Object msg)  throws  Exception {
 
         ByteBuf in = (ByteBuf) msg;
         try  {
             // Do something with msg
             System.out.println( "client get :"  + in.toString(CharsetUtil.UTF_8));
 
             ctx.close();
         finally  {
             //ByteBuf是一个引用计数对象,这个对象必须显示地调用release()方法来释放
             //or ((ByteBuf)msg).release();
             ReferenceCountUtil.release(msg);
         }
     }
}


运行结果

wKioL1hzlXeC1dvnAAAa_zDNVxs631.png


到这里,你会发现Netty处理TCP拆包、粘包问题很简单,通过编解码技术支持,让我们编写自定义协议也很方便,在后续的Netty博客中,我将继续为大家介绍Netty在实际中的一些应用(比如实现心跳检测),See You~


本文转自zfz_linux_boy 51CTO博客,原文链接:http://blog.51cto.com/zhangfengzhe/1890577,如需转载请自行联系原作者

相关文章
|
网络协议 前端开发
netty的TCP服务端和客户端实现
本文介绍了使用Netty框架实现TCP服务端和客户端的步骤,包括添加Netty依赖、编写服务端和客户端的代码,涉及NioEventLoopGroup、ServerBootstrap、Bootstrap、ChannelInitializer等核心组件,以及如何启动服务端监听和客户端连接。
897 4
|
编解码 网络协议 开发者
Netty运行原理问题之NettyTCP的粘包和拆包的问题如何解决
Netty运行原理问题之NettyTCP的粘包和拆包的问题如何解决
129 1
|
移动开发 网络协议 算法
(十)Netty进阶篇:漫谈网络粘包、半包问题、解码器与长连接、心跳机制实战
在前面关于《Netty入门篇》的文章中,咱们已经初步对Netty这个著名的网络框架有了认知,本章的目的则是承接上文,再对Netty中的一些进阶知识进行阐述,毕竟前面的内容中,仅阐述了一些Netty的核心组件,想要真正掌握Netty框架,对于它我们应该具备更为全面的认知。
786 2
|
消息中间件 存储 网络协议
拼多多面试:Netty如何解决粘包问题?
粘包和拆包问题也叫做粘包和半包问题,**它是指在数据传输时,接收方未能正常读取到一条完整数据的情况(只读取了部分数据,或多读取到了另一条数据的情况)就叫做粘包或拆包问题。** 从严格意义上来说,粘包问题和拆包问题属于两个不同的问题,接下来我们分别来看。 ## 1.粘包问题 粘包问题是指在网络通信中,发送方连续发送的多个小数据包被接收方一次性接收的现象。这可能是因为底层传输层协议(如 TCP)会将多个小数据包合并成一个大的数据块进行传输,导致接收方在接收数据时一次性接收了多个数据包,造成粘连。 例如以下案例,正常情况下客户端发送了两条消息,分别为“ABC”和“DEF”,那么接收端也应该收到两
140 0
拼多多面试:Netty如何解决粘包问题?
|
网络协议
netty粘包问题分析
netty粘包问题分析
165 0
|
Java
Netty传输object并解决粘包拆包问题
Netty传输object并解决粘包拆包问题
178 0
|
Java
Netty中粘包拆包问题解决探讨
Netty中粘包拆包问题解决探讨
158 0
|
机器学习/深度学习 人工智能 网络协议
TCP/IP五层(或四层)模型,IP和TCP到底在哪层?
TCP/IP五层(或四层)模型,IP和TCP到底在哪层?
378 4
|
域名解析 网络协议
IP协议, TCP协议 和DNS 服务分别是干什么的?
大家好,我是阿萨。昨天讲解了网络四层协议[TCP/IP协议族分为哪4层?]今天我们学习下IP 协议, TCP 协议和DNS 协议分别是干什么的。
481 0
IP协议, TCP协议 和DNS 服务分别是干什么的?
|
网络协议
ACK的累加规则-wireshark抓包分析-不包含tcp头部、ip头部、数据链路层头部等。
ACK的累加规则-wireshark抓包分析-不包含tcp头部、ip头部、数据链路层头部等。
ACK的累加规则-wireshark抓包分析-不包含tcp头部、ip头部、数据链路层头部等。

热门文章

最新文章