《精通并发与Netty》学习笔记(13 - 解决TCP粘包拆包(一)概念及实例演示)
一、粘包/拆包概念
TCP是一個“流”協議,所謂流,就是沒有界限的一長串二進制數據。TCP作為傳輸層協議并不不了解上層業務數據的具體含義,它會根據TCP緩沖區的實際情況進行數據包的劃分,所以在業務上認為是一個完整的包,可能會被TCP拆分成多個包進行發送,也有可能把多個小的包封裝成一個大的數據包發送,這就是所謂的TCP粘包和拆包問題。
一般所謂的TCP粘包是在一次接收數據不能完全地體現一個完整的消息數據。TCP通訊為何存在粘包呢?主要原因是TCP是以流的方式來處理數據,再加上網絡上MTU的往往小于在應用處理的消息數據,所以就會引發一次接收的數據無法滿足消息的需要,導致粘包的存在。處理粘包的唯一方法就是制定應用層的數據通訊協議,通過協議來規范現有接收的數據是否滿足消息數據的需要。?
現在假設客戶端向服務端連續發送了兩個數據包,用packet1和packet2來表示,那么服務端收到的數據可以分為三種,現列舉如下:?
第一種情況:
接收端正常收到兩個數據包,即沒有發生拆包和粘包的現象,此種情況不在本文的討論范圍內。
?
第二種情況:
接收端只收到一個數據包,由于TCP是不會出現丟包的,所以這一個數據包中包含了發送端發送的兩個數據包的信息,這種現象即為粘包。這種情況由于接收端不知道這兩個數據包的界限,所以對于接收端來說很難處理。
?
第三種情況:
這種情況有兩種表現形式,如下圖。接收端收到了兩個數據包,但是這兩個數據包要么是不完整的,要么就是多出來一塊,這種情況即發生了拆包和粘包。這兩種情況如果不加特殊處理,對于接收端同樣是不好處理的。
??
? ?
二、粘包問題的解決策略
- 消息定長,報文大小固定長度,不夠空格補全,發送和接收方遵循相同的約定,這樣即使粘包了通過接收方編程實現獲取定長報文也能區分。
- 包尾添加特殊分隔符,例如每條報文結束都添加回車換行符(例如FTP協議)或者指定特殊字符作為報文分隔符,接收方通過特殊分隔符切分報文區分。
- 將消息分為消息頭和消息體,消息頭中包含表示信息的總長度(或者消息體長度)的字段
?三、Netty粘包和拆包解決方案
Netty提供了多個解碼器,可以進行分包的操作,分別是:
LineBasedFrameDecoder
DelimiterBasedFrameDecoder(添加特殊分隔符報文來分包)
FixedLengthFrameDecoder(使用定長的報文來分包)
LengthFieldBasedFrameDecoder
四、TCP粘包和拆包實例演示
首先編寫服務端
package com.spring.netty.handler;import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.ChannelFuture; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.nio.NioServerSocketChannel;public class MyServer {public static void main(String[] args) throws Exception {EventLoopGroup bossGroup = new NioEventLoopGroup(1);EventLoopGroup workerGroup = new NioEventLoopGroup();try {ServerBootstrap serverBootstrap = new ServerBootstrap();serverBootstrap.group(bossGroup,workerGroup).channel(NioServerSocketChannel.class).childHandler(new MyServerInitializer());ChannelFuture channelFuture = serverBootstrap.bind(8899).sync();channelFuture.channel().closeFuture().sync();}finally {bossGroup.shutdownGracefully();workerGroup.shutdownGracefully();}} }?
package com.spring.netty.handler;import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelPipeline; import io.netty.channel.socket.SocketChannel;public class MyServerInitializer extends ChannelInitializer<SocketChannel> {@Overrideprotected void initChannel(SocketChannel ch) throws Exception {ChannelPipeline pipeline = ch.pipeline();pipeline.addLast(new MyServerHandler());} }?
package com.spring.netty.handler;import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.SimpleChannelInboundHandler;import java.nio.charset.Charset; import java.util.UUID;public class MyServerHandler extends SimpleChannelInboundHandler<ByteBuf> {private int count;@Overrideprotected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception {byte[] buffer = new byte[msg.readableBytes()];msg.readBytes(buffer);String message = new String(buffer, Charset.forName("utf-8"));System.out.println("服務端接收到的消息內容:"+message);System.out.println("服務端接收的消息數量:"+(++this.count));ByteBuf responseByteBuf = Unpooled.copiedBuffer(UUID.randomUUID().toString(),Charset.forName("utf-8"));ctx.writeAndFlush(responseByteBuf);}@Overridepublic void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {cause.printStackTrace();ctx.close();} }然后編寫客戶端
package com.spring.netty.handler;import io.netty.bootstrap.Bootstrap; import io.netty.channel.ChannelFuture; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.nio.NioSocketChannel;public class MyClient {public static void main(String[] args) throws Exception {EventLoopGroup eventLoopGroup = new NioEventLoopGroup();try {Bootstrap bootstrap = new Bootstrap();bootstrap.group(eventLoopGroup).channel(NioSocketChannel.class).handler(new MyClientInitializer());ChannelFuture channelFuture = bootstrap.connect("localhost",8899).sync();channelFuture.channel().closeFuture().sync();}finally {eventLoopGroup.shutdownGracefully();}} }?
package com.spring.netty.handler;import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelPipeline; import io.netty.channel.socket.SocketChannel;public class MyClientInitializer extends ChannelInitializer<SocketChannel> {@Overrideprotected void initChannel(SocketChannel ch) throws Exception {ChannelPipeline pipeline = ch.pipeline();pipeline.addLast(new MyClientHandler());} }?
package com.spring.netty.handler;import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.channel.ChannelHandler; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.SimpleChannelInboundHandler; import io.netty.util.concurrent.EventExecutorGroup;import java.nio.charset.Charset;public class MyClientHandler extends SimpleChannelInboundHandler<ByteBuf> {private int count;@Overridepublic void channelActive(ChannelHandlerContext ctx) throws Exception {for(int i=0;i<10;i++){ByteBuf buffer = Unpooled.copiedBuffer("send from client ", Charset.forName("utf-8"));ctx.writeAndFlush(buffer);}}@Overrideprotected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception {byte[] buffer = new byte[msg.readableBytes()];msg.readBytes(buffer);String message = new String(buffer,Charset.forName("utf-8"));System.out.println("客戶端接收到的消息內容:"+message);System.out.println("客戶端接收到的消息數量:"+(++this.count));}@Overridepublic void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {cause.printStackTrace();ctx.close();} }分別運行服務端和客戶端查看運行效果
服務端效果:
客戶端效果:
本節我們介紹了TCP粘包拆包的現象及做了個實例演示,下節我們來介紹在Netty中如何解決粘包拆包問題。
轉載于:https://www.cnblogs.com/happy2010/p/10904121.html
總結
以上是生活随笔為你收集整理的《精通并发与Netty》学习笔记(13 - 解决TCP粘包拆包(一)概念及实例演示)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 产品常用网址
- 下一篇: Vue状态管理之Vuex