[Netty学习笔记]八、Netty核心组件介绍

Netty核心组件介绍

在了解新知识之前,先来对前面的知识回顾下:

  1. Netty抽象出两组线程池,BossGroup专门负责接收客户端连接,WorkerGroup专门负责网络读写
  2. NioEventLoop表示一个不断循环执行处理任务的线程,每个NioEventLoop都有一个selector,用于监听绑定在其上的socket网络通道
  3. NioEventLoop内部采用串行化设计,从消息的读取->解码->编码->发送,始终由IO线程NioEventLoop负责
  4. NioEventLoopGroup下包含多个NioEventLoop
  5. 每个NioEventLoop中包含一个Selector,一个taskQueue
  6. 每个NioEventLoop的Selector上可以注册监听多个NioChannel
  7. 每个NioChannel只会绑定在唯一的NioEventLoop上
  8. 每个NioChannel都绑定有一个自己的ChannelPipeline
任务队列中Task的使用

关于示例代码以NettyServerHandler举例

1. 用户程序自定义的普通任务

public class NettyServerCustomTaskHandler extends NettyServerHandler {

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        //比如有个很耗时的业务->异步执行->提交到该channel对应的NioEventLoop下的taskQueue中
        ctx.channel().eventLoop().execute(() -> {
            try {
                Thread.sleep(5 * 1000);
                ctx.writeAndFlush(Unpooled.copiedBuffer("hello,这是一个耗时操作", CharsetUtil.UTF_8));
                System.out.println("channel code=" + ctx.channel().hashCode());
            } catch (Exception ex) {
                System.out.println("发生异常" + ex.getMessage());
            }
        });
    }

}

2.用户自定义定时任务

public class NettyServerCustomTaskHandler extends NettyServerHandler {

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        //用户自定义定时任务 ->该任务是提交到 scheduledTaskQueue 中
        ctx.channel().eventLoop().schedule(() -> {
            try {
                Thread.sleep(5 * 1000);
                ctx.writeAndFlush(Unpooled.copiedBuffer("hello,这是一个耗时操作", CharsetUtil.UTF_8));
                System.out.println("channel code=" + ctx.channel().hashCode());
            } catch (Exception ex) {
                System.out.println("发生异常" + ex.getMessage());
            }
        }, 5, TimeUnit.SECONDS);

    }

}

3.非当前Reactor线程调用channel的各种方法

例如在推送系统的业务线程里,根据用户标志找到对应的Channel引用,然后调用write类方法向该用户推送消息,就会进入到这种场景。最终的write会提交到任务队列中后被异步消费

异步模型

基本介绍

  1. 异步的概念和同步相对。当一个异步过程调用发出后,调用者不能立刻得到结果。实际处理这个调用的组件在完成后,通过状态通知和回调来通知调用者。
  2. Netty中的IO操作时异步的。包括Bind/Write/Connect等操作会简单的返回一个ChannelFuture。
  3. 调用者并不能立刻获得结果,而是通过Future-Listener机制,用户可以方便主动地获取或者通过通知机制获取IO操作结果
  4. Netty的异步模型是建立在future和callback(回调)之上的。Future的核心思想是:假设一个方法fun,计算过程可能非常耗时,等待fun返回显然不太合适。那可以在调用fun的时候立马返回一个future,后续可以通过Future去监控方法fun的处理过程(Future-Listener机制)
Future说明
  1. 表示异步的执行结果,可以通过它提供的方法来检测执行是否完成,比如检索计算等
  2. ChannelFuture是一个接口,我们可以添加监听器,当监听的事件发生时就会通过监听器
Future-Listener机制
  1. 当Future对象刚刚创建时,处于非完成状态,调用者可以通过返回的ChannelFuture来获取操作执行的状态,注册监听函数来执行完成后的操作
  2. 常见有如下操作
    • 通过isDone方法来判断当前操作是否完成
    • 通过isSuccess方法来判断已完成的当前操作是成功
    • 通过getCause方法来获取已完成的当前操作失败的原因
    • 通过isCancelled方法来判断已完成的当前操作是否被取消
    • 通过addListener方法来注册监听器,当操作已完成(isDone方法返回完成),将会通知指定的监听器,如果Future对象已完成,则通知指定的监听器

示例

public class NettyServer {

    public static void main(String[] args) {

        NioEventLoopGroup bossGroup = new NioEventLoopGroup();
        NioEventLoopGroup workGroup = new NioEventLoopGroup();

        try {
            ServerBootstrap bootstrap = new ServerBootstrap();
            bootstrap.group(bossGroup, workGroup)
                    .option(ChannelOption.SO_BACKLOG, 128)
                    .channel(NioServerSocketChannel.class)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel socketChannel) throws Exception {

                            socketChannel.pipeline().addLast(new NettyServerCustomTaskHandler());

                        }
                    });

            ChannelFuture channelFuture = bootstrap.bind(6668).sync();
//给 channelFuture 注册监听器,监控我们关心的事件
            channelFuture.addListener(new GenericFutureListener<Future<? super Void>>() {
                @Override
                public void operationComplete(Future<? super Void> future) throws Exception {
                    if(channelFuture.isDone()){
                        System.out.println("已完成");
                    }
                    if(channelFuture.isSuccess()){
                        System.out.println("已成功");
                    }
                }
            });

            channelFuture.channel().closeFuture().sync();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            bossGroup.shutdownGracefully();
            workGroup.shutdownGracefully();
        }
    }

}

示例:基于Netty实现一个简单的HTTP服务

  1. Netty服务器在6668端口监听,浏览器发出请求"http://localhost:16668/"
  2. 服务器回复消息给浏览器或客户端消息,并对指定请求资源进行过滤
//服务器端
public class NettyBrowserServer {

    public static void main(String[] args) {
        NioEventLoopGroup bossGroup = new NioEventLoopGroup(1);

        NioEventLoopGroup workerGroup = new NioEventLoopGroup();

        try {
            ServerBootstrap serverBootstrap = new ServerBootstrap();

            serverBootstrap.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .option(ChannelOption.SO_BACKLOG, 128)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ch.pipeline().addLast("serverCodec", new HttpServerCodec());
                            ch.pipeline().addLast(new NettyBrowserHandler());
                        }
                    });

            ChannelFuture channelFuture = serverBootstrap.bind(16668).sync();
            channelFuture.addListener(future -> {
                if (channelFuture.isSuccess()) {
                    System.out.println("运行成功");
                }
            });
            channelFuture.channel().closeFuture().sync();

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }


    }

}


//handler
/**
 * Created by myk
 * 2020/1/21 下午2:28
 * 1、SimpleChannelInboundHandler是ChannelInboundHandlerAdapter的子类
 * 2、客户端和服务器端通讯的数据放在HttpObject中
 */
public class NettyBrowserHandler extends SimpleChannelInboundHandler<HttpObject> {
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, HttpObject msg) throws Exception {


        //判断是否为Http请求
        if (msg instanceof HttpRequest) {
            System.out.println("pipeline hashcode" + ctx.pipeline().hashCode() + ",NettyBrowserHandler.hash=" + this.hashCode());
            System.out.println("消息类型" + msg.getClass());
            System.out.println("客户端地址:" + ctx.channel().remoteAddress());
            HttpRequest httpRequest = (HttpRequest) msg;
            //浏览器请求时会请求图标favicon.ico 这里没有这个图标因此将这个给过滤掉
            if (Objects.equals(httpRequest.uri(), "/favicon.ico")) {
                return;
            }

            //回复信息给浏览器
            ByteBuf byteBuf = Unpooled.copiedBuffer("hello,这是浏览器", CharsetUtil.UTF_16);
            //给浏览器回复信息 要符合http协议规范'
            FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, byteBuf);
            //设置content-type=text-plain
            response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/plain");
            //设置content-length
            response.headers().set(HttpHeaderNames.CONTENT_LENGTH, byteBuf.readableBytes());
            //将构建好 response 返回
            ctx.writeAndFlush(response);

        }

    }
}

浏览器的输出结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rSJJz2k4-1580353534681)(/Users/wojiushiwo/Library/Application Support/typora-user-images/image-20200122190312737.png)]

Bootstrap、ServerBootstrap介绍
  1. Bootstrap意思是引导,一个Netty应用通常由一个Bootstrap开始,主要作用是配置整个Netty程序,串联各个组件,Netty中Bootstrap类是客户端程序的启动引导类,ServerBootstrap是服务端启动引导类,

  2. 常见的方法有

    public ServerBootstrap group(EventLoopGroup parentGroup,EventLoopGroup childGroup)//该方法用于服务器端,用来设置两个EventLoop
    
    public B group(EventLoopGroup group);//该方法用于客户端,用来设置一个EventLoop
    public B channel(Class<? extends C> channelClass);//该方法用来设置一个服务器端的通道实现
    public <T> B option(ChannelOption<T> option,T value);//用来给ServerChannel添加配置
    public <T> ServerBootstrap childOption(ChannelOption<T> childOption,T value);//用来给接收到的通道添加配置
    public ServerBootstrap childHandler(ChannelHandler childHandler);//该方法用来设置业务处理类(自定义的handler)
    public ChannelFuture bind(int inetPort);//该方法用于服务器端,用来设置占用的端口号
    public ChannelFuture connect(String inetHost,int inetPort);//该方法用于客户端,用来连接服务器端
    
Future、ChannelFuture介绍

Netty中所有的IO操作都是异步的,不能立刻得知消息是否被正确处理。但是可以过一会儿等它执行完成或者直接注册一个监听,具体的实现就是通过Future和ChannelFutures,它们可以注册一个监听,当操作执行成功或失败时监听会自动触发注册的监听事件

常见的方法有:

Channel channel();//返回当前正在执行IO操作的通道
ChannelFuture sync();//等待异步操作执行完毕
Channel介绍
  1. Netty网络通信的组件,能够用于执行网络IO操作
  2. 通过Channel可获得当前网络连接的通道的状态
  3. 通过Channel可获得网络连接的配置参数(例如接收缓冲区的大小)
  4. Channe提供异步的网络IO操作(如建立连接、读写、绑定端口),异步调用意味着任何IO调用都将立即返回,并且不保证在调用结束时所请求的IO操作已完成
  5. 调用立即返回一个ChannelFuture实例,通过注册监听器到ChannelFuture上,可以IO操作成功、失败或取消时回调通知对方
  6. 支持关联IO操作与对应的处理程序
  7. 不同协议、不同阻塞类型的链接都有不同的Channel类型与之对应,常用的Channel类型:
    • NioSocketchannel 异步客户端TCP Scoket连接
    • NioServerSocketChannel,异步的服务器端TCP Socket连接
    • NioSctpChannel,异步的UDP连接
    • NioDatagramChannel,异步的UDP连接
    • NioSctpServerChannel,异步的Sctp服务器端连接,这些通道涵盖了UDP和TCP网络IO以及文件IO
Selector
  1. Netty基于Selector对象实现IO多路复用,通过Selector一个线程可以监听多个连接的Channel事件。
  2. 当向一个Selector中注册Channel后,Selector内部的机制就可以自动不断地查询(Select)这些注册的Channel是否有已就绪的IO事件(例如可读、可写、网络连接完成等),这些程序就可以很简单地使用一个线程高效地管理多个Channel
ChannelHandler及其实现类
  1. ChannelHandler是一个接口,处理IO事件或拦截IO操作,并将其转发到其ChannelPipeline(业务处理链)中的下一个处理程序
  2. ChannelHandler本身并没有提供很多方法,因为这个接口有很多的方法需要实现,使用期间可以继承它的子类

在这里插入图片描述

  • ChannelInboundHandler用于处理入站IO事件

  • ChannelOutboundHandler用于处理出站IO操作

    //适配器

  • ChannelInboundHandlerAdapter用户处理入站IO事件

  • ChannelOutboundHandlerAdapter用于处理出站IO操作

  • ChannelDuplexHandler用于处理入站和出站事件

对常用方法的介绍(部分未列出):

void channelRegistered(ChannelHandlerContext ctx) throws Exception;

  
    void channelUnregistered(ChannelHandlerContext ctx) throws Exception;

  	//通道就绪事件
    void channelActive(ChannelHandlerContext ctx) throws Exception;

    //通道不活跃事件
    void channelInactive(ChannelHandlerContext ctx) throws Exception;

    //通道读取数据事件
    void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception;

    //通道读取完毕事件
    void channelReadComplete(ChannelHandlerContext ctx) throws Exception;

    //异常捕获事件
    void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception;

执行顺序:handlerAdded->channelRegistered->channelActive->channelReadComplete->channelInactive->channelUnregistered->handlerRemoved

Pipeline和ChannelPipeline的介绍
  1. ChannelPipeline是Handler的集合,它负责处理和拦截inbound或者outbound的事件和操作,相当于一个贯穿Netty的链(ChannelPipeline是保存ChannelHandler的list,用于处理或拦截Channel的入站事件和出站操作)

  2. ChannelPipeline实现了一种高级形式的拦截过滤器模式,使用户可以完全控制事件的处理方式,以及channel中各个ChannelHandler如何相互交互

  3. 在Netty中每个Channel都有且仅有一个ChannelPipeline与之对应,它们的组成关系如下:

在这里插入图片描述

说明:

一个channel包含了一个ChannelPipeline,而ChannelPipeline中又维护了一个由ChannelHandlerContext组成的双向链表,并且每个ChannelHandlerContext中又关联着一个ChannelHandler

入站事件和出站事件在一个双向链表中,入站事件会从链表head往后传递到最后一个入站的handler,出站事件会从链表tail往前传递到最前一个出站的handler,两种类型的handler互不干扰。

常用方法

ChannelPipeline addFirst(ChannelHandler... handlers);//把一个业务处理类(handler)添加到链中的每一个位置
ChannelPipeline addLast(ChannelHandler...handlers);//把一个业务处理类(handler)添加到链中的最后一个位置
ChannelHandlerContext介绍
  1. 保存Channel相关的所有上下文信息,同时管理一个ChannelHandler对象

  2. ChannelHandlerContext中包含一个具体的事件处理器ChannelHandler,同时ChannelHandlerContext也绑定了对应的pipeline和Channel的信息,方便对ChannelHandler进行调用

  3. 常用方法

    ChannelFuture close();//关闭通道
    ChannelOutboundInvoker flush();//刷新
    ChannelFuture writeAndFlush(Object msg);//将数据写到ChannelPipeline中当前ChannelHandler的下一个ChannelHandler开始处理(出站)
    
ChannelOption介绍

Netty在创建Channel实例后,一般都需要设置ChannelOption参数:

ChannelOption.SO_BACKLOG

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

ChannelOption.SO_KEEPALIVE

一直保持连接活动状态

EventLoopGroup和实现类NioEventLoopGroup的介绍
  1. EventLoopGroup是一组EventLoop的抽象,Netty为了更好的利用多核CPU资源,一般会有多个EventLoop同时工作,每个EventLoop维护者一个Selector实例

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

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

    在这里插入图片描述

    说明:

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

常用方法:

public NioEventLoopGroup();//构造方法
public Future<?> shutdownGracefully();//断开连接,关闭线程
发布了116 篇原创文章 · 获赞 23 · 访问量 8万+

猜你喜欢

转载自blog.csdn.net/zyxwvuuvwxyz/article/details/104113212