Netty编程(一)—— 初识Netty+超全注释
之前的博客介绍了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);
}
}
下面对这段代码进行解读:
- 首先使用
new ServerBootstrap()
打开一个服务端的启动器,它负责装配下面的Nettty组件,同时会启动服务器 group(new NioEventLoopGroup())
用来创建一个事件循环组EventLoopGroup,可以把它理解成是Selector+线程池的组合channel(NioServerSocketChannel.class)
选择服务器的ServerSocketChannel 实现childHandler(new ChannelInitializer<NioSocketChannel>()
中的child可以理解成worker,是负责处理读写事件的,这个方法决定了child执行哪些操作(handler)- channel的作用是待客户端SocketChannel建立连接后与客户端进行读写的通道,执行initChannel初始化,作用是添加别的handler
initChannel(NioSocketChannel nioSocketChannel)
方法是用来添加具体的handler,具体是使用nioSocketChannel.pipeline().addLast
来添加,代码中添加了第一个handler是解码,将客户端发来的数据变成String,添加了第二个handler是自定义handler,并且这个handler需要使用上一个解码handler的结果。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
}
}
可以看到其实客户端代码与服务端代码类似,下面对这段客户端的代码进行解读:
new Bootstrap()
启动一个客户端group(new NioEventLoopGroup())
打开一个事件循环组,可以向其中添加handlerchannel(NioSocketChannel.class)
选择客户 Socket 实现类,NioSocketChannel 表示基于 NIO 的客户端实现handler(new ChannelInitializer<NioSocketChannel>()
添加 ChannelInitializer 处理器,它的作用是待客户端SocketChannel 建立连接 后,执行initChannel以便添加更多的处理器connect(new InetSocketAddress("localhost", 8080))
指定要连接的服务器和端口sync()
的作用是阻塞,他等待connect连接完毕channel()
获取 channel 对象,它即为通道抽象,可以进行数据读写操作writeAndFlush("hello world")
写入消息并清空缓冲区,无论收发数据,都会通过handle,调用处理器内部的方法
执行流程
有以下几点执行顺序与代码顺序不同:
- 服务器端初始化了ChannelInitializer 处理器后,会执行最后一行的bind方法进行绑定端口号,之后等待客户端的连接
- 客户端初始化ChannelInitializer后会去执行connect方法连接服务端,连接未成功之前会阻塞住,在连接成功后会立即执行上面的initChannel方法来添加handler
- 客户端拿到channel连接对象后发送数据"hello world",然后添加handler会把String转成ByteBuf(类似于ByteBuffer)后发送给服务端
- 服务端发现有读事件发生后,会启用事件循环组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