Netty组件
Netty核心组件
为了后期更好地理解和进一步深入 Netty,有必要总体认识一下 Netty 所用到的核心组件以及他们在整个 Netty 架构中是如何协调工作的。Nettty 有如下几个核心组件:
- Channel
- EventLoop
- Handler
- Pipeline
一. EventLoop & EventLoopGroup
这里简单说一下 Reactor 线程模型:
Reactor:把IO事件分配给对应的handler处理
Acceptor:处理客户端连接事件
Handler:处理非阻塞的任务
- 单线程模型
只有一个Reactor线程负责新的客户端连接,还要负责客户端的读写请求。 弊端很明显:如果Reactor线程负载过重,会无法及时响应其它客户的服务要求,导致客户端连接超时
> 应用案例:Redis
-
多线程模型
也是只有一个Reactor线程,但是它只负责事件的监听和响应,另外有一个线程池创建多个NIO线程去处理IO。虽然多线程模型从一定程度上减少了单Reactor线程的压力,但是还是无法解决高并发下单Reactor线程的的性能瓶颈 -
主从多线程模型
主从线程模型:一组线程池接收请求,一组线程池处理IO
原理是将Reactor线程拆分了mainReactor和subReactor两个部分,mainReactor只处理连接事件,读写事件交给subReactor来处理。业务逻辑还是由线程池来处理
这种模型使各个模块职责单一,降低耦合度,性能和稳定性都有提高
应用案例:netty
1. EventLoop
EventLoop 字面理解就是事件循环,事件循环的意思就是:它运行在一个循环中,直到它停止
Netty 中使用 EventLoop 接口代表事件循环,EventLoop 是从EventExecutor 和ScheduledExecutorService 扩展而来,所以可以将任务(Runnable 或者 Callable)直接交给 EventLoop 执行
类关系图如下:
NioEventLoop (注意是NioEventLoop)继承于 SingleThreadEventLoop, 而 SingleThreadEventLoop 又继承于 SingleThreadEventExecutor。SingleThreadEventExecutor 是 Netty 中对本地线程的抽象, 它内部有一个 Thread thread 属性, 存储了一个本地 Java 线程. 因此我们可以认为, 一个 NioEventLoop 其实和一个特定的线程绑定, 并且在其生命周期内, 绑定的线程都不会再改变
EventLoop 代表了 Reactor 模型中的 Handler,主要需要处理IO事件和其他两种任务,分别为定时任务和一般任务
一个EventLoop,可以注册很多不同的Netty Channel。相当于是一对多的关系
2. EventLoopGroup
EventLoopGroup是一组EventLoop的抽象,主要目的是为了减少上下文切换,和资源重复利用
它的作用是把Channel绑定到一个的EventLoop上去,因此我们需要获取到里面的某个EventLoop
EventExecutor也提供了inEventLoop方法用户判断当前代码执行是不是在绑定的线程,如果不是,我们就需要通过提交任务的方式提交,如果是,我们就可以直接执行,因此我们可以看到很多类似代码
类的继承关系:
源码:
/**
* Return the next {@link EventLoop} to use
*/
@Override
EventLoop next();
/**
* Register a {@link Channel} with this {@link EventLoop}. The returned {@link ChannelFuture}
* will get notified once the registration was complete.
*/
ChannelFuture register(Channel channel);
/**
* Register a {@link Channel} with this {@link EventLoop} using a {@link ChannelFuture}. The passed
* {@link ChannelFuture} will get notified once the registration was complete and also will get returned.
*/
ChannelFuture register(ChannelPromise promise);
/**
* Register a {@link Channel} with this {@link EventLoop}. The passed {@link ChannelFuture}
* will get notified once the registration was complete and also will get returned.
*
* @deprecated Use {@link #register(ChannelPromise)} instead.
*/
@Deprecated
ChannelFuture register(Channel channel, ChannelPromise promise);
二. Channel
Channel是Netty中用来传输数据的通道,每个Channel代表了客户端的连接
核心API一览:
- EventLoop eventLoop()
返回该通道注册的事件轮询器。 - Channel parent()
返回该通道的父通道,如果是ServerSocketChannel实例则返回null,SocketChannel实例则返回对应的ServerSocketChannel。 - ChannelConfig config()
返回该通道的配置参数。 - boolean isOpen()
端口是否处于open,通道默认一创建isOpen方法就会返回true,close方法被调用后该方法返回false。 - boolean isRegistered()
是否已注册到EventLoop。 - boolean isActive()
通道是否处于激活。NioSocketChannel的实现是java.nio.channels.SocketChannel实例的isOpen()与isConnected()都返回true。NioServerSocketChannel的实现ServerSocketChannel.socket().isBound(),如果绑定到端口中,意味着处于激活状态。 - ChannelFuture closeFuture()
Future 模式的应用,调用该方法的目的并不是关闭通道,而是预先创建一个凭证(Future),等通道关闭时,会通知该 Future,用户可以通过该 Future 注册事件。 - ChannelFuture bind(SocketAddress localAddress)
Netty 服务端绑定到本地端口,开始监听客户端的连接请求。该过程会触发事件链(ChannelPipeline)。该部分将在后续讲解服务端启动流程时再详细分析。 - ChannelFuture connect(SocketAddress remoteAddress)
Netty客户端连接到服务端,该过程同样会触发一系列事件(ChannelPipeline)。该部分将在后续讲解客户端启动流程时再详细分析。 - ChannelFuture disconnect()
断开连接,但不会释放资源,该通道还可以再通过connect重新与服务器建立连接。 - ChannelFuture close()
关闭通道,回收资源,该通道的生命周期完全结束。 - ChannelFuture deregister()
取消注册。 - Channel read()
通道读,该方法并不是直接从读写缓存区读取文件,而是向NIO Selecor注册读事件(目前主要基于NIO)。当通道收到对端的数后,事件选择器会处理读事件,从而触发ChannelInboundHandler#channelRead 事件,然后继续触发ChannelInboundHandler#channelReadComplete(ChannelHandlerContext)事件。 - ChannelFuture write(Object msg)
向通道写字节流,会触发响应的写事件链,该方法只是会将字节流写入到通道缓存区,并不会调用flush方法写入通道中。 - Channel flush()
刷写所有挂起的消息(刷写到流中)。 - ChannelFuture writeAndFlush(Object msg)
相当于调用write与flush方法。
Netty中,真正帮助Channel完成IO读写操作的是它的内部类unsafe, 源码如下, 很多重要的功能在这个接口中定义, 下面列举的常用的方法
interface Unsafe {
// 把channel注册进EventLoop
void register(EventLoop eventLoop, ChannelPromise promise);
// todo 给channel绑定一个 adress,
void bind(SocketAddress localAddress, ChannelPromise promise);
// 把channel注册进Selector
void deregister(ChannelPromise promise);
// 从channel中读取IO数据
void beginRead();
// 往channe写入数据
void write(Object msg, ChannelPromise promise);
...
...
1. ChannelFuture
Netty 里面的IO操作全部是异步的
。这意味着,在调用结束时,无法保证IO操作已完成。但是它会返回给你一个ChannelFuture 实例,里面保存了IO操作的结果信息或状态
打个比方:
把客户端代码改成这样
public static void main(String[] args) {
EventLoopGroup eventExecutors = new NioEventLoopGroup();
try {
// 创建客户端启动对象
Channel channel = new Bootstrap()
.group(eventExecutors) // 设置线程组
.channel(NioSocketChannel.class) //设置 客户端通道的实现类
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) {
ch.pipeline().addLast(new StringEncoder()); // 解码器
}
})
.connect("localhost", 8000).channel();
channel.writeAndFlush("加入了连接");
} catch (Exception e) {
e.printStackTrace();
}
}
目的是:连接之后给服务端发送一个消息,
代码本身看着没有任何问题,但是运行之后,服务端无法收到消息
原因:
是因为connect方法是异步的,原理是在connect方法中执行连接的线程不是main线程,而是前面创建的nio线程,导致main线程执行发送消息的代码时,还没有连接成功(即channel还没有初始化成功),这就是netty框架中异步的一种体现,可以理解为netty中的所有操作时异步的
解决办法:
-
让主线程延迟几秒
这样确实可以,但是治标不治本 -
sync()方法,阻塞到直到执行结果出来
修改后代码如下:public static void main(String[] args) { EventLoopGroup eventExecutors = new NioEventLoopGroup(); try { // 创建客户端启动对象 ChannelFuture channelFuture = new Bootstrap() .group(eventExecutors) // 设置线程组 .channel(NioSocketChannel.class) //设置 客户端通道的实现类 .handler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel ch) { ch.pipeline().addLast(new StringEncoder()); // 解码器 } }) .connect("localhost", 8000); channelFuture.sync(); // 同步阻塞 到 执行完成 channelFuture.channel().writeAndFlush("加入了连接"); } catch (Exception e) { e.printStackTrace(); } }
-
addListener() 添加执行成功后的回调
修改后的代码:public static void main(String[] args) { EventLoopGroup eventExecutors = new NioEventLoopGroup(); try { // 创建客户端启动对象 ChannelFuture channelFuture = new Bootstrap() .group(eventExecutors) // 设置线程组 .channel(NioSocketChannel.class) //设置 客户端通道的实现类 .handler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel ch) { ch.pipeline().addLast(new StringEncoder()); // 解码器 } }) .connect("localhost", 8000); channelFuture.addListener(new ChannelFutureListener() { public void operationComplete(ChannelFuture future) { future.channel().writeAndFlush("加入了连接!"); } }); } catch (Exception e) { e.printStackTrace(); } }
三. Pipeline & Handler
Netty 使用 ChannelHandler 来处理 Channel 上的各种事件,分为入站、出站两种。所有 ChannelHandler 被连成一串,就是 Pipeline
-
入站处理器通常是 ChannelInboundHandlerAdapter 的子类,主要用来读取客户端数据,写回结果
-
出站处理器通常是 ChannelOutboundHandlerAdapter 的子类,主要对写回结果进行加工
一. 入站处理器
public static void main(String[] args) {
// 创建线程组
// bossGroup 处理链接请求
// workerGroup 处理客户端业务逻辑
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try{
// 创建服务端启动对象
new ServerBootstrap()
.group(bossGroup, workerGroup) // 设置两个线程组
.channel(NioServerSocketChannel.class) //设置tcpSocket通道 使用nio作为服务器通道
.childHandler(new ChannelInitializer<NioSocketChannel>() {
protected void initChannel(NioSocketChannel ch) {
ch.pipeline().addLast(new ChannelInboundHandlerAdapter(){
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
System.out.println("第一个入站消息处理器");
ctx.fireChannelRead(msg);
}
});
ch.pipeline().addLast(new ChannelInboundHandlerAdapter(){
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
System.out.println("第二个入站消息处理器");
ctx.channel().writeAndFlush(msg);
}
});
ch.pipeline().addLast(new ChannelOutboundHandlerAdapter(){
@Override
public void write(ChannelHandlerContext ctx, Object msg,
ChannelPromise promise) {
System.out.println("第一个出站消息处理器");
ctx.write(msg, promise);
}
});
ch.pipeline().addLast(new ChannelOutboundHandlerAdapter(){
@Override
public void write(ChannelHandlerContext ctx, Object msg,
ChannelPromise promise) {
System.out.println("第二个出站消息处理器");
ctx.write(msg, promise);
}
});
}
}).bind(8000).sync();
} catch (Exception e){
e.printStackTrace();
}
}
多个 入站处理器的处理顺序是根据 addLast()
的顺序来的
多个 出站处理器的处理顺序是根据 addLast()
的倒序来的