Netty粘包拆包及使用原理详解

目录
  • 为什么使用Netty框架
  • Netty框架介绍
  • Netty实战
    • Netty编写服务器端
    • Netty客户端
  • 粘包与拆包

为什么使用Netty框架

  • NIO的类库和API繁杂,使用麻烦,你需要熟练掌握Selector、ServerSocketChannel、SocketChannel、ByteBuffer等。
  • 需要具备其他的额外技能做铺垫,例如熟悉Java多线程编程。这是因为NIO编程涉及到 Reactor 模式,你必须对多线程和网路编程非常熟悉,才能编写出高质量的NIO程序。
  • 可靠性能力补齐,工作量和难度都非常大。例如客户端面临断连重连、网络闪断、半包读写、失败缓存、网络拥塞和异常码流的处理等问题,NIO编程的特点是功能开发相对容易,但是可靠性能力补齐的工作量和难度都非常大。
  • JDK NIO的BUG,例如臭名昭著的 epoll bug,它会导致Selector空轮询,最终导致CPU 100%。官方声称在JDK1.6版本的update18修复了该问题,但是直到JDK1.7版本该问题仍旧存在,只不过该BUG发生概率降低了一些而已,它并没有被根本解决。该BUG以及与该BUG相关的问题单可以参见以下链接内容。

由于上述原因,在大多数场景下,不建议大家直接使用JDK的NIO类库,除非你精通NIO编程或者有特殊的需求。在绝大多数的业务场景中,我们可以使用NIO框架Netty来进行NIO编程,它既可以作为客户端也可以作为服务端,同时支持UDP和异步文件传输,功能非常强大。

Netty框架介绍

Netty是业界最流行的NIO框架之一,它的健壮性、功能、性能、可定制性和可扩展性在同类框架中都是首屈一指的,它已经得到成百上千的商用项目验证,例如Hadoop的RPC框架Avro就使用了Netty作为底层通信框架,其他还有业界主流的RPC框架,也使用Netty来构建高性能的异步通信能力。

优点总结:

  • API使用简单,开发门槛低;
  • 功能强大,预置了多种编解码功能,支持多种主流协议;
  • 定制能力强,可以通过ChannelHandler对通信框架进行灵活地扩展;
  • 性能高,通过与其他业界主流的NIO框架对比,Netty的综合性能最优;
  • 成熟、稳定,Netty修复了已经发现的所有JDK NIO BUG,业务开发人员不需要再为NIO的BUG而烦恼;
  • 社区活跃,版本迭代周期短,发现的BUG可以被及时修复,同时,更多的新功能会加入;
  • 经历了大规模的商业应用考验,质量得到验证。Netty在互联网、大数据、网络游戏、企业应用、电信软件等众多行业已经得到了成功商用,证明它已经完全能够满足不同行业的商业应用了。

Netty实战

首先引入Netty的jar包。

<dependency>
    <groupId>io.netty</groupId>
    <artifactId>netty-all</artifactId>
    <version>4.1.42.Final</version>
</dependency>

Netty编写服务器端

NettyServer 类

public class NettyServer {
    /**
     * netty启动端口号
     */
    private static int port = 8080;
    public static void main(String[] args) {
        /**
         *  客户端创建两个线程池组分别为 boss线程组和工作线程组
         */
        // 用于接受客户端连接的请求 (并没有处理请求)
        NioEventLoopGroup bossGroup = new NioEventLoopGroup();
        // 用于处理客户端连接的读写操作(处理请求操作)
        NioEventLoopGroup workGroup = new NioEventLoopGroup();
        ServerBootstrap serverBootstrap = new ServerBootstrap();
        //NioServerSocketChannel   标记当前是服务器端
        serverBootstrap.group(bossGroup, workGroup).channel(NioServerSocketChannel.class)
                .childHandler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel socketChannel) throws Exception {
                        // 设置我们分割最大长度为1024
                        socketChannel.pipeline().addLast(new LineBasedFrameDecoder(1024));
                        // 获取数据的结果为string类型
                        socketChannel.pipeline().addLast(new StringEncoder());
                         //处理每个handler(也就是每次客户端请求)
                        socketChannel.pipeline().addLast(new ServerHandler());
                    }
                });
        try {
            //绑定端口号
            ChannelFuture bind = serverBootstrap.bind(port);
            ChannelFuture sync = bind.sync();
            System.out.println("服务器端启动成功:" + port);
            //等待监听我们的请求
            sync.channel().closeFuture().sync();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            //优雅的关闭我们的线程池
            bossGroup.shutdownGracefully();
            workGroup.shutdownGracefully();
        }
    }
}

ServerHandler 类

public class ServerHandler extends SimpleChannelInboundHandler {
    /*
     * @Author kaico
     * @Date 9:56 2020/10/8
     * @Description //TODO 获取数据
     * @Param [channelHandlerContext, o]
     * @return void
     **/
    @Override
    protected void channelRead0(ChannelHandlerContext channelHandlerContext, Object o) throws Exception {
        ByteBuf byteBuf = (ByteBuf) o;
        String request = byteBuf.toString(CharsetUtil.UTF_8);
        System.out.println("request:" + request);
        // 响应内容:
        channelHandlerContext.writeAndFlush(Unpooled.copiedBuffer("这里是Netty服务端\n", CharsetUtil.UTF_8));
    }
}

Netty客户端

NettyClient 类

public class NettyClient {
    public static void main(String[] args) {
        //创建nioEventLoopGroup
        NioEventLoopGroup group = new NioEventLoopGroup();
        Bootstrap bootstrap = new Bootstrap();
        bootstrap.group(group).channel(NioSocketChannel.class)
                .remoteAddress(new InetSocketAddress("127.0.0.1", 8080))
                .handler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel ch) throws Exception {
                        // 设置我们分割最大长度为1024
                        ch.pipeline().addLast(new LineBasedFrameDecoder(1024));
                        // 获取数据的结果为string类型
                        ch.pipeline().addLast(new StringEncoder());
                        ch.pipeline().addLast(new ClientHandler());
                    }
                });
        try {
            // 发起同步连接
            ChannelFuture sync = bootstrap.connect().sync();
            sync.channel().closeFuture().sync();
        } catch (Exception e) {

        } finally {
            group.shutdownGracefully();
        }
    }
}

ClientHandler 类

public class ClientHandler extends SimpleChannelInboundHandler {
    /**
     * 活跃通道可以发送消息
     *
     * @param ctx
     * @throws Exception
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        for (int i = 0; i < 10; i++) {
            // 发送数据
            ctx.writeAndFlush(Unpooled.copiedBuffer("你是什么类型的服务端啊?\n", CharsetUtil.UTF_8));
        }
        //客户端发十条消息
    }
    /**
     * 读取消息
     *
     * @param ctx
     * @param msg
     * @throws Exception
     */
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {
        ByteBuf byteBuf = (ByteBuf) msg;
        System.out.println("resp:" + byteBuf.toString(CharsetUtil.UTF_8));
    }
}

粘包与拆包

原因:因为我们现在tcp协议默认是长连接形式实现通讯,发送请求完了之后整个连接暂时不会关闭

1.短连接

客户端与服务器端建立连接的时候,客户端发送一条消息,客户端与服务器连接关闭

2.长连接

客户端与服务器端建立连接的时候,客户端发送多条消息,客户端与服务器连接关闭

什么是粘包:多次发送的消息,服务器一次合并读取msgmsg

什么是拆包:多次发送消息 服务器读取第一条数据完整+第二条不完整数据 第二条不完整数据 Msgm sg

为什么会造成拆包和粘包? 前提长连接、其次缓冲区

原因的造成:

Tcp协议为了能够高性能的传输数据,发送和接受时候都会采用缓冲区,必须等待缓冲区满了以后才可以发送或者读取;

当我们的应用程序如果发送的数据大于了我们的套字节的缓冲区大小的话,就会造成了拆包。拆分成多条消息读取。当我们应用程序如果发送的写入的消息如果小于套字节缓冲区大小的时候

粘包与拆包产生的背景:

Tcp协议为了高性能的传输,发送和接受的时候都采用了缓冲区

3. 当我们的应用程序发送的数据大于套字节缓冲区的时候,就会实现拆包。

4. 当我们的应用程序写入的数据小于套字节缓冲区的时候,多次发送的消息会合并到一起接受,这个过程我们可以称做为粘包。

5. 接受端不够及时的获取缓冲区的数据,也会产生粘包的问题

6. 进行mss(最大报文长度)大小的TCP分段,当TCP报文长度-TCP头部长度>mss的时候将发生拆包。

解决思路:

7. 以固定的长度发送数据,到缓冲区

8. 可以在数据之间设置一些边界(\n或者\r\n)

9. 利用编码器LineBaseDFrameDecoder解决tcp粘包的问题

常用编码器:

  • DelimiterBasedFrameDecoder 解决TCP的粘包解码器
  • StringDecoder 消息转成String解码器
  • LineBasedFrameDecoder 自动完成标识符分隔解码器
  • FixedLengthFrameDecoder 固定长度解码器,二进制
  • Base64Decoder 解码器

利用编码器LineBaseDFrameDecoder解决tcp粘包的问题的Java代码案例,核心思路就是增加边界 \n

服务器端类 NettyServer 的修改点

serverBootstrap.group(bossGroup, workGroup).channel(NioServerSocketChannel.class)
        .childHandler(new ChannelInitializer<SocketChannel>() {
            @Override
            protected void initChannel(SocketChannel socketChannel) throws Exception {
                // 设置我们分割最大长度为1024
                socketChannel.pipeline().addLast(new LineBasedFrameDecoder(1024));
                // 获取数据的结果为string类型
                socketChannel.pipeline().addLast(new StringEncoder());
                //发送数据的时候设置边界 \n
                socketChannel.pipeline().addLast(new ServerHandler());
            }
        });

服务器端类 ServerHandler 的修改点

@Override
    protected void channelRead0(ChannelHandlerContext channelHandlerContext, Object o) throws Exception {
        ByteBuf byteBuf = (ByteBuf) o;
        String request = byteBuf.toString(CharsetUtil.UTF_8);
        System.out.println("request:" + request);
        // 响应内容:
        channelHandlerContext.writeAndFlush(Unpooled.copiedBuffer("这里是Netty服务端\n", CharsetUtil.UTF_8));
    }

客户端的类 NettyClient 的修改点

bootstrap.group(group).channel(NioSocketChannel.class)
                .remoteAddress(new InetSocketAddress("127.0.0.1", 8080))
                .handler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel ch) throws Exception {
                        // 设置我们分割最大长度为1024
                        ch.pipeline().addLast(new LineBasedFrameDecoder(1024));
                        // 获取数据的结果为string类型
                        ch.pipeline().addLast(new StringEncoder());
                        ch.pipeline().addLast(new ClientHandler());
                    }
                });

客户端的类 ClientHandler 的修改点

 @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        for (int i = 0; i < 10; i++) {
            // 发送数据
            ctx.writeAndFlush(Unpooled.copiedBuffer("你是什么类型的服务端啊?\n", CharsetUtil.UTF_8));
        }
        //客户端发十条消息
    }

到此这篇关于Netty粘包拆包详解及实战流程的文章就介绍到这了,更多相关Netty粘包拆包内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 使用Netty解决TCP粘包和拆包问题过程详解

    前言 上一篇我们介绍了如果使用Netty来开发一个简单的服务端和客户端,接下来我们来讨论如何使用解码器来解决TCP的粘包和拆包问题 TCP为什么会粘包/拆包 我们知道,TCP是以一种流的方式来进行网络转播的,当tcp三次握手简历通信后,客户端服务端之间就建立了一种通讯管道,我们可以想象成自来水管道,流出来的水是连城一片的,是没有分界线的. TCP底层并不了解上层的业务数据的具体含义,它会根据TCP缓冲区的实际情况进行包的划分. 所以对于我们应用层而言.我们直观是发送一个个连续完整TCP数据包的,

  • Netty分布式固定长度解码器实现原理剖析

    固定长度解码器 上一小节:解码器读取数据不完整的逻辑剖析 我们了解到, 解码器需要继承ByteToMessageDecoder, 并重写decode方法, 将解析出来的对象放入集合中集合, ByteToMessageDecoder中可以将解析出来的对象向下进行传播, 这一小节带大家剖析一个最简单的解码器FixedLengthFrameDecoder, 从它入手了解码器的相关原理 FixedLengthFrameDecoder是一个固定长度的解码器, 功能就是根据固定长度, 截取固定大小的字节数进

  • Netty解决 TCP 粘包拆包的方法

    什么是粘包/拆包 一般所谓的TCP粘包是在一次接收数据不能完全地体现一个完整的消息数据.TCP通讯为何存在粘包呢?主要原因是TCP是以流的方式来处理数据,再加上网络上MTU的往往小于在应用处理的消息数据,所以就会引发一次接收的数据无法满足消息的需要,导致粘包的存在.处理粘包的唯一方法就是制定应用层的数据通讯协议,通过协议来规范现有接收的数据是否满足消息数据的需要. 我们都知道TCP是基于字节流的传输协议. 那么数据在通信层传播其实就像河水一样并没有明显的分界线,而数据具体表示什么意思什么地方有句

  • 分析Netty直接内存原理及应用

    一.通常的内存模型概述 一般地,系统为了保证系统本身的安全性和健壮性,会将内存从逻辑上隔离成内核区域和用户区域,这很容易理解.因为用户行为不可控性太强,暴露得太多,就容易导致各种神奇的用法,超出系统的控制范围.当然,有的语言是支持直接控制内存的,比如C, 你可以用一个指针,访问内存中的几乎任意位置的数据(除了一些硬件地址).而像汇编,则可以访问任意地址.而这些底层的语言,已经离我们越来越远了,它基本上和普通程序员关系不大了. 用户很多时候的编程控制,都是在用户区域进行的,比如我做一些加减乘除,如

  • Netty粘包拆包问题解决方案

    TCP黏包拆包 TCP是一个流协议,就是没有界限的一长串二进制数据.TCP作为传输层协议并不不了解上层业务数据的具体含义,它会根据TCP缓冲区的实际情况进行数据包的划分,所以在业务上认为是一个完整的包,可能会被TCP拆分成多个包进行发送,也有可能把多个小的包封装成一个大的数据包发送,这就是所谓的TCP粘包和拆包问题. 怎么解决? • 消息定长度,传输的数据大小固定长度,例如每段的长度固定为100字节,如果不够空位补空格 • 在数据包尾部添加特殊分隔符,比如下划线,中划线等 • 将消息分为消息头和

  • Netty粘包拆包及使用原理详解

    目录 为什么使用Netty框架 Netty框架介绍 Netty实战 Netty编写服务器端 Netty客户端 粘包与拆包 为什么使用Netty框架 NIO的类库和API繁杂,使用麻烦,你需要熟练掌握Selector.ServerSocketChannel.SocketChannel.ByteBuffer等. 需要具备其他的额外技能做铺垫,例如熟悉Java多线程编程.这是因为NIO编程涉及到 Reactor 模式,你必须对多线程和网路编程非常熟悉,才能编写出高质量的NIO程序. 可靠性能力补齐,工

  • python TCP Socket的粘包和分包的处理详解

    概述 在进行TCP Socket开发时,都需要处理数据包粘包和分包的情况.本文详细讲解解决该问题的步骤.使用的语言是Python.实际上解决该问题很简单,在应用层下,定义一个协议:消息头部+消息长度+消息正文即可. 那什么是粘包和分包呢? 关于分包和粘包 粘包:发送方发送两个字符串"hello"+"world",接收方却一次性接收到了"helloworld". 分包:发送方发送字符串"helloworld",接收方却接收到了两

  • SpringBoot应用jar包启动原理详解

    目录 1.maven打包 2.Jar包目录结构 3.可执行Jar(JarLauncher) 4.WarLauncher 5.总结 1.maven打包 Spring Boot项目的pom.xml文件中默认使用spring-boot-maven-plugin插件进行打包: <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>s

  • Spring AOP的实现原理详解及实例

    Spring AOP的实现原理详解及实例 spring 实现AOP是依赖JDK动态代理和CGLIB代理实现的. 以下是JDK动态代理和CGLIB代理简单介绍 JDK动态代理:其代理对象必须是某个接口的实现,它是通过在运行期间创建一个接口的实现类来完成对目标对象的代理. CGLIB代理:实现原理类似于JDK动态代理,只是它在运行期间生成的代理对象是针对目标类扩展的子类.CGLIB是高效的代码生成包,底层是依靠ASM(开源的Java字节码编辑类库)操作字节码实现的,性能比JDK强. 在Spring中

  • 简单实现Spring的IOC原理详解

    控制反转(InversionofControl,缩写为IoC) 简单来说就是当自己需要一个对象的时候不需要自己手动去new一个,而是由其他容器来帮你提供:Spring里面就是IOC容器. 例如: 在Spring里面经常需要在Service这个装配一个Dao,一般是使用@Autowired注解:类似如下 public Class ServiceImpl{ @Autowired Dao dao; public void getData(){ dao.getData(); } 在这里未初始化Dao直接

  • Node.Js中实现端口重用原理详解

    本文介绍了Node.Js中实现端口重用原理详解,分享给大家,具体如下: 起源,从官方实例中看多进程共用端口 const cluster = require('cluster'); const http = require('http'); const numCPUs = require('os').cpus().length; if (cluster.isMaster) { console.log(`Master ${process.pid} is running`); for (let i =

  • spring boot微服务自定义starter原理详解

    这篇文章主要介绍了spring boot微服务自定义starter原理详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 使用spring boot开发微服务后,工程的数量大大增加(一定要按照领域来切,不要一个中间件客户端包一个),让各个jar从开发和运行时自包含成了一个重要的内容之一.spring boot starter就可以用来解决该问题(没事启动时别依赖于applicationContext.getBean获取bean进行处理,依赖关系

  • Java代码块与代码加载顺序原理详解

    这篇文章主要介绍了Java代码块与代码加载顺序原理详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 本文首先介绍几个基本的名次,然后介绍了三种代码块的特性和使用方法. 在面试大型公司时,如果遇到大型国企或者大的互联网私企,笔试中经常遇到代码块和代码加载顺序的笔试题.这里做一个总结,也方便各位小伙伴飙车不会飘. 名词解释 代码块 由 { } 包起来的代码,称为代码块 静态代码块 由 static { } 包起来的代码,称为静态代码块. 不同类型

随机推荐