精华内容
下载资源
问答
  • Netty框架使用

    千次阅读 2019-02-15 14:24:54
    首先在使用Netty框架的时候需要了解Netty是一个什么东西。 Netty是由JBOSS提供的一个java开源框架。Netty提供异步的、事件驱动的网络应用程序框架和工具,用以快速开发高性能、高可靠性的网络服务器和客户端程序。...

    前言

    首先在使用Netty框架的时候需要了解Netty是一个什么东西。

    • Netty是由JBOSS提供的一个java开源框架。Netty提供异步的、事件驱动的网络应用程序框架和工具,用以快速开发高性能、高可靠性的网络服务器和客户端程序。也就是说,Netty 是一个基于NIO的客户、服务器端编程框架。
    • 使用Netty 可以确保你快速和简单的开发出一个网络应用,例如实现了某种协议的客户,服务端应用。Netty相当简化和流线化了网络应用的编程开发过程,例如,TCP和UDP的socket服务开发。
    • “快速”和“简单”并不用产生维护性或性能上的问题。Netty 是一个吸收了多种协议的实现经验,这些协议包括FTP,SMTP,HTTP,各种二进制,文本协议,并经过相当精心设计的项目,最终,Netty 成功的找到了一种方式,在保证易于开发的同时还保证了其应用的性能,稳定性和伸缩性。

    下面就来看看第一个使用Netty搭建的应用

    一、开发环境

    首先我们需要准备对应的全套的Netty的jar包,这些可以参考Netty的官方网站
    Netty的官网是:http://netty.io
    当然也可以访问第三方的中文网站 http://ifeve.com/netty5-user-guide/

    准备Netty4+
    Eclipse或者IDEA
    我这里使用的是IDEA

    Netty客户端和服务器端描述

    一般情况下,在实际使用的时候更多的关注于服务器端的开发,而很少关注客户端的开发,因为对于不同的业务逻辑会有不同的客户端实现,但是对于这些客户端来说服务器端只有一个。服务器会写数据到客户端并且处理多个客户端的并发连接。从理论上来说,限制程序性能的因素只有系统资源和JVM。为了方便理解,这里举了个生活例子,在山谷或高山上大声喊,你会听见回声,回声是山返回的;
    在这个例子中,你是客户端,山是服务器。喊的行为就类似于一个Netty客户端将数据发送到服务器,听到回声就类似于服务器将相同的数据返回给你,你离开山谷就断开了连接,但是你可以返回进行重连服务器并且可以发送更多的数据。

    逻辑分析

    首先Netty是一个网络框架,所以说就要涉及到客户端和服务器端,这样的话就需要分析一下客户端和服务器端各自具有什么样子的作用,根据具体的作用设计对应的代码。

    1. 客户端连接到服务器
    2. 建立连接后,发送或接收数据
    3. 服务器处理所有的客户端连接

    这个是建立一个网络应用必须的三个步骤。下面就是具体的按照这三个步骤编写

    开始创建应用

    首先创建一个服务器端的程序代码如下

    服务器端代码
    public class ServerHelloWorld {
        //创建线程组,监听客户端的请求
        private EventLoopGroup acceptorGroup = null;
        //处理客户端相关操作线程组,负责处理与客户端端的请求操作。
        private EventLoopGroup clientGroup = null;
        //服务器启动相关配置信息
        private ServerBootstrap bootstrap = null;
    
        public ServerHelloWorld() {
            init();
        }
        //初始化
        private void init() {
            //初始化线程组
            acceptorGroup = new NioEventLoopGroup();
            //处理客户端逻辑
            clientGroup = new NioEventLoopGroup();
            //初始化配置信息
            bootstrap = new ServerBootstrap();
            //绑定监听线程组
            bootstrap.group(acceptorGroup, clientGroup);
            //设置通信模式为NIO模式同步非阻塞
            bootstrap.channel(NioServerSocketChannel.class);
            //设定缓存区的大小,单位是字节
            bootstrap.option(ChannelOption.SO_BACKLOG, 1024);
            //SO_SNDBUF 表示发送缓冲区,SO_RCVBUF 表示接收缓存区,SO_KEEPALIVE 表示是否开启心跳检查,保证连接有效
            bootstrap.option(ChannelOption.SO_SNDBUF, 16 * 1024)
                    .option(ChannelOption.SO_RCVBUF, 16 * 1024)
                    .option(ChannelOption.SO_KEEPALIVE, true);
        }
        /**
         * 监听处理逻辑
         * @param port 监听端口
         * @param acceptorHandlers 处理器
         * @return
         * @throws InterruptedException
         */
        public ChannelFuture doAccept(int port, final ChannelHandler... acceptorHandlers) throws InterruptedException {
            /**
             * childHandler 是服务端的BootStrap独有的方法是用于提供处理对象,提供处理对象可以一次性的增加若干个处理逻辑
             * 类似责任链模式的处理逻辑,也就是说你增加A 和B两个处理逻辑,在处理逻辑的时候会按照A 和 B 的顺序进行依次处理
             *
             * ChannelInitializer 用于提供处理器的一个模型对象,这个模型对象,其中定义了一个方法initChannel
             *
             * initChannel 这个方法适用于初始化处理逻辑责任链条的。可以保证服务端的BootStrap只初始化一次处理器,尽量提供处理器的重用,
             * 减少了反复创建处理器的操作
             */
            bootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
                @Override
                protected void initChannel(SocketChannel ch) throws Exception {
                    ch.pipeline().addLast(acceptorHandlers);
                }
            });
            /**
             * bind 方法 用来绑定处理端口,ServerBootstrap可以绑定多个监听端口。多次调用bind方法即可
             *
             * sync 方法开始启动监听逻辑,返回ChannelFuture 返回结果是监听成功后的未来结果,可以使用
             * 这个ChannelFuture实现后续的服务器与客户端的交互所以要获取这个ChannelFuture对象
             */
            ChannelFuture future = bootstrap.bind(port).sync();
            //ChannelFuture future = bootstrap.bind(port).sync();
            //ChannelFuture future = bootstrap.bind(port).sync();
            return future;
        }
        /**
         * 回收方法
         * shutdownGracefully 是一个安全关闭的方法,可以保证不放弃任何一个以接收的客户端请求
         */
        public void release(){
            this.acceptorGroup.shutdownGracefully();
            this.clientGroup.shutdownGracefully();
        }
    
        public static void main(String[] args) {
            ChannelFuture future = null;
            ServerHelloWorld server = null;
            try{
                server = new ServerHelloWorld();
                //建立连接
                future = server.doAccept(8081, new ServerHandler());
                System.out.println("server started.");
                //关闭连接,回收资源
                future.channel().closeFuture();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
                if (null != future){
                    try {
                        future.channel().closeFuture().sync();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                if (null!=server){
                    server.release();
                }
            }
        }
    }
    

    对于服务器端的程序来说,创建了两个线程组EventLoopGroup,这里需要通过原理图来详细的说明一下。
    在这里插入图片描述
    首先对于上面的图来说我们可以看到,在服务器端创建了两个线程组,这个两个线程组一个用来接收请求,一个用来处理业务逻辑,之前提到Netty是使用NIO来设计的,也就是说是同步非阻塞的一种IO。对于NIO、AIO、BIO等来说这些概念在后面的更新中会有总结到,现在就是简单的提供这样的一个概念。
    在建立服务器端的时候,会看到一个ServerBootstrap 对象,这个表示服务器端,当然在客户端有一个与这个类对等的类BootStrap类,这两个类为链接的建立提供了很多的配置项信息。

    服务端业务处理逻辑
    **
     *
     * @ChannelHandler.Sharable 这个注解表示当前是一个可以分享的处理器,,服务注册此Handler,可以分享给多个客户端使用
     * 如果不使用这个注解的话,每次客户端请求时,必须为客户端重新创建一个新的处理器Handler对象
     *
     * bootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
     *             @Override
     *             protected void initChannel(SocketChannel ch) throws Exception {
     *                 ch.pipeline().addLast(new XXXHandler());
     *             }
     *         });
     * 建议自己开发的时候就会使的Handler是可共享的
     *
     * 如果Handler是一个可分享的,一定避免定义一个可以写的实例变量。不安全
     *
     */
    @ChannelHandler.Sharable
    public class ServerHandler extends ChannelHandlerAdapter {
        /**
         * 业务处理逻辑  用于处理读取数据请求的逻辑。它里面的方法和参数如下
         * @param ctx 上下文对象,其中包含于客户端建立连接的所有资源,比如说对应的Channel
         * @param msg 读取到的数据,默认类型是bytebuf 这个ByteBuf 是对ByteBuffer的一个封装。简化了操作
         * @throws Exception
         */
        @Override
        public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
            //强制类型转换
            ByteBuf readBuffer = (ByteBuf) msg;
            // 创建一个字节数组,用于保存缓存中的数据。readableBytes 在原生的ByteBuffer也有同样的功能的方法
            byte[] tempDatas = new byte[readBuffer.readableBytes()];
            //读取到对应的数据 可以直接读取,这个不需要考虑复位的问题,
            readBuffer.readBytes(tempDatas);
            String message = new String(tempDatas,"UTF-8");
            System.out.println("from client :"+message);
            if ("exit".equals(message)){
                //如果客户端断开连接,则关闭上下文
                ctx.close();
                return;
            }
            String line = "server message to client!";
            //写操作自动释放缓存,避免内存溢出的问题。
            ctx.writeAndFlush(Unpooled.copiedBuffer(line.getBytes("UTF-8")));
    
            /**
             * 如果调用的是write方法,不会刷新缓存,缓存中的数据不会发送到客户端,必须调用flush方法进行强制刷出
             *  ctx.write(msg);
             *  ctx.flush();
             */
        }
        /**
         * 异常处理逻辑
         * ChannelHandlerContext关闭代表当前与客户端的连接处理逻辑,当客户端异常退出的时候这个异常也会执行
         * @param ctx
         * @param cause
         * @throws Exception
         */
        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
            System.out.println("server exceptionCaught method run …… ");
            ctx.close();
        }
    }
    
    客户端代码
    public class ClientHelloWorld {
        //处理请求线程组
        private EventLoopGroup group = null;
        //服务启动相关配置信息
        private Bootstrap bootstrap = null;
        public ClientHelloWorld(){
            init();
        }
        private void init(){
            group = new NioEventLoopGroup();
            bootstrap = new Bootstrap();
            //定义线程组
            bootstrap.group(group);
            bootstrap.channel(NioSocketChannel.class);
        }
        public ChannelFuture doRequest(String host, int port, final ChannelHandler ... handlers) throws InterruptedException {
            this.bootstrap.handler(new ChannelInitializer<SocketChannel>() {
                /**
                 * 客户端的Handler没有childHandler方法,只有Handler方法
                 * 这个方法与服务器的方法是类似的。
                 * 客户端必须绑定处理器,也就说必须调用Handler方法
                 * @param ch
                 * @throws Exception
                 */
                @Override
                protected void initChannel(SocketChannel ch) throws Exception {
                    ch.pipeline().addLast(handlers);
                }
            });
            //建立连接
            ChannelFuture future = this.bootstrap.connect(host,port).sync();
            return future;
        }
        public void release(){
            this.group.shutdownGracefully();
        }
        public static void main(String[] args) {
            ClientHelloWorld client = null;
            ChannelFuture future = null;
            try{
                client = new ClientHelloWorld();
                future = client.doRequest("localhost",8081,new ClientHandler());
                Scanner scanner = null;
                while (true){
                    scanner = new Scanner(System.in);
                    System.out.println("enter message send to server (enter exit close client)");
                    String line = scanner.nextLine();
                    if ("exit".equals(line)){
                        future.channel().writeAndFlush(Unpooled.copiedBuffer(line.getBytes("UTF-8"))).addListener(ChannelFutureListener.CLOSE);
                        break;
                    }
                    future.channel().writeAndFlush(Unpooled.copiedBuffer(line.getBytes("UTF-8")));
                    TimeUnit.SECONDS.sleep(1);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }finally {
                if (null!=client){
                    client.release();
                }
                if (null!=future){
                    try {
                        future.channel().closeFuture().sync();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
    
    客户端逻辑处理逻辑
    public class ClientHandler extends ChannelHandlerAdapter {
    
        @Override
        public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
            try {
                //强制类型转换
                ByteBuf readBuffer = (ByteBuf) msg;
                // 创建一个字节数组,用于保存缓存中的数据。readableBytes 在原生的ByteBuffer也有同样的功能的方法
                byte[] tempDatas = new byte[readBuffer.readableBytes()];
                //读取到对应的数据 可以直接读取,这个不需要考虑复位的问题,
                readBuffer.readBytes(tempDatas);
                System.out.println("from server " + new String(tempDatas, "UTF-8"));
            }finally {
                //用于避免内存溢出
                ReferenceCountUtil.release(msg);
            }
        }
        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
            System.out.println("client exceptionCaught method run …… ");
            ctx.close();
        }
    }
    
    

    总结

    Netty是一个比较高效的网络应用框架,在很多的项目中都使用到了Netty。弥补了原生的的IO的很多的缺陷,但是也有很多不足的地方,需要更具具体的使用情况进行开发。

    展开全文
  • Netty框架简单使用

    2019-11-29 13:40:08
    1.概述: Netty 是由 JBOSS 提供的一个 Java 开源框架。... Netty 是一个基于 NIO 的网络编程框架使用 Netty 可以帮助你快速、简单的开发出一个网络应用,相当于简化和流程化了 NIO 的开发过程。 作为当前最...

    1.概述:

        Netty 是由 JBOSS 提供的一个 Java 开源框架。Netty 提供异步的、基于事件驱动的网络应用程序框架,用以快速开发高性能、高可靠性的网络 IO 程序。
               Netty 是一个基于 NIO 的网络编程框架,使用 Netty 可以帮助你快速、简单的开发出一 个网络应用,相当于简化和流程化了 NIO 的开发过程。
                作为当前最流行的 NIO 框架,Netty 在互联网领域、大数据分布式计算领域、游戏行业、 通信行业等获得了广泛的应用,知名的 Elasticsearch 、Dubbo 框架内部都采用了 Netty。

    2.Netty的整体设计

    2.1 线程模型

     1.单线程模型

     

    服务器端用一个线程通过多路复用搞定所有的 IO 操作(包括连接,读、写等),编码 简单,清晰明了,但是如果客户端连接数量较多,将无法支撑,咱们前面的 NIO 案例就属 于这种模型。

    2.线程池模型

    服务器端采用一个线程专门处理客户端连接请求,采用一个线程池负责 IO 操作。在绝 大多数场景下,该模型都能满足使用。

    3.Netty模型

     

    比较类似于上面的线程池模型,Netty 抽象出两组线程池,BossGroup 专门负责接收客 户端连接,WorkerGroup 专门负责网络读写操作。NioEventLoop 表示一个不断循环执行处理 任务的线程,每个 NioEventLoop 都有一个 selector,用于监听绑定在其上的 socket 网络通道。 NioEventLoop 内部采用串行化设计,从消息的读取->解码->处理->编码->发送,始终由 IO 线 程 NioEventLoop 负责。

    • 一个 NioEventLoopGroup下包含多个 NioEventLoop
    • 每个 NioEventLoop 中包含有一个 Selector,一个 taskQueue
    • 每个 NioEventLoop 的 Selector 上可以注册监听多个 NioChannel
    • 每个 NioChannel 只会绑定在唯一的 NioEventLoop 上
    • 每个 NioChannel 都绑定有一个自己的 ChannelPipeline

    2.2 异步模型

    FUTURE, CALLBACK 和 HANDLER
    Netty 的异步模型是建立在 future和callback的之上的。callback 大家都比较熟悉了,这 里重点说说 Future,它的核心思想是:假设一个方法 fun,计算过程可能非常耗时,等待 fun 返回显然不合适。那么可以在调用 fun 的时候,立马返回一个 Future,后续可以通过 Future 去监控方法 fun 的处理过程。 在使用 Netty 进行编程时,拦截操作和转换出入站数据只需要您提供 callback 或利用 future 即可。这使得链式操作简单、高效, 并有利于编写可重用的、通用的代码。Netty 框 架的目标就是让你的业务逻辑从网络基础应用编码中分离出来、解脱出来。

    Netty的核心api:

    • ChannelHandler及其实现类

    ChannelHandler接口定义了许多事件处理的方法,我们可以通过重写这些方法去实现具 体的业务逻辑。API关系如下图所示:

    我们经常需要自定义一个Handler类去继承ChannelInboundHandlerAdapter,然后通过 重写相应方法实现业务逻辑我们接下来看看一般都需要重写哪些方法:

    1. public void channelActive(ChannelHandlerContext ctx),通道就绪事件
    2. public void channelRead(ChannelHandlerContext ctx, Object msg),通道读取数据事件
    3. public void channelReadComplete(ChannelHandlerContext ctx) ,数据读取完毕事件
    4. public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause),通道发生异常事件
    • Pipeline和ChannelPipeline

    ChannelPipeline是一个Handler的集合,它负责处理和拦截 inbound或者outbound 的事 件和操作,相当于一个贯穿Netty的链。

    1. ChannelPipeline addFirst(ChannelHandler... handlers),把一个业务处理类(handler)添加 到链中的第一个位置
    2. ChannelPipeline addLast(ChannelHandler... handlers),把一个业务处理类(handler)添加 到链中的最后一个位置
    • ChannelHandlerContext 

    这是事件处理器上下文对象,Pipeline链中的实际处理节点。每个处理节点ChannelHandlerContext中包含一个具体的事件处理器ChannelHandler,同时ChannelHandlerContext中也绑定了对应的pipeline和Channel的信息,方便对ChannelHandler进行调用。常用方法如下所示:

    1. ChannelFuture close(),关闭通道
    2. ChannelOutboundInvoker flush(),刷新
    3. ChannelFuture writeAndFlush(Object msg),将数据写到ChannelPipeline中当前ChannelHandler的下一个ChannelHandler开始处理(出站)
    • ChannelOption

     Netty 在创建 Channel 实例后,一般都需要设置 ChannelOption 参数。ChannelOption 是 Socket 的标准参数,而非 Netty 独创的。常用的参数配置有:

    1. ChannelOption.SO_BACKLOG
      对应TCP/IP协议listen函数中的backlog参数,用来初始化服务器可连接队列大小。服务端处理客户端连接请求是顺序处理的,所以同一时间只能处理一个客户端连接。多个客户 端来的时候,服务端将不能处理的客户端连接请求放在队列中等待处理,backlog参数指定了队列的大小。
    2. ChannelOption.SO_KEEPALIVE,一直保持连接活动状态。
    • ChannelFuture

    表示Channel中异步I/O操作的结果,在Netty中所有的I/O操作都是异步的,I/O的调用会直接返回,调用者并不能立刻获得结果,但是可以通过ChannelFuture来获取I/O操作的处理状态。常用方法如下所示:

    1. Channel channel(),返回当前正在进行IO操作的通道
    2. ChannelFuture sync(),等待异步操作执行完毕
    • EventLoopGroup和其实现类NioEventLoopGroup

    EventLoopGroup是一组EventLoop的抽象,Netty为了更好的利用多核CPU 资源,一般会有多个EventLoop同时工作,每个EventLoop维护着一个Selector实例。

    EventLoopGroup提供next接口,可以从组里面按照一定规则获取其中一个EventLoop来处理任务。在Netty服务器端编程中,我们一般都需要提供两个EventLoopGroup,例如:BossEventLoopGroup 和 WorkerEventLoopGroup。

    通常一个服务端口即一个ServerSocketChannel对应一个Selector和一个EventLoop线程。BossEventLoop负责接收客户端的连接并将SocketChannel交给WorkerEventLoopGroup来进行IO 处理,如下图所示:

     

    BossEventLoopGroup通常是一个单线程的EventLoop,EventLoop维护着一个注册了ServerSocketChannel的Selector实例,BossEventLoop不断轮询Selector将连接事件分离出来,通常是OP_ACCEPT事件,然后将接收到的SocketChannel交给WorkerEventLoopGroup,WorkerEventLoopGroup会由next选择其中一个EventLoopGroup来将这个SocketChannel注册到其维护的Selector并对其后续的IO事件进行处理。
          常用方法如下所示:

    1. public  NioEventLoopGroup(),构造方法

    2. public  Future<?>shutdownGracefully(),断开连接,关闭线程
    • ServerBootstrap和Bootstrap

    ServerBootstrap是Netty中的服务器端启动助手,通过它可以完成服务器端的各种配置;Bootstrap是Netty中的客户端启动助手,通过它可以完成客户端的各种配置。常用方法如下所示:

    1. public ServerBootstrap group(EventLoopGroupparentGroup,EventLoopGroupchildGroup),该方法用于服务器端,用来设置两个EventLoop
    2. public B group(EventLoopGroupgroup),该方法用于客户端,用来设置一个EventLoop
    3. public B channel(Class<?extendsC>channelClass),该方法用来设置一个服务器端的通道实现
    4. public <T>B option(ChannelOption<T>option,Tvalue),用来给ServerChannel添加配置
    5. public <T>  ServerBootstrap  childOption(ChannelOption<T>childOption,Tvalue),用来给接收到的通道添加配置
    6. public  ServerBootstrap  childHandler(ChannelHandlerchildHandler),该方法用来设置业务处理类(自定义的handler)
    7. public ChannelFuture bind(intinetPort),该方法用于服务器端,用来设置占用的端口号
    8. public  ChannelFuture  connect(StringinetHost,intinetPort),该方法用于客户端,用来连接服务器端
    • Unpooled类

    这是Netty提供的一个专门用来操作缓冲区的工具类,常用方法如下所示:

    1. public static ByteBuf copiedBuffer(CharSequencestring,Charsetcharset),通过给定的数据和字符编码返回一个ByteBuf对象(类似于NIO中的ByteBuffer对象)

    测试案例:

    导入pom坐标

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
    
        <groupId>com.lkw</groupId>
        <artifactId>netty_test2</artifactId>
        <version>1.0-SNAPSHOT</version>
    
        <dependencies>
            <dependency>
                <groupId>io.netty</groupId>
                <artifactId>netty-all</artifactId>
                <version>4.1.25.Final</version>
            </dependency>
            <dependency>
                <groupId>com.google.code.gson</groupId>
                <artifactId>gson</artifactId>
                <version>2.8.5</version>
            </dependency>
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <version>1.18.6</version>
            </dependency>
        </dependencies>
    
        <build>
            <pluginManagement>
                <plugins>
                    <plugin>
                        <groupId>org.apache.maven.plugins</groupId>
                        <artifactId>maven-compiler-plugin</artifactId>
                        <version>3.2</version>
                        <configuration>
                            <source>1.8</source>
                            <target>1.8</target>
                            <encoding>UTF-8</encoding>
                            <showWarnings>true</showWarnings>
                        </configuration>
                    </plugin>
                </plugins>
            </pluginManagement>
        </build>
    
    </project>

    采用 4.1.8 版本

    自定义服务器端业务处理类,继承 ChannelInboundHandlerAdapter

    package com.lkw;
    
    import com.google.gson.Gson;
    import com.google.gson.reflect.TypeToken;
    import io.netty.buffer.Unpooled;
    import io.netty.channel.Channel;
    import io.netty.channel.ChannelHandlerContext;
    import io.netty.channel.SimpleChannelInboundHandler;
    import io.netty.util.CharsetUtil;
    
    import java.util.ArrayList;
    import java.util.List;
    
    public class NettyServerHandler extends SimpleChannelInboundHandler<String> {
        private static List<Channel> channels = new ArrayList<>();
    
        //通道就绪事件
        @Override
        public void channelActive(ChannelHandlerContext ctx) throws Exception {
            Channel channel = ctx.channel();
            channels.add(channel);
            System.out.println(channel.remoteAddress().toString() + "上线了");
        }
    
        //通道未就绪事件
        @Override
        public void channelInactive(ChannelHandlerContext ctx) throws Exception {
            Channel channel = ctx.channel();
            channels.remove(channel);
            System.out.println(channel.remoteAddress().toString() + "下线了");
        }
    
        @Override
        public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
            ctx.writeAndFlush(Unpooled.copiedBuffer("收到了", CharsetUtil.UTF_8));
        }
    
        //数据读取事件
        @Override
        protected void channelRead0(ChannelHandlerContext channelHandlerContext, String s) throws Exception {
            Gson gson = new Gson();
            List<Person> personList = gson.fromJson(s, new TypeToken<List<Person>>() {
            }.getType());
            for (Person person : personList) {
                System.out.println(person.getName() + " " + person.getAge() + " " + person.getAddr());
            }
    
    
        }
    
        //异常读取事件
        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
            Channel channel = ctx.channel();
            System.out.println(channel.remoteAddress().toString() + "发生异常");
            ctx.close();
        }
    
    }
    

    创建Netty服务端程序

    package com.lkw;
    
    import io.netty.bootstrap.ServerBootstrap;
    import io.netty.channel.ChannelFuture;
    import io.netty.channel.ChannelInitializer;
    import io.netty.channel.ChannelOption;
    import io.netty.channel.EventLoopGroup;
    import io.netty.channel.nio.NioEventLoopGroup;
    import io.netty.channel.socket.SocketChannel;
    import io.netty.channel.socket.nio.NioServerSocketChannel;
    import io.netty.handler.codec.string.StringDecoder;
    import io.netty.handler.codec.string.StringEncoder;
    
    public class NettyServer {
        private int port; //端口号
    
        public NettyServer(int port) {
            this.port = port;
        }
    
        public void run() throws Exception {
            //首先创建两个线程组,创建主线程组,专门处理连接请求
            EventLoopGroup bossGroup = new NioEventLoopGroup();
            //创建workerGroup,专门处理读写操作
            EventLoopGroup workerGroup = new NioEventLoopGroup();
    
            //创建启动助手来配置相关东西
            ServerBootstrap serverBootstrap = new ServerBootstrap();
            serverBootstrap.group(bossGroup, workerGroup)  //将两个主从线程组
                    .channel(NioServerSocketChannel.class)   //设置用什么通道
                    .option(ChannelOption.SO_BACKLOG, 128) //设置线程队列中等待的个数
                    .childOption(ChannelOption.SO_KEEPALIVE, true) //设置一直保持活动连接状态
                    .childHandler(new ChannelInitializer<SocketChannel>() {//创建一个通道初始化容器
                        @Override
                        protected void initChannel(SocketChannel socketChannel) throws Exception {
                            socketChannel.pipeline().addLast("decoder", new StringDecoder());   //添加解码器
                            socketChannel.pipeline().addLast("encoder", new StringEncoder());   //添加编码器
                            socketChannel.pipeline().addLast(new NettyServerHandler());            //添加自定义的Handler
                            System.out.println("正在启动");
                        }
                    });
            //启动服务器并绑定端口号,等待客户端连接!非阻塞
            ChannelFuture cf = serverBootstrap.bind(port).sync();
            System.out.println("服务器已经启动");
            //关闭线程,关闭通道
            cf.channel().closeFuture().sync();
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    
    
        public static void main(String[] args) throws Exception {
            NettyServer nettyServer = new NettyServer(9999);
            nettyServer.run();
        }
    }
    

    编写一个客户端,继承 ChannelInboundHandlerAdapter ,同样这里也可以继承SimpleChannelInboundHandler,这个是她的子类,并分别重写了四个方法。

    package com.lkw;
    
    import com.google.gson.Gson;
    import io.netty.buffer.Unpooled;
    import io.netty.channel.Channel;
    import io.netty.channel.ChannelHandlerContext;
    import io.netty.channel.ChannelInboundHandlerAdapter;
    import io.netty.channel.SimpleChannelInboundHandler;
    import io.netty.util.CharsetUtil;
    
    import java.util.ArrayList;
    import java.util.Arrays;
    import java.util.List;
    
    public class NettyClientHandler extends SimpleChannelInboundHandler<String> {
        private List<Person> people = Arrays.asList(
                new Person("张三", "19", "湖北"),
                new Person("李四", "19", "湖北"),
                new Person("王五", "19", "湖北")
        );
    
        //通道就绪事件
        @Override
        public void channelActive(ChannelHandlerContext ctx) throws Exception {
            Gson gson = new Gson();
            String s = gson.toJson(people);
            ctx.writeAndFlush(Unpooled.copiedBuffer(s, CharsetUtil.UTF_8));
        }
    
        //通道读取完毕事件
        @Override
        public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
            ctx.flush();
        }
    
        @Override
        protected void channelRead0(ChannelHandlerContext channelHandlerContext, String s) throws Exception {
            System.out.println(s.trim());
        }
    }
    

    编写客户端程序:

    package com.lkw;
    
    import io.netty.bootstrap.Bootstrap;
    import io.netty.channel.ChannelFuture;
    import io.netty.channel.ChannelInitializer;
    import io.netty.channel.EventLoopGroup;
    import io.netty.channel.nio.NioEventLoopGroup;
    import io.netty.channel.socket.SocketChannel;
    import io.netty.channel.socket.nio.NioSocketChannel;
    import io.netty.handler.codec.string.StringDecoder;
    import io.netty.handler.codec.string.StringEncoder;
    
    public class NettyClient {
        private final int port;
        private final String host;
    
        public NettyClient(int port, String host) {
            this.host = host;
            this.port = port;
        }
    
    
        public void run() {
            EventLoopGroup group = null;
            try {
                //创建一个线程组来
                group = new NioEventLoopGroup();
    
                //创建客户端启动助手
                Bootstrap bootstrap = new Bootstrap();
                bootstrap.group(group)   //将线程组放入启动助手里面
                        .channel(NioSocketChannel.class)  //指定通道
                        .handler(new ChannelInitializer<SocketChannel>() {
                            @Override
                            protected void initChannel(SocketChannel socketChannel) throws Exception {
                                socketChannel.pipeline().addLast("decoder", new StringDecoder());
                                socketChannel.pipeline().addLast("encoder", new StringEncoder());
                                socketChannel.pipeline().addLast(new NettyClientHandler());
                            }
                        });
                ChannelFuture sync = bootstrap.connect(host, port).sync();
                sync.channel().closeFuture().sync();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                group.shutdownGracefully();
            }
        }
    
        public static void main(String[] args) {
            NettyClient nettyClient = new NettyClient(9999, "127.0.0.1");
            nettyClient.run();
        }
    
    }
    

    上述代码编写了一个客户端程序,配置了线程组,配置了自定义的业务处理类,并启动连接 了服务器端。最终运行效果如下图所示:

    服务端:

    客户端:

     

    展开全文
  • NETTY框架使用

    2019-10-06 12:15:39
    Netty 是基于 Java NIO 的异步事件驱动的网络应用框架使用 Netty 可以快速开发网络应用,Netty 提供了高层次的抽象来简化 TCP 和 UDP 服务器的编程,但是你仍然可以使用底层的 API。 Netty 的内部实现是很复杂的...

    一、Netty 简介

    Netty 是基于 Java NIO 的异步事件驱动的网络应用框架,使用 Netty 可以快速开发网络应用,Netty 提供了高层次的抽象来简化 TCP 和 UDP 服务器的编程,但是你仍然可以使用底层的 API。

    Netty 的内部实现是很复杂的,但是 Netty 提供了简单易用的API从网络处理代码中解耦业务逻辑。Netty 是完全基于 NIO 实现的,所以整个 Netty 都是异步的。

    Netty 是最流行的 NIO 框架,它已经得到成百上千的商业、商用项目验证,许多框架和开源组件的底层 rpc 都是使用的 Netty,如 Dubbo、Elasticsearch 等等。下面是官网给出的一些 Netty 的特性:

    设计方面

    • 对各种传输协议提供统一的 API(使用阻塞和非阻塞套接字时候使用的是同一个 API,只是需要设置的参数不一样)。
    • 基于一个灵活、可扩展的事件模型来实现关注点清晰分离。
    • 高度可定制的线程模型——单线程、一个或多个线程池。
    • 真正的无数据报套接字(UDP)的支持(since 3.1)。

    易用性

    • 完善的 Javadoc 文档和示例代码。
    • 不需要额外的依赖,JDK 5 (Netty 3.x) 或者 JDK 6 (Netty 4.x) 已经足够。

    性能

    • 更好的吞吐量,更低的等待延迟。
    • 更少的资源消耗。
    • 最小化不必要的内存拷贝。

    安全性

    • 完整的 SSL/TLS 和 StartTLS 支持

    对于初学者,上面的特性我们在脑中有个简单了解和印象即可, 下面开始我们的实战部分。

    二、一个简单 Http 服务器

    开始前说明下我这里使用的开发环境是 IDEA+Gradle+Netty4,当然你使用 Eclipse 和 Maven 都是可以的,然后在 Gradle 的 build 文件中添加依赖 compile 'io.netty:netty-all:4.1.26.Final',这样就可以编写我们的 Netty 程序了,正如在前面介绍 Netty 特性中提到的,Netty 不需要额外的依赖。

    第一个示例我们使用 Netty 编写一个 Http 服务器的程序,启动服务我们在浏览器输入网址来访问我们的服务,便会得到服务端的响应。功能很简单,下面我们看看具体怎么做?

    首先编写服务启动类

    复制代码
    public class HttpServer {
        public static void main(String[] args) { //构造两个线程组 EventLoopGroup bossrGroup = new NioEventLoopGroup(); EventLoopGroup workerGroup = new NioEventLoopGroup(); try { //服务端启动辅助类 ServerBootstrap bootstrap = new ServerBootstrap(); bootstrap.group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class) .childHandler(new HttpServerInitializer()); ChannelFuture future = bootstrap.bind(8080).sync(); //等待服务端口关闭  future.channel().closeFuture().sync(); } catch (InterruptedException e) { e.printStackTrace(); }finally { // 优雅退出,释放线程池资源  bossGroup.shutdownGracefully(); workerGroup.shutdownGracefully(); } } }
    复制代码

     

    在编写 Netty 程序时,一开始都会生成 NioEventLoopGroup 的两个实例,分别是 bossGroup 和 workerGroup,也可以称为 parentGroup 和 childGroup,为什么创建这两个实例,作用是什么?可以这么理解,bossGroup 和 workerGroup 是两个线程池, 它们默认线程数为 CPU 核心数乘以 2,bossGroup 用于接收客户端传过来的请求,接收到请求后将后续操作交由 workerGroup 处理。

    在这里我向大家推荐一个架构学习交流群。交流学习群号:747981058 里面会分享一些资深架构师录制的视频录像:有Spring,MyBatis,Netty源码分析,高并发、高性能、分布式、微服务架构的原理,JVM性能优化、分布式架构等这些成为架构师必备的知识体系。

    接下来我们生成了一个服务启动辅助类的实例 bootstrap,boostrap 用来为 Netty 程序的启动组装配置一些必须要组件,例如上面的创建的两个线程组。channel 方法用于指定服务器端监听套接字通道 NioServerSocketChannel,其内部管理了一个 Java NIO 中的ServerSocketChannel实例。

    channelHandler 方法用于设置业务职责链,责任链是我们下面要编写的,责任链具体是什么,它其实就是由一个个的 ChannelHandler 串联而成,形成的链式结构。正是这一个个的 ChannelHandler 帮我们完成了要处理的事情。

    接着我们调用了 bootstrap 的 bind 方法将服务绑定到 8080 端口上,bind 方法内部会执行端口绑定等一系列操,使得前面的配置都各就各位各司其职,sync 方法用于阻塞当前 Thread,一直到端口绑定操作完成。接下来一句是应用程序将会阻塞等待直到服务器的 Channel 关闭。

    启动类的编写大体就是这样了,下面要编写的就是上面提到的责任链了。如何构建一个链,在 Netty 中很简单,不需要我们做太多,代码如下:

    复制代码
    public class HttpServerInitializer extends ChannelInitializer<SocketChannel> {
        protected void initChannel(SocketChannel sc) throws Exception { ChannelPipeline pipeline = sc.pipeline(); //处理http消息的编解码 pipeline.addLast("httpServerCodec", new HttpServerCodec()); //添加自定义的ChannelHandler pipeline.addLast("httpServerHandler", new HttpServerHandler()); } }
    复制代码

     

    我们自定义一个类 HttpServerInitializer 继承 ChannelInitializer 并实现其中的 initChannel方法。

    ChannelInitializer 继承 ChannelInboundHandlerAdapter,用于初始化 Channel 的 ChannelPipeline。通过 initChannel 方法参数 sc 得到 ChannelPipeline 的一个实例。

    当一个新的连接被接受时, 一个新的 Channel 将被创建,同时它会被自动地分配到它专属的 ChannelPipeline。

    ChannelPipeline 提供了 ChannelHandler 链的容器,推荐读者仔细自己看看 ChannelPipeline 的 Javadoc,文章后面也会继续说明 ChannelPipeline 的内容。

    Netty 是一个高性能网络通信框架,同时它也是比较底层的框架,想要 Netty 支持 Http(超文本传输协议),必须要给它提供相应的编解码器。

    所以我们这里使用 Netty 自带的 Http 编解码组件 HttpServerCodec 对通信数据进行编解码,HttpServerCodec 是 HttpRequestDecoder 和 HttpResponseEncoder 的组合,因为在处理 Http 请求时这两个类是经常使用的,所以 Netty 直接将他们合并在一起更加方便使用。所以对于上面的代码:

    pipeline.addLast("httpServerCodec", new HttpServerCodec())

     

    我们替换成如下两行也是可以的。

    pipeline.addLast("httpResponseEndcoder", new HttpResponseEncoder());
    pipeline.addLast("HttpRequestDecoder", new HttpRequestDecoder());

     

    通过 addLast 方法将一个一个的 ChannelHandler 添加到责任链上并给它们取个名称(不取也可以,Netty 会给它个默认名称),这样就形成了链式结构。在请求进来或者响应出去时都会经过链上这些 ChannelHandler 的处理。

    最后再向链上加入我们自定义的 ChannelHandler 组件,处理自定义的业务逻辑。下面就是我们自定义的 ChannelHandler。

    复制代码
    public class HttpServerChannelHandler0 extends SimpleChannelInboundHandler<HttpObject> {
        private HttpRequest request; @Override protected void channelRead0(ChannelHandlerContext ctx, HttpObject msg) throws Exception { if (msg instanceof HttpRequest) { request = (HttpRequest) msg; request.method(); String uri = request.uri(); System.out.println("Uri:" + uri); } if (msg instanceof HttpContent) { HttpContent content = (HttpContent) msg; ByteBuf buf = content.content(); System.out.println(buf.toString(io.netty.util.CharsetUtil.UTF_8)); ByteBuf byteBuf = Unpooled.copiedBuffer("hello world", CharsetUtil.UTF_8); FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, byteBuf); response.headers().add(HttpHeaderNames.CONTENT_TYPE, "text/plain"); response.headers().add(HttpHeaderNames.CONTENT_LENGTH, byteBuf.readableBytes()); ctx.writeAndFlush(response); } } }
    复制代码

     

    至此一个简单的 Http 服务器就完成了。首先我们来看看效果怎样,我们运行 HttpServer 中的 main 方法。让后使用 Postman 这个工具来测试下,使用 post 请求方式(也可以 get,但没有请求体),并一个 json 格式数据作为请求体发送给服务端,服务端返回给我们一个hello world字符串。

     

    服务端控制台打印如下:

     

    对于自定义的 ChannelHandler, 一般会继承 Netty 提供的SimpleChannelInboundHandler类,并且对于 Http 请求我们可以给它设置泛型参数为 HttpOjbect 类,然后覆写 channelRead0 方法,在 channelRead0 方法中编写我们的业务逻辑代码,此方法会在接收到服务器数据后被系统调用。

    Netty 的设计中把 Http 请求分为了 HttpRequest 和 HttpContent 两个部分,HttpRequest 主要包含请求头、请求方法等信息,HttpContent 主要包含请求体的信息。

    所以上面的代码我们分两块来处理。在 HttpContent 部分,首先输出客户端传过来的字符,然后通过 Unpooled 提供的静态辅助方法来创建未池化的 ByteBuf 实例, Java NIO 提供了 ByteBuffer 作为它的字节容器,Netty 的 ByteBuffer 替代品是 ByteBuf。

    接着构建一个 FullHttpResponse 的实例,并为它设置一些响应参数,最后通过 writeAndFlush 方法将它写回给客户端。

    上面这样获取请求和消息体则相当不方便,Netty 又提供了另一个类 FullHttpRequest,FullHttpRequest 包含请求的所有信息,它是一个接口,直接或者间接继承了 HttpRequest 和 HttpContent,它的实现类是 DefalutFullHttpRequest。

    因此我们可以修改自定义的 ChannelHandler 如下:

    复制代码
    public class HttpServerChannelHandler extends SimpleChannelInboundHandler<FullHttpRequest> {
     
        protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest msg) throws Exception { ctx.channel().remoteAddress(); FullHttpRequest request = msg; System.out.println("请求方法名称:" + request.method().name()); System.out.println("uri:" + request.uri()); ByteBuf buf = request.content(); System.out.print(buf.toString(CharsetUtil.UTF_8)); ByteBuf byteBuf = Unpooled.copiedBuffer("hello world", CharsetUtil.UTF_8); FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, byteBuf); response.headers().add(HttpHeaderNames.CONTENT_TYPE, "text/plain"); response.headers().add(HttpHeaderNames.CONTENT_LENGTH, byteBuf.readableBytes()); ctx.writeAndFlush(response); } }
    复制代码

     

    这样修改就可以了吗,如果你去启动程序运行看看,是会抛异常的。前面说过 Netty 是一个很底层的框架,对于将请求合并为一个 FullRequest 是需要代码实现的,然而这里我们并不需要我们自己动手去实现,Netty 为我们提供了一个 HttpObjectAggregator 类,这个 ChannelHandler作用就是将请求转换为单一的 FullHttpReques。

    所以在我们的 ChannelPipeline 中添加一个 HttpObjectAggregator 的实例即可。

    复制代码
    public class HttpServerInitializer extends ChannelInitializer<SocketChannel> {
        protected void initChannel(SocketChannel sc) { ChannelPipeline pipeline = sc.pipeline(); //处理http消息的编解码 pipeline.addLast("httpServerCodec", new HttpServerCodec()); pipeline.addLast("aggregator", new HttpObjectAggregator(65536)); //添加自定义的ChannelHandler pipeline.addLast("httpServerHandler", new HttpServerChannelHandler0()); } }
    复制代码

     

    启动程序运行,一切都顺畅了,好了,这个简单 Http 的例子就 OK 了。

    在这里我向大家推荐一个架构学习交流群。交流学习群号:747981058 里面会分享一些资深架构师录制的视频录像:有Spring,MyBatis,Netty源码分析,高并发、高性能、分布式、微服务架构的原理,JVM性能优化、分布式架构等这些成为架构师必备的知识体系。

    三、编写 Netty 客户端

    上面的两个示例中我们都是以 Netty 做为服务端,接下来看看如何编写 Netty 客户端,以第一个 Http 服务的例子为基础,编写一个访问 Http 服务的客户端。

    复制代码
    public class HttpClient {
    
       public static void main(String[] args) throws Exception { String host = "127.0.0.1"; int port = 8080; 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 pipeline = ch.pipeline(); pipeline.addLast(new HttpClientCodec()); pipeline.addLast(new HttpObjectAggregator(65536)); pipeline.addLast(new HttpClientHandler()); } }); // 启动客户端. ChannelFuture f = b.connect(host, port).sync(); f.channel().closeFuture().sync(); } finally { group.shutdownGracefully(); } } }
    复制代码

     

    客户端启动类编写基本和服务端类似,在客户端我们只用到了一个线程池,服务端使用了两个,因为服务端要处理 n 条连接,而客户端相对来说只处理一条,因此一个线程池足以。

    然后服务端启动辅助类使用的是 ServerBootstrap,而客户端换成了 Bootstrap。通过 Bootstrap 组织一些必要的组件,为了方便,在 handler 方法中我们使用匿名内部类的方式来构建 ChannelPipeline 链容器。最后通过 connect 方法连接服务端。

    接着编写 HttpClientHandler 类。

    复制代码
    public class HttpClientHandler extends SimpleChannelInboundHandler<FullHttpResponse> {
     
        @Override
        public void channelActive(ChannelHandlerContext ctx) throws Exception { URI uri = new URI("http://127.0.0.1:8080"); String msg = "Are you ok?"; FullHttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, uri.toASCIIString(), Unpooled.wrappedBuffer(msg.getBytes("UTF-8"))); // 构建http请求 // request.headers().set(HttpHeaderNames.HOST, "127.0.0.1"); // request.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE);  request.headers().set(HttpHeaderNames.CONTENT_LENGTH, request.content().readableBytes()); // 发送http请求  ctx.channel().writeAndFlush(request); } @Override public void channelRead0(ChannelHandlerContext ctx, FullHttpResponse msg) { FullHttpResponse response = msg; response.headers().get(HttpHeaderNames.CONTENT_TYPE); ByteBuf buf = response.content(); System.out.println(buf.toString(io.netty.util.CharsetUtil.UTF_8)); } }
    复制代码

     

    在 HttpClientHandler 类中,我们覆写了 channelActive 方法,当连接建立时,此方法会被调用,我们在方法中构建了一个 FullHttpRequest 对象,并且通过 writeAndFlush 方法将请求发送出去。

    channelRead0 方法用于处理服务端返回给我们的响应,打印服务端返回给客户端的信息。至此,Netty 客户端的编写就完成了,我们先开启服务端,然后开启客户端就可以看到效果了。

    希望通过前面介绍的几个例子能让大家基本知道如何编写 Netty 客户端和服务端,下面我们来说说 Netty 程序为什么是这样编写的,这也是 Netty 中最为重要的一部分知识,可以让你在编写 netty 程序时做到心中有数。

    在这里我向大家推荐一个架构学习交流群。交流学习群号:747981058 里面会分享一些资深架构师录制的视频录像:有Spring,MyBatis,Netty源码分析,高并发、高性能、分布式、微服务架构的原理,JVM性能优化、分布式架构等这些成为架构师必备的知识体系。

    四、Channel、ChannelPipeline、ChannelHandler、ChannelHandlerContext 之间的关系

    在编写 Netty 程序时,经常跟我们打交道的是上面这几个对象,这也是 Netty 中几个重要的对象,下面我们来看看它们之间有什么样的关系。

    Netty 中的 Channel 是框架自己定义的一个通道接口,Netty 实现的客户端 NIO 套接字通道是 NioSocketChannel,提供的服务器端 NIO 套接字通道是 NioServerSocketChannel。

    当服务端和客户端建立一个新的连接时, 一个新的 Channel 将被创建,同时它会被自动地分配到它专属的 ChannelPipeline。

    ChannelPipeline 是一个拦截流经 Channel 的入站和出站事件的 ChannelHandler 实例链,并定义了用于在该链上传播入站和出站事件流的 API。那么就很容易看出这些 ChannelHandler 之间的交互是组成一个应用程序数据和事件处理逻辑的核心。

     

    上图描述了 IO 事件如何被一个 ChannelPipeline 的 ChannelHandler 处理的。

    ChannelHandler分为 ChannelInBoundHandler 和 ChannelOutboundHandler 两种,如果一个入站 IO 事件被触发,这个事件会从第一个开始依次通过 ChannelPipeline中的 ChannelInBoundHandler,先添加的先执行。

    若是一个出站 I/O 事件,则会从最后一个开始依次通过 ChannelPipeline 中的 ChannelOutboundHandler,后添加的先执行,然后通过调用在 ChannelHandlerContext 中定义的事件传播方法传递给最近的 ChannelHandler。

    在 ChannelPipeline 传播事件时,它会测试 ChannelPipeline 中的下一个 ChannelHandler 的类型是否和事件的运动方向相匹配。

    如果某个ChannelHandler不能处理则会跳过,并将事件传递到下一个ChannelHandler,直到它找到和该事件所期望的方向相匹配的为止。

    假设我们创建下面这样一个 pipeline:

    复制代码
    ChannelPipeline p = ...;
    p.addLast("1", new InboundHandlerA());
    p.addLast("2", new InboundHandlerB());
    p.addLast("3", new OutboundHandlerA()); p.addLast("4", new OutboundHandlerB()); p.addLast("5", new InboundOutboundHandlerX()); 
    复制代码

     

    在上面示例代码中,inbound 开头的 handler 意味着它是一个ChannelInBoundHandler。outbound 开头的 handler 意味着它是一个 ChannelOutboundHandler。

    当一个事件进入 inbound 时 handler 的顺序是 1,2,3,4,5;当一个事件进入 outbound 时,handler 的顺序是 5,4,3,2,1。在这个最高准则下,ChannelPipeline 跳过特定 ChannelHandler 的处理:

    • 3,4 没有实现 ChannelInboundHandler,因而一个 inbound 事件的处理顺序是 1,2,5。
    • 1,2 没有实现 ChannelOutBoundhandler,因而一个 outbound 事件的处理顺序是 5,4,3。
    • 5 同时实现了 ChannelInboundHandler 和 channelOutBoundHandler,所以它同时可以处理 inbound 和 outbound 事件。

    ChannelHandler 可以通过添加、删除或者替换其他的 ChannelHandler 来实时地修改 ChannelPipeline 的布局。

    (它也可以将它自己从 ChannelPipeline 中移除。)这是 ChannelHandler 最重要的能力之一。

    ChannelHandlerContext 代表了 ChannelHandler 和 ChannelPipeline 之间的关联,每当有 ChannelHandler 添加到 ChannelPipeline 中时,都会创建 ChannelHandlerContext。

    ChannelHandlerContext 的主要功能是管理它所关联的 ChannelHandler 和在同一个 ChannelPipeline 中的其他 ChannelHandler 之间的交互。事件从一个 ChannelHandler 到下一个 ChannelHandler 的移动是由 ChannelHandlerContext 上的调用完成的。

     

    但是有些时候不希望总是从 ChannelPipeline 的第一个 ChannelHandler 开始事件,我们希望从一个特定的 ChannelHandler 开始处理。

    你必须引用于此 ChannelHandler 的前一个 ChannelHandler 关联的 ChannelHandlerContext,利用它调用与自身关联的 ChannelHandler 的下一个 ChannelHandler。

    如下:

    ChannelHandlerContext ctx = ...;   // 获得 ChannelHandlerContext引用
    // write()将会把缓冲区发送到下一个ChannelHandler  
    ctx.write(Unpooled.copiedBuffer("Netty in Action", CharsetUtil.UTF_8));
     
    //流经整个pipeline ctx.channel().write(Unpooled.copiedBuffer("Netty in Action", CharsetUtil.UTF_8));

     

    如果我们想有一些事件流全部通过 ChannelPipeline,有两个不同的方法可以做到:

    • 调用 Channel 的方法
    • 调用 ChannelPipeline 的方法
      这两个方法都可以让事件流全部通过 ChannelPipeline,无论从头部还是尾部开始,因为它主要依赖于事件的性质。如果是一个 “ 入站 ” 事件,它开始于头部;若是一个 “ 出站 ” 事件,则开始于尾部。

    那为什么你可能会需要在 ChannelPipeline 某个特定的位置开始传递事件呢?

    • 减少因为让事件穿过那些对它不感兴趣的 ChannelHandler 而带来的开销
    • 避免事件被那些可能对它感兴趣的 ChannlHandler 处理

    五、Netty 线程模型

    在这里我向大家推荐一个架构学习交流群。交流学习群号:747981058 里面会分享一些资深架构师录制的视频录像:有Spring,MyBatis,Netty源码分析,高并发、高性能、分布式、微服务架构的原理,JVM性能优化、分布式架构等这些成为架构师必备的知识体系。

    在前面的示例中我们程序一开始都会生成两个 NioEventLoopGroup 的实例,为什么需要这两个实例呢?这两个实例可以说是 Netty 程序的源头,其背后是由 Netty 线程模型决定的。

    Netty 线程模型是典型的 Reactor 模型结构,其中常用的 Reactor 线程模型有三种,分别为:Reactor 单线程模型、Reactor 多线程模型和主从 Reactor 多线程模型。

    而在 Netty 的线程模型并非固定不变,通过在启动辅助类中创建不同的 EventLoopGroup 实例并通过适当的参数配置,就可以支持上述三种 Reactor 线程模型。

    Reactor 线程模型

    Reactor 单线程模型

    Reactor 单线程模型指的是所有的 IO 操作都在同一个 NIO 线程上面完成。作为 NIO 服务端接收客户端的 TCP 连接,作为 NIO 客户端向服务端发起 TCP 连接,读取通信对端的请求或向通信对端发送消息请求或者应答消息。

    由于 Reactor 模式使用的是异步非阻塞 IO,所有的 IO 操作都不会导致阻塞,理论上一个线程可以独立处理所有 IO 相关的操作。

     

    Netty 使用单线程模型的的方式如下:

    EventLoopGroup bossGroup = new NioEventLoopGroup(1);
    ServerBootstrap b = new ServerBootstrap();
    b.group(bossGroup)
     .channel(NioServerSocketChannel.class) ...

     

    在实例化 NioEventLoopGroup 时,构造器参数是 1,表示 NioEventLoopGroup 的线程池大小是 1。然后接着我们调用 b.group(bossGroup) 设置了服务器端的 EventLoopGroup,因此 bossGroup和 workerGroup 就是同一个 NioEventLoopGroup 了。

    Reactor 多线程模型

    对于一些小容量应用场景,可以使用单线程模型,但是对于高负载、大并发的应用却不合适,需要对该模型进行改进,演进为 Reactor 多线程模型。

    Rector 多线程模型与单线程模型最大的区别就是有一组 NIO 线程处理 IO 操作。

    在该模型中有专门一个 NIO 线程 -Acceptor 线程用于监听服务端,接收客户端的 TCP 连接请求;而 1 个 NIO 线程可以同时处理N条链路,但是 1 个链路只对应 1 个 NIO 线程,防止发生并发操作问题。

    网络 IO 操作-读、写等由一个 NIO 线程池负责,线程池可以采用标准的 JDK 线程池实现,它包含一个任务队列和 N 个可用的线程,由这些 NIO 线程负责消息的读取、解码、编码和发送。

     

     

    Netty 中实现多线程模型的方式如下:

    EventLoopGroup bossGroup = new NioEventLoopGroup(1);
    EventLoopGroup workerGroup = new NioEventLoopGroup();
    ServerBootstrap b = new ServerBootstrap(); b.group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class) ...

     

    bossGroup 中只有一个线程,而 workerGroup 中的线程是 CPU 核心数乘以 2,那么就对应 Recator 的多线程模型。

    主从 Reactor 多线程模型

    在并发极高的情况单独一个 Acceptor 线程可能会存在性能不足问题,为了解决性能问题,产生主从 Reactor 多线程模型。

    主从 Reactor 线程模型的特点是:服务端用于接收客户端连接的不再是 1 个单独的 NIO 线程,而是一个独立的 NIO 线程池。

    Acceptor 接收到客户端 TCP 连接请求处理完成后,将新创建的 SocketChannel 注册到 IO 线程池(sub reactor 线程池)的某个 IO 线程上,由它负责 SocketChannel 的读写和编解码工作。

    Acceptor 线程池仅仅只用于客户端的登陆、握手和安全认证,一旦链路建立成功,就将链路注册到后端 subReactor 线程池的 IO 线程上,由 IO 线程负责后续的 IO 操作。

     

    根据前面所讲的两个线程模型,很容想到 Netty 实现多线程的方式如下:

    EventLoopGroup bossGroup = new NioEventLoopGroup(4);
    EventLoopGroup workerGroup = new NioEventLoopGroup();
    ServerBootstrap b = new ServerBootstrap(); b.group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class) ...

     

    但是,在 Netty 的服务器端的 acceptor 阶段,没有使用到多线程, 因此上面的主从多线程模型在 Netty 的实现是有误的。

    服务器端的 ServerSocketChannel 只绑定到了 bossGroup 中的一个线程,因此在调用 Java NIO 的 Selector.select 处理客户端的连接请求时,实际上是在一个线程中的,所以对只有一个服务的应用来说,bossGroup 设置多个线程是没有什么作用的,反而还会造成资源浪费。

    至于 Netty 中的 bossGroup 为什么使用线程池,我在 stackoverflow 找到一个对于此问题的讨论 。

    the creator of Netty says multiple boss threads are useful if we share NioEventLoopGroup between different server bootstraps

    EventLoopGroup 和 EventLoop

    当系统在运行过程中,如果频繁的进行线程上下文切换,会带来额外的性能损耗。多线程并发执行某个业务流程,业务开发者还需要时刻对线程安全保持警惕,哪些数据可能会被并发修改,如何保护?这不仅降低了开发效率,也会带来额外的性能损耗。

    为了解决上述问题,Netty采用了串行化设计理念,从消息的读取、编码以及后续 ChannelHandler 的执行,始终都由 IO 线程 EventLoop 负责,这就意外着整个流程不会进行线程上下文的切换,数据也不会面临被并发修改的风险。

    EventLoopGroup 是一组 EventLoop 的抽象,一个 EventLoopGroup 当中会包含一个或多个 EventLoop,EventLoopGroup 提供 next 接口,可以从一组 EventLoop 里面按照一定规则获取其中一个 EventLoop 来处理任务。

    在 Netty 服务器端编程中我们需要 BossEventLoopGroup 和 WorkerEventLoopGroup 两个 EventLoopGroup 来进行工作。

    BossEventLoopGroup 通常是一个单线程的 EventLoop,EventLoop 维护着一个注册了 ServerSocketChannel 的 Selector 实例,EventLoop 的实现涵盖 IO 事件的分离,和分发(Dispatcher),EventLoop 的实现充当 Reactor 模式中的分发(Dispatcher)的角色。

    所以通常可以将 BossEventLoopGroup 的线程数参数为 1。

    BossEventLoop 只负责处理连接,故开销非常小,连接到来,马上按照策略将 SocketChannel 转发给 WorkerEventLoopGroup,WorkerEventLoopGroup 会由 next 选择其中一个 EventLoop 来将这 个SocketChannel 注册到其维护的 Selector 并对其后续的 IO 事件进行处理。

    ChannelPipeline 中的每一个 ChannelHandler 都是通过它的 EventLoop(I/O 线程)来处理传递给它的事件的。所以至关重要的是不要阻塞这个线程,因为这会对整体的 I/O 处理产生严重的负面影响。但有时可能需要与那些使用阻塞 API 的遗留代码进行交互。

    对于这种情况, ChannelPipeline 有一些接受一个 EventExecutorGroup 的 add() 方法。如果一个事件被传递给一个自定义的 EventExecutorGroup, DefaultEventExecutorGroup 的默认实现。

    就是在把 ChannelHanders 添加到 ChannelPipeline 的时候,指定一个 EventExecutorGroup,ChannelHandler 中所有的方法都将会在这个指定的 EventExecutorGroup 中运行。

    static final EventExecutor group = new DefaultEventExecutorGroup(16);
    ...
    ChannelPipeline p = ch.pipeline();
    pipeline.addLast(group, "handler", new MyChannelHandler()); 

     

    最后小结一下:(如果你还没明白,可以看一下群里面的视频解析)
    • NioEventLoopGroup 实际上就是个线程池,一个 EventLoopGroup 包含一个或者多个 EventLoop;
    • 一个 EventLoop 在它的生命周期内只和一个 Thread 绑定;
    • 所有有 EnventLoop 处理的 I/O 事件都将在它专有的 Thread 上被处理;
    • 一个 Channel 在它的生命周期内只注册于一个 EventLoop;
    • 每一个 EventLoop 负责处理一个或多个 Channel;

    转载于:https://www.cnblogs.com/qiu18359243869/p/11026388.html

    展开全文
  • Netty框架学习之(一):Netty框架简介

    万次阅读 多人点赞 2018-05-23 18:43:19
    官方定义为:”Netty 是一款异步的事件驱动的网络应用程序框架,支持快速地开发可维护的高性能的面向协议的服务器 和客户端”,按照惯例贴上一张High Level的架构图: 纵观Java系的多种服务器/大数据框架,都离不...

    1. 简介

    官方定义为:”Netty 是一款异步的事件驱动的网络应用程序框架,支持快速地开发可维护的高性能的面向协议的服务器
    和客户端”,按照惯例贴上一张High Level的架构图:

    这里写图片描述

    纵观Java系的多种服务器/大数据框架,都离不开Netty做出的贡献,本文对Netty做一个简单的概述

    2. 主要特性

    Netty有很多重要的特性,主要特性如下:
    - 优雅的设计
    - 统一的API接口,支持多种传输类型,例如OIO,NIO
    - 简单而强大的线程模型
    - 丰富的文档
    - 卓越的性能
    - 拥有比原生Java API 更高的性能与更低的延迟
    - 基于池化和复用技术,使资源消耗更低
    - 安全性
    - 完整的SSL/TLS以及StartTLS支持
    - 可用于受限环境,如Applet以及OSGI

    Netty的以上特性,比较适合客户端数据较大的请求/处理场景,例如web服务器等,要想知道有哪些系统使用了Netty,可以参考:http://netty.io/wiki/adopters.html

    3. 主要术语

    在正式开始之前,先对Netty涉及到的一些术语做个简单的说明

    3.1 IO模型:BIO/NIO/Netty

    3.1.1 BIO(Blocking IO):阻塞IO

    早期的Java API(java.net)提供了由本地系统套接字库提供的所谓的阻塞函数,样例代码如下:

    ServerSocket serverSocket = new ServerSocket(portNumber);
    Socket clientSocket = serverSocket.accept();
    BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
    PrintWriter out =new PrintWriter(clientSocket.getOutputStream(), true);
    String request, response;
    while ((request = in.readLine()) != null) {
        if ("Done".equals(request)) {
            break;
    }
    response = processRequest(request);
    out.println(response);
    }
    

    这段代码片段将只能同时处理一个连接,要管理多个并发客户端,需要为每个新的客户端
    Socket 创建一个新的 Thread,线程模型如下图所示:

    这里写图片描述

    该种模型存在以下两个问题:
    1. 在任何时候都可能有大量的线程处于休眠状态,只是等待输入或者输出数据就绪,这可能算是一种资源浪费
    2. 需要为每个线程的调用栈都分配内存
    3. 即使 Java 虚拟机(JVM) 在物理上可以支持非常大数量的线程, 但是远在到达该极限之前, 上下文切换所带来的开销就会带来麻烦

    3.1.2 NIO(Non Blocking IO):非阻塞IO

    Java的NIO特性在JDK 1.4中引入,其结构如下:

    这里写图片描述

    从该图可以看出Selector 是Java 的非阻塞 I/O 实现的关键。它使用了事件通知 API
    以确定在一组非阻塞套接字中有哪些已经就绪能够进行 I/O 相关的操作。因为可以在任何的时间检查任意的读操作或者写操作的完成状态。该种模型下,一个单一的线程便可以处理多个并发的连接。
    与BIO相比,该模型有以下特点:
    1. 使用较少的线程便可以处理许多连接,因此也减少了内存管理和上下文切换所带来开销
    2. 当没有 I/O 操作需要处理的时候,线程也可以被用于其他任务

    虽然Java 的NIO在性能上比BIO已经相当的优秀,但是要做到如此正确和安全并
    不容易。特别是,在高负载下可靠和高效地处理和调度 I/O 操作是一项繁琐而且容易出错的任务,此时就时Netty上场的时间了。

    3.1.3 Netty

    Netty对NIO的API进行了封装,通过以下手段让性能又得到了一定程度的提升
    1. 使用多路复用技术,提高处理连接的并发性
    2. 零拷贝:
    1. Netty的接收和发送数据采用DIRECT BUFFERS,使用堆外直接内存进行Socket读写,不需要进行字节缓冲区的二次拷贝
    2. Netty提供了组合Buffer对象,可以聚合多个ByteBuffer对象进行一次操作
    3. Netty的文件传输采用了transferTo方法,它可以直接将文件缓冲区的数据发送到目标Channel,避免了传统通过循环write方式导致的内存拷贝问题
    3. 内存池:为了减少堆外直接内存的分配和回收产生的资源损耗问题,Netty提供了基于内存池的缓冲区重用机制
    4. 使用主从Reactor多线程模型,提高并发性
    5. 采用了串行无锁化设计,在IO线程内部进行串行操作,避免多线程竞争导致的性能下降
    6. 默认使用Protobuf的序列化框架
    7. 灵活的TCP参数配置

    详细说明,可参考: http://www.infoq.com/cn/articles/netty-high-performance#anch111813

    3.1.4 简单的性能测试

    通过在本地分别使用BIO,NIO,Netty NIO实现了一个简单的服务端程序(该程序接收到请求后,sleep 1毫秒,并返回简单的一句话)分别对三种方式使用Jemeter进行性能测试(一百个并发,每个并发发送一百个相同消息),结果如下:

    单线程的java net:

    这里写图片描述

    NIO:
    这里写图片描述

    Netty NIO:
    这里写图片描述

    以上结果或是受到其他条件的影响,结果仅供供参考

    3.2 Callback:

    回调在广泛的编程场景中都有应用,一般是在完成某个特定的操作后对相关方法进行调用。

    Netty 在内部使用回调来处理事件;当一个回调被触发时,相关的事件可以被一个 interfaceChannelHandler 的实现处理,例如Channel激活时会调用ChannelActive方法,样例代码如下:

    public class ConnectHandler extends ChannelInboundHandlerAdapter {
    @Override
        public void channelActive(ChannelHandlerContext ctx)throws Exception {
            System.out.println("Client " + ctx.channel().remoteAddress() + connected");
    
        }
    }

    3.3 Future:

    Future一般用在当执行异步操作时需要获取未来的某个时候才能获取到的结果。

    JDK 预置了 interface java.util.concurrent.Future,但是其所提供的实现,只
    允许手动检查对应的操作是否已经完成,或者一直阻塞直到它完成。这是非常繁琐的,所以 Netty提供了它自己的实现——ChannelFuture,用于在执行异步操作的时候使用。

    ChannelFuture提供了几种额外的方法,这些方法使得我们能够注册一个或者多个
    ChannelFutureListener实例。监听器的回调方法operationComplete(),将会在对应的
    操作完成时被调用。然后监听器可以判断该操作是成功地完成了还是出错了。如果是后者,我们可以检索产生的Throwable。 通过使用ChannelFutureListener机制可以避免对
    操作结果进行手动检查。

    每个 Netty 的出站 I/O 操作都将返回一个ChannelFuture,即不会阻塞后续的操作。

    下面的例子中的connect()方法会直接返回,后续的成功或失败将由其注册的FutureListener来处理。

            try {
                // 使用异步的方式连接Server,不管成功失败,都是执行下面System.out的语句,最后的连接结果由FutureListener进行处理
                ChannelFuture future = bootstrap.connect();
                System.out.println("Finished connect operation");
                future.addListener((ChannelFutureListener) future1 -> {
                    if (future1.isSuccess()){
                        ByteBuf buffer = Unpooled.copiedBuffer(
                                "Hello", Charset.defaultCharset());
                        ChannelFuture wf = future1.channel()
                                .writeAndFlush(buffer);
                        System.out.println("Connect successful!");
                    }else{
                        System.out.println("Connect failed!");
                        Throwable cause = future1.cause();
                        cause.printStackTrace();
                    }
                });
                System.out.println("Finished connect operation2");
                future.channel().closeFuture().sync();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    

    最后的打印结果如下:

    Finished connect operation
    Finished connect operation2
    Connect failed!
    io.netty.channel.AbstractChannel$AnnotatedConnectException: Connection refused: no further information: localhost/127.0.0.1:8888
        at sun.nio.ch.SocketChannelImpl.checkConnect(Native Method)
        at sun.nio.ch.SocketChannelImpl.finishConnect(SocketChannelImpl.java:717)
        at io.netty.channel.socket.nio.NioSocketChannel.doFinishConnect(NioSocketChannel.java:325)
        at io.netty.channel.nio.AbstractNioChannel$AbstractNioUnsafe.finishConnect
        ...............................................
        Caused by: java.net.ConnectException: Connection refused: no further information
        ... 11 more

    3.4 Event

    Netty 使用不同的事件来通知状态的改变或者是操作的状态。事件可能包括:
    - 连接已被激活或者连接失活
    - 数据读取;
    - 用户事件;
    - 错误事件。
    - 打开或者关闭到远程节点的连接;
    - 将数据写到或者冲刷到套接字。

    每个事件都可以被分发给 ChannelHandler 类中的某个用户实现的方法。这是将事件驱动范式直接转换为应用程序逻辑处理比较理想的位置。
    下图展示了事件是怎么被处理的:

    这里写图片描述

    对每个事件可以进行,记录日志,数据转换,应用程序逻辑处理等操作,

    Netty 提供了大量预定义的可以开箱即用的 ChannelHandler 实现,包括用于各种协议
    (如 HTTP 和 SSL/TLS)的 ChannelHandler。后续博文会对一些Handler进行简单的介绍

    展开全文
  • Netty框架

    2019-10-01 19:40:54
    学习Netty框架,三连问:  什么是Netty框架?  为什么要用Netty框架?  怎么用Netty框架? 什么是Netty框架?  Netty是一个广受欢迎的异步事件驱动的Java开源网络应用程序框架,用于快速开发可维护的高...
  • 二.Netty框架简介 1.什么是Netty Netty 是一个基于NIO的客户、服务器端编程框架,使用Netty 可以确保你快速和简单的开发出一个网络应用,例如实现了某种协议的客户、服务端应用。Netty相当于简化和流线化了网络...
  • Netty框架使用protocol buffer协议

    千次阅读 2014-09-26 11:13:10
    关于netty和protobuf的使用前面文章you'shuo'g
  • netty框架

    2019-06-04 17:37:59
    Netty 是一个异步事件驱动的网络应用程序框架,用于快速开发可维护的高性能协议服务器和客户端。 JDK 原生 NIO JDK 原生也有一套网络应用程序 API,但是存在一系列问题,主要如下: NIO 的类库和 API 繁杂,...
  • Netty框架学习: 包含netty的各个面试知识点,以及粘包与半包的解析,以及实例和底层tcp协议的滑动窗口的解析;
  • Netty框架入门案例,代码示例

    千次阅读 2019-02-02 14:02:40
    Netty框架入门案例 pom文件 &lt;?xml version="1.0" encoding="UTF-8"?&gt; &lt;project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="...
  • Netty是由JBOSS提供的一个Java开源框架,现为Github独立项目。 Netty是一个异步的、基于事件驱动的网络应用框架,用以快速开发高性能,高可用的网络IO程序。 Netty主要针对TCP协议下,面向Client端高并发应用,或者...
  • Netty框架的作用 Netty框架体系结构 Netty框架Channel、ChannelHandler、Future、事件详解 前言 Netty框架是什么?Netty是一款用于快速开发高性能的网络应用程序的Java框架。它封装了网络编程的复杂性,使网络编程...
  • 在开发网络游戏的时候,Netty也常用于游戏服务器或网关的通信层框架,所以,可以通过学习RocketMQ是如何使用Netty框架,从中借鉴一此应用技巧。 在RocketMQ中的rocketmq-remoting项目就是针对网络层封装的项目...
  • 在SpringBoot中整合使用Netty框架 Netty是一个非常优秀的Socket框架。如果需要在SpringBoot开发的app中,提供Socket服务,那么Netty是不错的选择。 Netty与SpringBoot的整合,我想无非就是要整合几个地方 让netty跟...
  • 在SpringBoot中整合使用Netty框架 Netty是一个非常优秀的Socket框架。如果需要在SpringBoot开发的app中,提供Socket服务,那么Netty是不错的选择。 Netty与SpringBoot的整合,我想无非就是要整合几个地方 让netty跟...
  • Netty框架快速入门视频教程,共十课。从原理到源码分析再到案例实践。
  • netty-Netty框架简介

    2019-06-13 10:45:39
    概述:netty-网络通信框架库(第三方库),本质是对NIO的封装。 netty的实现: 我们通过maven来导入netty。在maven的pom.xml中实现对netty的依赖。 <dependencies> <dependency> <groupId>junit&...
  • 前阵子由于公司需要,接触到了网络传输框架...也就是说,Netty 是一个基于NIO的客户、服务器端编程框架使用Netty 可以确保你快速和简单的开发出一个网络应用,例如实现了某种协议的客户,服务端应用。Netty相当简化
  • 上篇讲解ChannelHandler的使用以及相关子类的使用,这一篇继续来讲讲ChannelPipeline与ChannelHandlerContext,它们三者的有效组合让Netty在处理业务逻辑上得心应手。在讲ChannelHandlerContext之前先来讲讲...
  • Netty框架介绍

    2017-03-20 16:46:00
    Netty框架介绍 转自:http://www.toutiao.com/i6398482015196283394/ 一 初步了解Netty Netty是由JBoss公司推出的一个高性能事件驱动型异步非阻塞的IO(NIO)框架。用于建立TCP等底层的连接,基于Netty可以建立高...
  • Netty框架多人聊天案例,代码示例

    千次阅读 2019-02-02 14:36:59
    Netty框架多人聊天案例,代码示例 pom &lt;?xml version="1.0" encoding="UTF-8"?&gt; &lt;project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 23,754
精华内容 9,501
关键字:

netty框架案例使用