前言

上一篇我们介绍了如果使用Netty来开发一个简单的服务端和客户端,接下来我们来讨论如何使用解码器来解决TCP的粘包和拆包问题

TCP为什么会粘包/拆包

我们知道,TCP是以一种流的方式来进行网络转播的,当tcp三次握手简历通信后,客户端服务端之间就建立了一种通讯管道,我们可以想象成自来水管道,流出来的水是连城一片的,是没有分界线的。

TCP底层并不了解上层的业务数据的具体含义,它会根据TCP缓冲区的实际情况进行包的划分。

所以对于我们应用层而言。我们直观是发送一个个连续完整TCP数据包的,而在底层就可能会出现将一个完整的TCP拆分成多个包发送或者将多个包封装成一个大的数据包发送。

这就是所谓的TCP粘包和拆包。

当发生TCP粘包/拆包会发生什么情况

我们举一个简单例子说明:

客户端向服务端发送两个数据包:第一个内容为 123;第二个内容为456。服务端接受一个数据并做相应的业务处理(这里就是打印接受数据加一个逗号)。

那么服务端输出结果将会出现下面四种情况

服务端响应结果 结论
123,456, 正常接收,没有发生粘包和拆包
123456, 异常接收,发生tcp粘包
123,4,56, 异常接收,发生tcp拆包
12,3456, 异常接收,发生tcp拆包和粘包

如何解决

主流的协议解决方案可以归纳如下:

  1. 消息定长,例如每个报文的大小固定为20个字节,如果不够,空位补空格;
  2. 在包尾增加回车换行符进行切割;
  3. 将消息分为消息头和消息体,消息头中包含表示消息总长度的字段;
  4. 更复杂的应用层协议。

对于之前描述的案例,在这里我们就可以采取方案1和方案3。

以方案1为例:我们每次发送的TCP包只有三个数字,那么我将报文设置为3个字节大小的,此时,服务器就会以三个字节为基准来接受包,以此来解决站包拆包问题。

Netty的解决之道

LineBasedFrameDecoder

废话不多说直接上代码

服务端

public class PrintServer {public void bind(int port) throws Exception {// 配置服务端的NIO线程组EventLoopGroup bossGroup = new NioEventLoopGroup();EventLoopGroup workerGroup = new NioEventLoopGroup();try {ServerBootstrap b = new ServerBootstrap();b.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class).option(ChannelOption.SO_BACKLOG, 1024).childHandler(new ChildChannelHandler());// 绑定端口,同步等待成功ChannelFuture f = b.bind(port).sync();// 等待服务端监听端口关闭f.channel().closeFuture().sync();} finally {// 优雅退出,释放线程池资源bossGroup.shutdownGracefully();workerGroup.shutdownGracefully();}}private class ChildChannelHandler extends ChannelInitializer<SocketChannel> {@Overrideprotected void initChannel(SocketChannel arg0) throws Exception {arg0.pipeline().addLast(new LineBasedFrameDecoder(1024));   //1arg0.pipeline().addLast(new StringDecoder());               //2arg0.pipeline().addLast(new PrintServerHandler());}}public static void main(String[] args) throws Exception {int port = 8080;new TimeServer().bind(port);}
}

服务端Handler

public class PrintServerHandler extends ChannelHandlerAdapter {@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg)throws Exception {ByteBuf buf = (ByteBuf) msg;                                        byte[] req = new byte[buf.readableBytes()]; buf.readBytes(req); //将缓存区的字节数组复制到新建的req数组中String body = new String(req, "UTF-8");System.out.println(body);String response= "打印成功";ByteBuf resp = Unpooled.copiedBuffer(response.getBytes());                      ctx.write(resp);                                                }   @Overridepublic void channelReadComplete(ChannelHandlerContext ctx) throws Exception {ctx.flush();                                                        }@Overridepublic void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {ctx.close();}
}

客户端

public class PrintClient {public void connect(int port, String host) throws Exception {EventLoopGroup group = new NioEventLoopGroup();                 try {Bootstrap b = new Bootstrap();                              b.group(group)                                             .channel(NioSocketChannel.class)                        .option(ChannelOption.TCP_NODELAY, true)                .handler(new ChannelInitializer<SocketChannel>() {      @Overridepublic void initChannel(SocketChannel ch)               throws Exception {ch.pipeline().addLast(new LineBasedFrameDecoder(1024));           //3ch.pipeline().addLast(new StringDecoder());     //4ch.pipeline().addLast(new PrintClientHandler());}});ChannelFuture f = b.connect(host, port).sync();             f.channel().closeFuture().sync();                           } finally {// 优雅退出,释放NIO线程组group.shutdownGracefully();}}/*** @param args* @throws Exception*/public static void main(String[] args) throws Exception {int port = 8080;new TimeClient().connect(port, "127.0.0.1");}
}

客户端的Handler

public class PrintClientHandler extends ChannelHandlerAdapter {private static final Logger logger = Logger.getLogger(TimeClientHandler.class.getName());private final ByteBuf firstMessage;/*** Creates a client-side handler.*/public TimeClientHandler() {byte[] req = "你好服务端".getBytes();firstMessage = Unpooled.buffer(req.length);                               firstMessage.writeBytes(req);}@Overridepublic void channelActive(ChannelHandlerContext ctx) {ctx.writeAndFlush(firstMessage);                                                }@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg)            throws Exception {ByteBuf buf = (ByteBuf) msg;    byte[] req = new byte[buf.readableBytes()];buf.readBytes(req);String body = new String(req, "UTF-8");System.out.println("服务端回应消息 : " + body);}@Overridepublic void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {  // 释放资源System.out.println("Unexpected exception from downstream : "+ cause.getMessage());ctx.close();}
}

上诉代码逻辑与上一章代码逻辑相同,客户端接受服务端数据答应,并回复客户端信息,客户端接受到数据后打印数据。

我们观察代码可以发现,要想Netty解决粘包拆包问题,只需在编写服务端和客户端的pipeline上加上相应的解码器即可,上诉注释 1,2,3,4处。其余代码无需做任何修改。

LineBasedFrameDecoder+StringDecoder的组合就是按行切换的文本解码器,它被设计用来支持TCP的粘包和拆包。原理为:如果连续读取到最大长度后任然没有发现换行符,就会抛出异常,同时忽略掉之前督导的异常码流。

DelimiteBasedFrameDecoder

该解码器的可以自动完成以分割符作为码流结束标识的消息解码。(其实上一个解码器类似,如果指定分隔符为换行符,那么与上一个编码器的作用基本相同)

使用也很简单:

只需要修改服务端和客户端对应代码中的initChannel代码即可

            public void initChannel(SocketChannel ch)               ByteBuf delimiter = Unpooled.copiedBuffer("_".getBytes()); //1ch.pipeline().addLast(new DelimiterBasedFrameDecoder(1024,delimiter));                                       //2ch.pipeline().addLast(new StringDecoder());                 //3ch.pipeline().addLast(new PrintHandler());}

注释1:首先创建分隔符缓冲对象ByteBuf,并指定以"_"作为分隔符。

注释2:将分隔符缓冲对象ByteBuf传入DelimiterBasedFrameDecoder,并指定最大长度。

注释3:指定为字符串字节流

FixedLengthFrameDecoder

该解码器为固定长度解码器,它能够按照指定的长度对详细进行自动解码。

使用同样也很简单:

同样只需要修改服务端和客户端对应代码中的initChannel代码即可

    public void initChannel(SocketChannel ch)throws Exception {ch.pipeline().addLast(new FixedLengthFrameDecoder(20));ch.pipeline().addLast(new StringDecoder());ch.pipeline().addLast(new PrintHandler());}});

这样我们就指定了,每接收20个字符大小的字符串字节流就将其看作一个包来经行处理。

总结

Netty已经在底层为我们做了很多事情,我们只需要简单的使用其提供好的解码器使用即可,源码内容待我研究归来,再进行展开,哈哈,完活~睡觉!

转载于:https://www.cnblogs.com/zhxiansheng/p/10880196.html

Netty入门系列(2) --使用Netty解决粘包和拆包问题相关推荐

  1. netty 高低位转码_Netty解决粘包和拆包问题的四种方案

    在RPC框架中,粘包和拆包问题是必须解决一个问题,因为RPC框架中,各个微服务相互之间都是维系了一个TCP长连接,比如dubbo就是一个全双工的长连接.由于微服务往对方发送信息的时候,所有的请求都是使 ...

  2. 【Netty】Netty解决粘包和拆包问题的四种方案

    在RPC框架中,粘包和拆包问题是必须解决一个问题,因为RPC框架中,各个微服务相互之间都是维系了一个TCP长连接,比如dubbo就是一个全双工的长连接.由于微服务往对方发送信息的时候,所有的请求都是使 ...

  3. Netty 解决粘包和拆包问题的四种方案

    点击上方蓝色"程序猿DD",选择"设为星标" 回复"资源"获取独家整理的学习资料! 来源 | https://my.oschina.net/ ...

  4. Netty解决粘包和拆包问题的四种方案

    在RPC框架中,粘包和拆包问题是必须解决一个问题,因为RPC框架中,各个微服务相互之间都是维系了一个TCP长连接,比如dubbo就是一个全双工的长连接.由于微服务往对方发送信息的时候,所有的请求都是使 ...

  5. Netty是如何解决粘包和拆包问题的

    本文来说下Netty是如何解决粘包和拆包问题的 文章目录 概述 粘包和拆包 常见解决方案 Netty提供的粘包拆包解决方案 FixedLengthFrameDecoder LineBasedFrame ...

  6. Netty入门系列(1) --使用Netty搭建服务端和客户端

    引言 前面我们介绍了网络一些基本的概念,虽然说这些很难吧,但是至少要做到理解吧.有了之前的基础,我们来正式揭开Netty这神秘的面纱就会简单很多. 服务端 public class PrintServ ...

  7. 什么是粘包和拆包,Netty如何解决粘包拆包?

    Netty粘包拆包 TCP 粘包拆包是指发送方发送的若干包数据到接收方接收时粘成一包或某个数据包被拆开接收. 如下图所示,client 发送了两个数据包 D1 和 D2,但是 server 端可能会收 ...

  8. Netty 框架学习(二):Netty粘包和拆包

    文章目录 一.什么是粘包和拆包 二.粘包和拆包示例代码 1.TimeServerHandler 2.TimeClientHandler 三.使用Netty解决粘包和拆包 1.TimeServerHan ...

  9. 【Netty】TCP粘包和拆包

    一.前言 前面已经基本上讲解完了Netty的主要内容,现在来学习Netty中的一些可能存在的问题,如TCP粘包和拆包. 二.粘包和拆包 对于TCP协议而言,当底层发送消息和接受消息时,都需要考虑TCP ...

  10. Netty 中的粘包和拆包详解

    Netty 底层是基于 TCP 协议来处理网络数据传输.我们知道 TCP 协议是面向字节流的协议,数据像流水一样在网络中传输那何来 "包" 的概念呢? TCP是四层协议不负责数据逻 ...

最新文章

  1. “机器学习还是很难用!”
  2. IOS个人开发者账号注册
  3. java中synchronized使用方法
  4. 十三、IntelliJ IDEA 中的版本控制介绍(下)
  5. php 反射原理,PHP反射机制详解
  6. Shell编程:shell script 的追踪与 debug
  7. JavaScript与WebAssembly进行比较
  8. http协议报文格式原理图
  9. wpsOffice文件在线预览-java接入
  10. LVM逻辑卷快照的添加,删除逻辑卷!
  11. 苹果iphone手机哪些机型支持刷公交卡
  12. 靠着“反转”设计,这些短视频火了
  13. ValueError: mode mismatch
  14. 全国各地的五十种面条大全
  15. 如何禁止百度自动转码?
  16. 想学3D建模,去哪儿学比较好
  17. 【vim小小记】vim的复制粘贴(包括系统剪贴板)
  18. php 点餐系统 毕业设计,php毕业设计_基于php的校园餐厅网上订餐系统
  19. 关键基础设施保护:四大免遭网络攻击的秘诀
  20. mount ntfs分区和配置xmms手记(转)

热门文章

  1. vue echarts div变化_数据可视化之echarts在Vue中的使用
  2. C#记事本的简单开发
  3. linux nvm node 权限不够_centos部署node+mongodb环境
  4. python 语法提示_Python 语法提示vim配置
  5. linux里netstat与ps,理解proc目录与linux进程、ps命令、netstat命令的关系
  6. python 实例对象 浅拷贝_Python 对象的深拷贝与浅拷贝 -- (转)
  7. Keras Model AttributeError:’str‘ object has no attribute ’call‘
  8. Windows核心编程_修改开机密码
  9. HTTP请求报头中各个字段的含义
  10. bzoj 3674 可持久化并查集加强版——可持久化并查集