Netty入门(四)任务队列,异步模型,实现Http服务器

任务队列

任务队列的task有3种典型的使用场景

  • 用户程序自定义的普通任务
  • 用户自定义定时任务
  • 非当前Reactor线程调用Channel的各种方法
    例如:在推送系统的业务线程里面,根据用户的标识,找到对应的Channel引用,然后调用write类方法向该用户推送消息,就会进入到这种场景。最终的write会提交到任务队列中后被异步消费。

假设有非常耗时的业务场景,我们希望使用异步执行解决这个问题。

解决方式一:用户程序自定义的普通任务

普通异步任务使用 execute 方法进行调度。

服务端的Handler

@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
    //比如这里我们有一个非常耗时的业务->异步执行->提交改channel对应的
    //NIOEventLoop的taskQueue中

    //解决方式一:用户程序自定义的普通任务
    //执行业务逻辑任务是通过workGroup的eventLoop,注意workgroup才是线程池,eventloop是池里的线程
    //所以这里并没有开辟一条新的线程,还是同一条线程
    //只是给这个线程eventloop添加了一个任务到它的任务队列中,在Netty模型中,也是可以看到eventloop中是含有taskQueue的
    //添加任务一。调用 NioEventLoop 线程的 execute 方法 , 即可将 Runnable 异步任务放入任务队列 TaskQueue ;
    ctx.channel().eventLoop().execute(()->{
        try {
            Thread.sleep(10000);
            ctx.writeAndFlush(Unpooled.copiedBuffer("hello,客户端000~",CharsetUtil.UTF_8));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    });
    //添加任务二
    ctx.channel().eventLoop().execute(()->{
        try {
            Thread.sleep(20000);
            ctx.writeAndFlush(Unpooled.copiedBuffer("hello,客户端000~",CharsetUtil.UTF_8));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    });
    System.out.println("go on...");
}

在这里插入图片描述
在这里插入图片描述
这里通过图解,也可以看到线程eventLoop的任务队列里多了两个任务,因为是队列,所以执行顺序是先执行任务一,再执行任务二,执行完这两任务需要30秒。

普通异步任务提交到 TaskQueue 任务队列中 。

解决方式二:用户自定义定时任务

定时异步任务使用 schedule 方法进行调度。

//解决方式二:用户自定义定时任务
ctx.channel().eventLoop().schedule(()->{
    try {
        Thread.sleep(5000);
        ctx.writeAndFlush(Unpooled.copiedBuffer("hello,客户端666~",CharsetUtil.UTF_8));
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
},5, TimeUnit.SECONDS);

在这里插入图片描述
eventloop也是能看到这个定时任务队列的。
定时异步任务提交到 ScheduleTaskQueue 任务队列中 。

解决方式三:非当前Reactor线程调用Channel的各种方法

可以在ChannelInitializer中的initChannel方法中,将Channelhashcode与用户绑定,加入到集合中,需要推送消息时,从集合中获取Channel,加入到相应的NIOEventLoopTaskQueueschdueleTaskQueue推送消息

https://blog.csdn.net/HongZeng_CSDN/article/details/130226526

异步模型

基本介绍

  • 异步的概念和同步相对。当一个异步过程调用发出后,调用者不能立刻得到结果。实际处理这个调用的组件在完成后,通过状态、通知和回调来通知调用者。
  • Netty 中的 I/O 操作是异步的,包括 Bind、Write、Connect 等操作会简单的返回一个 ChannelFuture
  • 调用者并不能立刻获得结果,而是通过 Future-Listener 机制,用户可以方便的主动获取或者通过通知机制获得 IO 操作结果
  • Netty 的异步模型是建立在 futurecallback 的之上的。callback 就是回调。重点说 Future,它的核心思想是:假设一个方法 fun,计算过程可能非常耗时,等待 fun返回显然不合适。那么可以在调用 fun 的时候,立马返回一个 Future,后续可以通过 Future去监控方法 fun 的处理过程(即 : Future-Listener 机制)

Future 说明

  • 表示异步的执行结果, 可以通过它提供的方法来检测执行是否完成,比如检索计算等等.
  • ChannelFuture 是一个接口,表示通道异步的执行结果: public interface ChannelFuture extends Future<Void>我们可以添加监听器,当监听的事件发生时,就会通知到监听器. 案例说明

Future-Listener 机制

Future-Listener :表示异步执行结果——监听机制。

  • Future 对象刚刚创建时,处于非完成状态,调用者可以通过返回ChannelFuture来获取操作执行的状态,注册监听函数来执行完成后的操作
  • 常见有如下操作:
    通过 isDone 方法来判断当前操作是否完成;
    通过 isSuccess 方法来判断已完成的当前操作是否成功;
    通过 getCause 方法来获取已完成的当前操作失败的原因;
    通过 isCancelled 方法来判断已完成的当前操作是否被取消;
    通过 addListener 方法来注册监听器,当操作已完成(isDone 方法返回完成),将会通知指定的监听器;如果 Future 对象已完成,则通知指定的监听器
  • 举例说明:绑定端口Bind是异步操作,当绑定操作处理完,将会调用相应的监听器处理逻辑
//启动服务器并绑定端口
ChannelFuture sync = serverBootstrap.bind(6668).sync();

sync.addListener((ChannelFutureListener) channelFuture -> {
    if (channelFuture.isSuccess()){
        System.out.println("绑定完成");
    }else {
        System.out.println("绑定失败");
    }
});

Netty实现一个简单的http服务器

由于客户端是浏览器,所以只需要编写服务端

服务端:

绑定6668端口

public class HttpServer {
    public static void main(String[] args) throws InterruptedException {
        //设置main方法日志级别
        LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
        List<Logger> loggerList = loggerContext.getLoggerList();
        loggerList.forEach(logger -> {
            logger.setLevel(Level.WARN);
        });

        //bossGroup专门负责接收客户端的连接
        EventLoopGroup bossGroup=new NioEventLoopGroup();
        //workerGroup专门负责网络的读写(真正的和客户端进行业务处理)
        EventLoopGroup workerGroup=new NioEventLoopGroup();

        try {
            //创建服务器端的启动对象,配置参数
            ServerBootstrap serverBootstrap = new ServerBootstrap();
            //使用链式编程进行配置参数
            serverBootstrap.group(bossGroup,workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        //初始化通道
                        @Override
                        protected void initChannel(SocketChannel socketChannel) throws Exception {
                            //1.获取管道
                            //2.加入一个netty提供的httpServerCodec(netty提供的一个http的编--解码器)
                            //codec中的co是coder编码器的意思, dec是decoder解码器的意思
                            //第一个字符串是编解码器的名称
                            //3.为管道 Pipeline添加一个处理器
                            socketChannel.pipeline()
                                    .addLast("HttpServerCodec",new HttpServerCodec())
                                    .addLast("HTTPServerHandler",new HttpServerHandler());
                        }
                    });
            //绑定一个端口并且同步,生成一个ChannelFuture对象
            //启动服务器并绑定端口
            ChannelFuture sync = serverBootstrap.bind(6668).sync();
            //异步执行结果监听器
            sync.addListener((ChannelFutureListener) channelFuture -> {
                if(channelFuture.isSuccess()){
                    System.out.println("监听端口 6668 成功");
                } else {
                    System.out.println("监听端口失败");
                }
            });
            //对关闭通道进行监听
            sync.channel().closeFuture().sync();
        } finally {
            //关闭线程池
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}

自定义handler处理器

浏览器会发送两个请求,一个正常的请求,一个网页图标,要分开处理

/**
 * 自定义handler处理器。
 * SimpleChannelInboundHandler说明:
 * 1.是ChannelInboundHandlerAdapter的子类
 * 2.HttpObject 客户端和服务器端相互通讯的数据被封装成 HttpObject
 **/
public class HttpServerHandler extends SimpleChannelInboundHandler<HttpObject> {
    private static byte[] bytes;

    static{
        File file=new File("G:\\Desktop\\fish3.jpg");
        FileInputStream fi=null;

        try {
            //获得文件字节大小
            long fileSize = file.length();
            if (fileSize>Integer.MAX_VALUE){
                    throw new IOException(file.getName()+" file too big...");
            }
            fi= new FileInputStream(file);
            //数据中转站 临时缓冲区
            bytes=new byte[(int)fileSize];
            int offSet=0;
            int numRead=0;

            //循环读取文件内容,输入流中将最多fileSize个字节的数据读入一个bytes数组中,返回类型是读取到的字节数。
            while(offSet<bytes.length && (numRead=fi.read(bytes,offSet,bytes.length-offSet))>=0){
                    offSet+=numRead;
            }
            //确保所有的数据均被读取
            if (offSet!=bytes.length){
                throw new IOException("Could not completely read file "
                        + file.getName());
            }
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            if(fi!=null){
                try {
                    fi.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
     //读取客户端数据
    @Override
    protected void channelRead0(ChannelHandlerContext channelHandlerContext, HttpObject httpObject) throws Exception {
        //判断httpObject是不是一个HttpRequest请求
        if (httpObject instanceof HttpRequest){
            System.out.println("msg类型:"+httpObject.getClass());
            System.out.println("客户端地址:"+channelHandlerContext.channel().remoteAddress());

            HttpRequest httpRequest = (HttpRequest) httpObject;
            URI uri = new URI(httpRequest.uri());
            //缓存
            ByteBuf content=null;
            //处理图标
            if ("/favicon.ico".equals(uri.getPath())){
                //将数据写入缓存
                content = Unpooled.copiedBuffer(bytes);
            }else{
                //回复信息给浏览器
                //将数据写入缓存
                content = Unpooled.copiedBuffer("hello,我是服务器", CharsetUtil.UTF_8);
            }
            //构造一个httpResponse
            DefaultFullHttpResponse defaultFullHttpResponse =
                    new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, content);
            //设置响应头
            //设置内容类型是文本类型
            defaultFullHttpResponse.headers().set(HttpHeaderNames.CONTENT_TYPE,"text/plain;charset=utf-8");
            //设置返回内容的长度
            defaultFullHttpResponse.headers().set(HttpHeaderNames.CONTENT_LENGTH,content.readableBytes());
            //写出HTTP数据
            channelHandlerContext.writeAndFlush(defaultFullHttpResponse);
        }
    }
}

测试

① 启动服务器 : 启动 HTTP 服务器 , 监听 6668端口 ;
在这里插入图片描述
② 浏览器访问服务器 : 浏览器中输入 http://127.0.0.1:6668/ 地址 , 即可访问 Netty HTTP 服务器 , 服务器返回 Hello Client 字符串信息 ; 注意:如果再浏览器中输入http://127.0.0.1:6668/ 地址,返回"无法访问此网站"的信息,换一个浏览器再试试或者把端口改成8000以上。
在这里插入图片描述
③ 服务器端日志 :
在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值