005-核心技术-netty线程模型、任务队列、异步模型

一、netty从简到繁解说线程模型

1.1、简单版

 

说明:1)BossGroup线程维护Selector,只关注Accept

2)当接收到Accept事件,获取到对应的SocketChannel,封装成NIOSocketChannel并注册到Worker线程(事件循环) 

3)当worker线程监听到selector中通道发生自己感兴趣的事件后,就进行处理(由Handler)注意handler已加入到通道。

1.2、进阶模型

如上图

1.3、详细模型

  

1)netty抽象出两组线程池:BossGroup负责接口客户端的连接,WorkGroup负责网络读写

2)BossGroup和WorkGroup都时NioEventLoopGroup

3)NioEventLoopGroup相当于一个事件循环组,组中含有多个事件循环,每一个事件都包含NioEventLoop

4)NIOEventLoop表示一个不断循环的执行处理任务的线程,每个NioEventLoop都有一个Selector,用于监听绑定在其上的socket的网络通讯

5)每个Boss NIOEventLoop 循环执行步骤:

  a、轮询accept事件

  b、处理accept事件,与client建立连接,生成NioSocketChannel,并将其注册到某个Work NioEventLoop上的selector

  c、处理任务队列的任务,即runAllTasks

6)每个Work NioEventLoop循环执行的步骤:

  a、轮询read、write事件

  b、处理IO事件,即read、write事件,在对应NioSocketChannel处理

  c、处理任务队列的任务,即runAllTasks

7)每个Worker NioEventLoop处理业务时,会使用Pipeline,pipeline中包含了channel,管道中维护了很多处理器

二、实例

2.1、添加本地jar以及source

idea找到project Structure,找的自己的Modules,找到dependences,左下角+,可以搜索下载,或者加载本地的,可以选择加载source、docs等

2.2、简单实例 基础通讯

服务端

package com.github.bjlhx15.netty.demo.netty.simple;

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;

public class NettyServer {
    public static void main(String[] args) throws InterruptedException {
        //        创建 bossGroup 和 workerGroup
        //        bossGroup 处理accept,workerGroup处理业务
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            //创建服务端的启动对象,配置参数
            ServerBootstrap bootstrap = new ServerBootstrap();
            bootstrap.group(bossGroup, workerGroup)//设置两个线程组
                    .channel(NioServerSocketChannel.class)//使用NioSocketChannel作为服务器的通道实现
                    .option(ChannelOption.SO_BACKLOG, 128)//设置线程队列得到的连接个数
                    .childOption(ChannelOption.SO_KEEPALIVE, true)//设置保持活动连接状态
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        //   创建一个通道初始化
                        //   给Pipeline设置处理器
                        @Override
                        protected void initChannel(SocketChannel socketChannel) throws Exception {
                            socketChannel.pipeline().addLast(new NettyServerHandler());
                        }
                    });
            System.out.println("……服务器 is ready。");
            //绑定一个端口并且同步,生成一个ChannelFuture对象
            //启动服务器(并绑定端口)
            ChannelFuture cf = bootstrap.bind(6668).sync();
            //对关闭通道进行监听
            cf.channel().closeFuture().sync();
        } finally {
//            优雅的关闭
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
} 

服务端handler

package com.github.bjlhx15.netty.demo.netty.simple;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.CharsetUtil;

/**
 * 1、自定义一个handler,继承ChannelInboundHandlerAdapter
 */
public class NettyServerHandler extends ChannelInboundHandlerAdapter {
    //读取实际数据
    //ChannelHandlerContext 上下文对象,含有管道pipeline、通道channel,地址
    //Object msg:客户端数据,默认Object
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        System.out.println("Server ctx=" + ctx);
        //        将msg 转ByteBuffer
        //        ByteBuf 时netty,性能优于nio bytebuffer
        ByteBuf buffer = (ByteBuf) msg;
        System.out.println("客户端发送消息是:" + buffer.toString(CharsetUtil.UTF_8));
        System.out.println("client address:" + ctx.channel().remoteAddress());
    }

    //数据读取完毕
    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        //        writeAndFlush 是 write和 flush
        //        将数据写入到缓存,并刷新
        //        将写入数据进行编码
        ctx.writeAndFlush(Unpooled.copiedBuffer("hello client", CharsetUtil.UTF_8));
    }

    //处理异常一般关闭通道
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        ctx.close();
    }
}

客户端

package com.github.bjlhx15.netty.demo.netty.simple;

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;

public class NettyClient {
    public static void main(String[] args) throws InterruptedException {
//        客户端需要一个事件循环组
        EventLoopGroup group = new NioEventLoopGroup();
        try {
//        创建客户端启动对象
//        不是ServerBootstrap 而是 Bootstrap
            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(new NettyClientHandler());//加入自己的处理器
                        }
                    });
            System.out.println("客户端 ok");
//        启动客户端去连接服务器
            ChannelFuture channelFuture = bootstrap.connect("127.0.0.1", 6668).sync();
//            关闭通道进行监听,
            channelFuture.channel().closeFuture().sync();
        } finally {
            group.shutdownGracefully();
        }
    }
} 

客户端Handler

package com.github.bjlhx15.netty.demo.netty.simple;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.CharsetUtil;

public class NettyClientHandler extends ChannelInboundHandlerAdapter {
    //    当通道就绪就会触发该方法
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        System.out.println("client ctx:" + ctx);
        ctx.writeAndFlush(Unpooled.copiedBuffer("Hello server.", CharsetUtil.UTF_8));
    }

    //当通道有读取事件时,会触发
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        ByteBuf byteBuf = (ByteBuf) msg;
        System.out.println("server response:" + byteBuf.toString(CharsetUtil.UTF_8));
        System.out.println("server address:" + ctx.channel().remoteAddress());
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        ctx.close();
    }
}

2.3、代码分析

服务端分析

1)bossGroup和workerGroup包含的子线程(NioEventLoop)个数默认是 实际CPU核数*2

2)bossGroup 一般设置1个即可,专门负责接受客户端连接

3)workerGroup,假如有8个子线程,那么会依次循环复用给客户端使用,主要负责网络读写操作

4)针对bossGroup和workerGroup内的子线程NioEventLoop(类型EventExecutor),都有自己独立的selector、taskQueue、executor(threadFactory)

5)channel and pipeline 关系,相互包含

Channel channel = ctx.channel();
ChannelPipeline pipeline = channel.pipeline();//pipeline是一个双向链表

6)ChannelHandlerContext 上下文,包含全部信息

7)NioEventLoop内部采用串行化设计,从消息的读取→解码→处理→编码→发送,始终由IO线程NioEventLoop负责

  NioEventLoopGroup下包含多个NioEventLoop;每个NioEventLoop中包含有一个Selector,一个taskQueue;

  每个NioEventLoop的Selector上可以注册监听多个NioChannel;每个NioChannel只会绑定在唯一的NioEventLoop上;每个NioChannel都绑定有一个自己的ChannelPipeline;

三、模型内关键变量介绍

3.1、Netty模型任务队列taskQueue

3种典型使用场景

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

2)用户自定义定时任务

3)非当前Reactor线程调用Channel的各种方法

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

实例:上述服务端handler的channelRead有耗时任务

 @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        Thread.sleep(10 * 1000);
        ctx.writeAndFlush(Unpooled.copiedBuffer("hello client 2", CharsetUtil.UTF_8));
        System.out.println("go on ……");
    } 

在channelReadComplete逻辑不变

    //数据读取完毕
    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        //        writeAndFlush 是 write和 flush
        //        将数据写入到缓存,并刷新
        //        将写入数据进行编码
        ctx.writeAndFlush(Unpooled.copiedBuffer("hello client 1", CharsetUtil.UTF_8));
    } 

此时客户端10s后输出:hello client 2;hello client 1;

然后服务端10s后输出:go on ……

3.1.1、用户程序自定义的普通任务,存储在taskQueue中异步执行

比如channelRead中有个非常耗时的任务→异步执行→提交channel对应的NIOEventLoop的taskQueue中。

服务端handler的channelRead修改

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
            //解决方案1 用户程序自定义普通任务
        ctx.channel().eventLoop().execute(()->{
            //耗时任务
            try {
                Thread.sleep(10 * 1000);
                ctx.writeAndFlush(Unpooled.copiedBuffer("hello client 2", CharsetUtil.UTF_8));
            } catch (InterruptedException e) {
                System.out.println("发生异常");
            }
        });
        System.out.println("go on ……");
    }

此时服务端立即输出:go on ……

客户端立即输出:hello client 1;

10s后客户端输出:hello client 2;

3.1.2、用户自定义定时任务,该任务提交到scheduleTaskQueue队列中

服务端handler的channelRead改造

        ctx.channel().eventLoop().schedule(() -> {
            //耗时任务
            try {
                Thread.sleep(5 * 1000);
                ctx.writeAndFlush(Unpooled.copiedBuffer("hello client 4", CharsetUtil.UTF_8));
            } catch (InterruptedException e) {
                System.out.println("发生异常");
            }
        },5, TimeUnit.SECONDS); 

3.1.3、非当前Reactor线程调用Channel的各种方法

在服务端的:.childHandler(new ChannelInitializer<SocketChannel>() {  的initChannel方法中使用一个集合管理SocketChannel,在推送消息时,可以将业务加入到各个channel 对应的NioEventLoop的taskQueue或者scheduleTaskQueue

四、异步模型 

4.1、基本介绍

1)异步和同步相对,当一个异步过程调用发出后,调用者不能立刻得到结果。实际处理这个调用的组件在完成后,通过状态、通知和回调来通知调用者。

2)Netty中的IO操作是异步的,包括bind、write、connect等操作会简单的返回一个ChannelFutura。

3)调用者并不能立刻获得结果,而是通过Future-Listener机制。用户可以方便的主动获取或者通过通知机制获得IO操作结果

4)Netty的异步模型是建立在future和callback上的。callback就是回调。Future核心思想是:假设一个方法fun,计算过程可能非常耗时,等待fun返回不合适,那么可以在调用fun的时候,立马返回一个Future,后续可以通过Future去监控方法fun的处理过程(即Future-Listener机制)

4.2、Future

  表示异步的执行结果,可以通过它提供的方法来检测执行是否完成,比如检索计算

  ChannelFuture是一个接口,可以添加监听器,当监听的事件发生时,就会通知到监听器

  

 

  说明:1)在使用Netty进行编程时,拦截操作和转换出入站数据只需要您提供callback或利用Future即可。链式操作简单、高效、有利于编写可重用的、通用的代码

    2)Netty目标,使业务逻辑从网络基础应用编码中分离出来,解脱出来

4.3、Future-Listener机制

  当Future对象刚刚创建时,处于非完成状态,调用者可以通过返回的ChannelFuture来获取操作执行的状态,注册监听函数来执行完成后的操作。

  常见操作:

isDone:是否完成

isSuccess:操作是否成功

getCause:操作失败原因

isCancelled:才做是否被取消

addListener方法来注册监听器,当操作已完成(isDone方法返回完成),将会通知指定的监听器,如果Future对象已完成,则通知指定的监听器 

示例:

           ChannelFuture cf = bootstrap.bind(6668).sync();

            cf.addListener(new ChannelFutureListener() {
                @Override
                public void operationComplete(ChannelFuture channelFuture) throws Exception {
                    if(cf.isSuccess()){
                        System.out.println("监听端口 6668 成功");
                    }else{
                        System.out.println("监听端口 6668 失败");
                    }
                }
            });

绑定端口是异步操作,当绑定操作处理完,将会调用相应的监听器处理逻辑。

4.4、HTTP服务示例

自定义一个TestHttpServerHandler

//SimpleChannelInboundHandler  是 ChannelInboundHandlerAdapter
//HttpObject 客户端和服务器端相互通讯的数据被封装成HttpObject
public class TestHttpServerHandler extends SimpleChannelInboundHandler<HttpObject> {

    @Override
    protected void channelRead0(ChannelHandlerContext channelHandlerContext, HttpObject msg) throws Exception {
        if (msg instanceof HttpRequest) {

            System.out.println("msg 类型是:" + msg.getClass());
            System.out.println("客户端地址:" + channelHandlerContext.channel().remoteAddress());

            HttpRequest httpRequest = (HttpRequest) msg;
            URI uri = new URI(httpRequest.uri());
            if ("/favicon.ico".equals(uri.getPath())) {
                System.out.println("请求了 /favicon.ico ,不处理");
                return;
            }

            ByteBuf byteBuf = Unpooled.copiedBuffer("helleo 你好", CharsetUtil.UTF_8);

            DefaultFullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, byteBuf);
            response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/plain");
            response.headers().set(HttpHeaderNames.CONTENT_LENGTH, byteBuf.readableBytes());
            channelHandlerContext.writeAndFlush(response);
        }
    }
}

定义一个TestServerInitializer

public class TestServerInitializer extends ChannelInitializer<SocketChannel> {
    @Override
    protected void initChannel(SocketChannel socketChannel) throws Exception {
        ChannelPipeline pipeline = socketChannel.pipeline();
        //http解码器
        pipeline.addLast("MyHttpServerCodec",new HttpServerCodec());
        pipeline.addLast("MyTestHttpServerHandler",new TestHttpServerHandler());
    }
}

Server

public class TestServer {
    public static void main(String[] args) throws InterruptedException {
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            ServerBootstrap bootstrap = new ServerBootstrap();
            bootstrap.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .childHandler(new TestServerInitializer());
            ChannelFuture channelFuture = bootstrap.bind(8081).sync();
            channelFuture.channel().closeFuture().sync();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}

 

posted @ 2021-08-06 11:10  bjlhx15  阅读(289)  评论(0编辑  收藏  举报
Copyright ©2011~2020 JD-李宏旭