Netty编程(一)—— 初识Netty+超全注释

这是我参与11月更文挑战的第20天,活动详情查看:2021最后一次更文挑战

之前的博客介绍了NIO网络编程的相关知识,从这篇博客开始,我将开始介绍Netty的相关知识。

什么是Netty

Netty 是一个异步的、基于事件驱动的网络应用框架,可用于快速开发可维护、高性能的网络服务器和客户端

  • 基于事件驱动意思是底层实现采用多路复用技术(selector),事件发生时才需要进行处理
  • 异步是指使用了多线程完成方法调用和处理结果相分离,并不是异步IO

Hello World

学习一个技术或者框架,可以先从hello world开始了解它,然后一步一步进行学习。下面就先通过一段最基础的Netty代码来初始Netty,这段代码可以看作Netty的Hello World,它分为服务器端和客户端两部分,首先来看服务端的代码

服务端

public class HelloServer {
    public static void main(String[] args) {
        // 1、服务器端的启动器,负责装配下方的netty组件,启动服务器
        new ServerBootstrap()
                // 2、创建 NioEventLoopGroup,可以简单理解为 线程池 + Selector
                .group(new NioEventLoopGroup())
                // 3、选择服务器的 ServerSocketChannel 实现
                .channel(NioServerSocketChannel.class)
                // 4、child(work) 负责处理读写,该方法决定了 child(work) 执行哪些操作(handler)
                // ChannelInitializer 处理器(仅执行一次)
                // 5、channel的作用是待客户端SocketChannel建立连接后与客户端进行读写的通道,执行initChannel初始化,作用是添加别的handler
                .childHandler(new ChannelInitializer<NioSocketChannel>() {
                    @Override
                    protected void initChannel(NioSocketChannel nioSocketChannel) throws Exception {//添加handler
                        // 6、添加具体的handler
                        nioSocketChannel.pipeline().addLast(new StringDecoder());//使用StringDecoder解码,ByteBuf=>String

                        nioSocketChannel.pipeline().addLast(new SimpleChannelInboundHandler<String>() {// 自定义handler,使用上一个处理器的处理结果
                            @Override
                            protected void channelRead0(ChannelHandlerContext channelHandlerContext, String s) throws Exception 							{
                                System.out.println(s);//打印上一步转换好的字符串
                            }
                        });
                    }
                    // 7、ServerSocketChannel绑定8080端口
                }).bind(8080);
    }
}
复制代码

下面对这段代码进行解读:

  1. 首先使用new ServerBootstrap()打开一个服务端的启动器,它负责装配下面的Nettty组件,同时会启动服务器
  2. group(new NioEventLoopGroup())用来创建一个事件循环组EventLoopGroup,可以把它理解成是Selector+线程池的组合
  3. channel(NioServerSocketChannel.class)选择服务器的ServerSocketChannel 实现
  4. childHandler(new ChannelInitializer<NioSocketChannel>()中的child可以理解成worker,是负责处理读写事件的,这个方法决定了child执行哪些操作(handler)
  5. channel的作用是待客户端SocketChannel建立连接后与客户端进行读写的通道,执行initChannel初始化,作用是添加别的handler
  6. initChannel(NioSocketChannel nioSocketChannel)方法是用来添加具体的handler,具体是使用nioSocketChannel.pipeline().addLast来添加,代码中添加了第一个handler是解码,将客户端发来的数据变成String,添加了第二个handler是自定义handler,并且这个handler需要使用上一个解码handler的结果。
  7. bind(8080)最后使用bind方法对端口号进行绑定

客户端

public class HelloClient {
    public static void main(String[] args) throws InterruptedException {
        //1、启动类
        new Bootstrap()
                //2、添加EventLoop
                .group(new NioEventLoopGroup())
                // 3、选择客户 Socket 实现类,NioSocketChannel 表示基于 NIO 的客户端实现
                .channel(NioSocketChannel.class)
                // 4、添加处理器   ChannelInitializer 处理器(仅执行一次)
                // 它的作用是待客户端SocketChannel建立连接后,执行initChannel以便添加更多的处理器
                .handler(new ChannelInitializer<NioSocketChannel>() {//初始化器会在连接建立后被调用,调用后就会执行下面的initChannel
                    @Override
                    protected void initChannel(NioSocketChannel channel) throws Exception {
                        // 消息会经过通道 handler 处理,这里是将 String => ByteBuf 编码发出
                        channel.pipeline().addLast(new StringEncoder());
                    }
                })
                // 指定要连接的服务器和端口
                .connect(new InetSocketAddress("localhost", 8080))
                // Netty 中很多方法都是异步的,如 connect
                // 这时需要使用 sync 方法等待 connect 建立连接完毕,是一个阻塞方法,知道连接建立
                .sync()
                // 获取 channel 对象,它即为通道抽象,可以进行数据读写操作
                .channel()
                // 写入消息并清空缓冲区,不管收发数据,都会走handle,调用处理器内部的方法
                .writeAndFlush("hello world");//把字符串转成了bytebuf
    }
}
复制代码

可以看到其实客户端代码与服务端代码类似,下面对这段客户端的代码进行解读:

  1. new Bootstrap()启动一个客户端
  2. group(new NioEventLoopGroup())打开一个事件循环组,可以向其中添加handler
  3. channel(NioSocketChannel.class)选择客户 Socket 实现类,NioSocketChannel 表示基于 NIO 的客户端实现
  4. handler(new ChannelInitializer<NioSocketChannel>()添加 ChannelInitializer 处理器,它的作用是待客户端SocketChannel 建立连接 后,执行initChannel以便添加更多的处理器
  5. connect(new InetSocketAddress("localhost", 8080))指定要连接的服务器和端口
  6. sync()的作用是阻塞,他等待connect连接完毕
  7. channel()获取 channel 对象,它即为通道抽象,可以进行数据读写操作
  8. writeAndFlush("hello world")写入消息并清空缓冲区,无论收发数据,都会通过handle,调用处理器内部的方法

执行流程

在这里插入图片描述

有以下几点执行顺序与代码顺序不同:

  1. 服务器端初始化了ChannelInitializer 处理器后,会执行最后一行的bind方法进行绑定端口号,之后等待客户端的连接
  2. 客户端初始化ChannelInitializer后会去执行connect方法连接服务端,连接未成功之前会阻塞住,在连接成功后会立即执行上面的initChannel方法来添加handler
  3. 客户端拿到channel连接对象后发送数据"hello world",然后添加handler会把String转成ByteBuf(类似于ByteBuffer)后发送给服务端
  4. 服务端发现有读事件发生后,会启用事件循环组EventLoopGroup中的一个EventLoop去处理这个读事件,调用具体的handler进行处理

组件解释

  • eventLoop 可以理解为处理数据的工人

    • eventLoop 可以管理多个 channel 的 io 操作,并且一旦 eventLoop 负责了某个 channel,就会将其与channel进行绑定(channel使用工人1发送数据了,之后channel要接收数据,那么还是使用工人1进行处理),即以后该 channel 中的 io 操作都由该 eventLoop 负责,这是为了线程安全,防止消息覆盖了
    • eventLoop 既可以执行 io 操作,也可以进行任务处理,每个 eventLoop 有自己的任务队列,队列里可以堆放多个 channel 的待处理任务
    • eventLoop 按照 pipeline 顺序,依次按照 handler 的规划(代码)处理数据,可以为每个 handler 指定不同的 eventLoop
  • handler 可以理解为数据的处理工序

    • 工序有多道,合在一起就是 pipeline(传递途径),pipeline 负责发布事件(读、读取完成…)传播给每个 handler, handler 对自己感兴趣的事件进行处理(重写了相应事件处理方法)

    • pipeline 中有多个 handler,处理时会依次调用其中的 handler

    • handler 分 Inbound 和 Outbound 两类

      • Inbound 入站,写入
      • Outbound 出站,写出
  • channel 可以理解为数据的通道

  • msg 理解为流动的数据,最开始输入是 ByteBuf,但经过 pipeline 中的各个 handler 加工,会变成其它类型对象,最后输出又变成 ByteBuf

猜你喜欢

转载自juejin.im/post/7032989469909352462