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

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

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

什么是Netty

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

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

Hello World

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

服务端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
java复制代码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方法对端口号进行绑定

客户端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
java复制代码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

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

0%