为什么使用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粘包拆包内容请搜索Devmax以前的文章或继续浏览下面的相关文章希望大家以后多多支持Devmax!

Netty粘包拆包及使用原理详解的更多相关文章

  1. ios – Netty Channel关闭检测

    我正在使用netty和ios构建服务器客户端应用程序,当用户在他/她的ios设备上关闭WiFi时,我遇到问题,netty服务器不了解它.服务器需要知道为该用户进行清理并将其设置为离线状态,但是当用户再次尝试连接时,服务器只是告诉他他/她已经在线.解决方法如果我正确地理解了您的问题:您想要监听服务器端的客户端通道关闭事件,并进行一些会话清理,在Netty有两种方式来收听频道封闭事件:1)如果您的服务

  2. Android MINA vs netty for Android

    在here,MINA和netty之间有一个非常翔实的比较当平台是Android时,我想知道您的偏好!>我有一个主机应该接受连接以及建立与Android设备的连接.>该主机为其操作实现了Boost.ASIO.我需要为android方面选择一个简单的框架.>基于几个小时的谷歌搜索,我,相当新的java,缩小到MINA和netty.两者似乎都很好,虽然netty似乎更容易.>当我读到一些关于在android中使用netty的bug报告时,我感到很困惑.>连接到主机的Android模拟器数量可以增长到很多.所以问

  3. VUE响应式原理的实现详解

    这篇文章主要为大家详细介绍了VUE响应式原理的实现,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下,希望能够给你带来帮助

  4. 五分钟理解keep alive用法及原理

    这篇文章主要为大家介绍了keep alive用法及原理示例解析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪

  5. PHP架构及原理知识点详解

    这篇文章主要介绍了PHP架构及原理知识点以及相关实例讲解,需要的朋友们参考下。

  6. DialogFragment运行原理及使用方法详解

    这篇文章主要介绍了DialogFragment运行原理及使用方法详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下

  7. vue3.0响应式函数原理详细

    这篇文章主要介绍了vue3.0响应式函数原理,Vue3的响应式系统可以监听动态添加的属性还可以监听属性的删除操作,以及数组的索引以及length属性的修改操作。另外Vue3的响应式系统还可以作为模块单独使用。下面更多介绍,需要的小伙伴可以才可以参考一下

  8. 一文搞懂Spring循环依赖的原理

    这篇文章将用实例来为大家详细介绍@Autowired解决循环依赖的原理,文中的示例代码讲解详细,对我们学习Spring有一定帮助,感兴趣的可以学习一下

  9. Spring Boot自动配置的原理及@Conditional条件注解

    这篇文章主要介绍了Spring Boot自动配置的原理及@Conditional条件注解,文章围绕主题展开详细的内容介绍,具有一定的参考价值,感兴趣的朋友可以参考一下

  10. Vue3 Reactive响应式原理逻辑详解

    这篇文章主要介绍了Vue3 Reactive响应式原理逻辑详解,文章围绕主题展开详细的内容介绍,具有一定的参考价值,需要的小伙伴可以参考一下

随机推荐

  1. 基于EJB技术的商务预订系统的开发

    用EJB结构开发的应用程序是可伸缩的、事务型的、多用户安全的。总的来说,EJB是一个组件事务监控的标准服务器端的组件模型。基于EJB技术的系统结构模型EJB结构是一个服务端组件结构,是一个层次性结构,其结构模型如图1所示。图2:商务预订系统的构架EntityBean是为了现实世界的对象建造的模型,这些对象通常是数据库的一些持久记录。

  2. Java利用POI实现导入导出Excel表格

    这篇文章主要为大家详细介绍了Java利用POI实现导入导出Excel表格,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  3. Mybatis分页插件PageHelper手写实现示例

    这篇文章主要为大家介绍了Mybatis分页插件PageHelper手写实现示例,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪

  4. (jsp/html)网页上嵌入播放器(常用播放器代码整理)

    网页上嵌入播放器,只要在HTML上添加以上代码就OK了,下面整理了一些常用的播放器代码,总有一款适合你,感兴趣的朋友可以参考下哈,希望对你有所帮助

  5. Java 阻塞队列BlockingQueue详解

    本文详细介绍了BlockingQueue家庭中的所有成员,包括他们各自的功能以及常见使用场景,通过实例代码介绍了Java 阻塞队列BlockingQueue的相关知识,需要的朋友可以参考下

  6. Java异常Exception详细讲解

    异常就是不正常,比如当我们身体出现了异常我们会根据身体情况选择喝开水、吃药、看病、等 异常处理方法。 java异常处理机制是我们java语言使用异常处理机制为程序提供了错误处理的能力,程序出现的错误,程序可以安全的退出,以保证程序正常的运行等

  7. Java Bean 作用域及它的几种类型介绍

    这篇文章主要介绍了Java Bean作用域及它的几种类型介绍,Spring框架作为一个管理Bean的IoC容器,那么Bean自然是Spring中的重要资源了,那Bean的作用域又是什么,接下来我们一起进入文章详细学习吧

  8. 面试突击之跨域问题的解决方案详解

    跨域问题本质是浏览器的一种保护机制,它的初衷是为了保证用户的安全,防止恶意网站窃取数据。那怎么解决这个问题呢?接下来我们一起来看

  9. Mybatis-Plus接口BaseMapper与Services使用详解

    这篇文章主要为大家介绍了Mybatis-Plus接口BaseMapper与Services使用详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪

  10. mybatis-plus雪花算法增强idworker的实现

    今天聊聊在mybatis-plus中引入分布式ID生成框架idworker,进一步增强实现生成分布式唯一ID,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

返回
顶部