文章目录

  • 1. 基本介绍
  • 2. Future 说明
  • 3. 工作原理示意图
  • 4. Future-Listener 机制
  • 5. 小结
  • 6. 快速入门实例-HTTP服务


1. 基本介绍

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

2. Future 说明

  1. 表示异步的执行结果, 可以通过它提供的方法来检测执行是否完成,比如检索计算等等.
  2. ChannelFuture 是一个接口 :

    我们可以添加监听器,当监听的事件发生时,就会通知到监听器.

3. 工作原理示意图

netty 异步 通过id response netty异步http请求_监听器

  • 说明:
  1. 在使用 Netty 进行编程时,拦截操作和转换出入站数据只需要您提供 callback 或利用 future 即可。这使得链式操作简单、高效, 并有利于编写可重用的、通用的代码。
  2. Netty 框架的目标就是让你的业务逻辑从网络基础应用编码中分离出来

4. Future-Listener 机制

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

• 通过 isDone 方法来判断当前操作是否完成;
• 通过 isSuccess 方法来判断已完成的当前操作是否成功;
• 通过 getCause 方法来获取已完成的当前操作失败的原因;
• 通过 isCancelled 方法来判断已完成的当前操作是否被取消;
• 通过 addListener 方法来注册监听器,当操作已完成(isDone 方法返回完成),将会通知 指定的监听器;如果 Future 对象已完成,则通知指定的监听器

  1. 代码示例
    给一个 ChannelFuture 注册监听器,来监控我们关系的事件
channelFuture.addListener(new ChannelFutureListener() {
     @Override
     public void operationComplete(ChannelFuture channelFuture) throws Exception {
          if (channelFuture.isSuccess()){
               System.out.println("监听端口 6668 成功");
          }else {
               System.out.println("监听端口 6668 失败");
          }
      }
});

5. 小结

相比传统阻塞 I/O,执行 I/O 操作后线程会被阻塞住, 直到操作完成;
异步处理的好处是不会造成线程阻塞,线程在 I/O 操作期间可以执行别的程序,在高并发情形下会更稳定和更高的吞吐量

6. 快速入门实例-HTTP服务

  • 要求:
  1. Netty 服务器在 6668 端口监听,浏览器发出请求 "http://localhost:6668/ "
  2. 服务器可以回复消息给客户端 "Hello! 我是服务器 5 " , 并 对特定请求资源进行过滤.
  • 目的:
Netty 可以做Http服务开发,并且理解Handler实例 和客户端及其请求的关系

netty 异步 通过id response netty异步http请求_监听器_02

  • 编写代码 —— 服务端代码
  1. 编写 服务端 :HttpServer
public class HttpServer {
    public static void main(String[] args) throws Exception{
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workerGroup = new NioEventLoopGroup(8);

        try {
            // 创建 服务端 启动对象,并配置参数
            ServerBootstrap serverBootstrap = new ServerBootstrap();
            serverBootstrap.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    // 使用自己写的 ServerInitializer 完成初始化
                    .childHandler(new HttpServerInitializer());
            System.out.println("服务器准备好了……");

            ChannelFuture channelFuture = serverBootstrap.bind(6660).sync();
            channelFuture.channel().closeFuture().sync();
        }finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}
  1. 编写 服务初始化器 :HttpServerInitializer
public class HttpServerInitializer extends ChannelInitializer<SocketChannel> {
    @Override
    protected void initChannel(SocketChannel socketChannel) throws Exception {
        // 向管道加入处理器
        // 得到管道
        ChannelPipeline pipeline = socketChannel.pipeline();

        // 加入一个 Netty 提供的 httpServerCodec (CoDec => Coder + Decoder => 编解码器)
        pipeline.addLast("MyHttpServerCodec",new HttpServerCodec());
        // 增加一个自己的 Handler
        pipeline.addLast("MyServerHandler", new HttpServerHandler());
    }
}
  1. 编写 服务处理器 :HttpServerHandler
/*
    1. SimpleChannelInboundHandler 是之前使用的 ChannelInboundHandlerAdapter 的子类
    2. HttpObject 这个类型表示, 客户端、服务端 相互通信的数据需要被封装成什么类型
 */
public class HttpServerHandler extends SimpleChannelInboundHandler<HttpObject> {
    /**
     * 读取客户端数据
     * @param channelHandlerContext 上下文
     * @param httpObject 传递过来的消息
     * @throws Exception
     */
    @Override
    protected void channelRead0(ChannelHandlerContext channelHandlerContext, HttpObject httpObject) throws Exception {
        // 判断 httpObject 是不是一个 HttpRequest 请求
        if (httpObject instanceof HttpRequest){
            System.out.println("httpObject 的类型 :"+ httpObject.getClass());
            System.out.println("客户端的地址 : "+ channelHandlerContext.channel().remoteAddress());

            // 回复信息给浏览器,需要把数据封装成 HttpObject 类型
            // 创建一个 ButeBuf
            ByteBuf byteBuf = Unpooled.copiedBuffer("Hello,我是服务器", CharsetUtil.UTF_8);
            // 构建一个 Http 的响应,即 httpResponse  ;  后面的三个参数 :(Http 协议的版本, Http 的状态码, 需要传输的内容)
            FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, byteBuf);
            // 设置文本的类型,及字符编码
            response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/plain;charset=utf-8");
            // 文本的长度
            response.headers().set(HttpHeaderNames.CONTENT_LENGTH, byteBuf.readableBytes());
            // 将构建好的 response 返回
            channelHandlerContext.writeAndFlush(response);
        }
    }
}
  • 启动测试
  • netty 异步 通过id response netty异步http请求_netty_03

  • 编写代码 —— 对特定资源的过滤
    上面的服务端启动后,在页面上不止接收到了文本,还接收到了一个网页的图标

    现在把它过滤掉
    修改 HttpServerHandler
// 获取请求的 URI
        HttpRequest httpRequest = (HttpRequest) httpObject;
        URI uri = new URI(httpRequest.uri());
        // 判断请求路径为 /favicon.ico,就不做处理
        if ("/favicon.ico".equals(uri.getPath())){
            System.out.println("请求了 图标 资源,不做响应");
            return;
        }

netty 异步 通过id response netty异步http请求_netty_04

  • 启动测试