Netty核心组件
为了后期更好地理解和进一步深入 Netty,有必要总体认识一下 Netty 所用到的核心组件以及他们在整个 Netty 架构中是如何协调工作的。Nettty 有如下几个核心组件:
这里简单说一下 Reactor 线程模型:
Reactor:把IO事件分配给对应的handler处理
Acceptor:处理客户端连接事件
Handler:处理非阻塞的任务
只有一个Reactor线程负责新的客户端连接,还要负责客户端的读写请求。 弊端很明显:如果Reactor线程负载过重,会无法及时响应其它客户的服务要求,导致客户端连接超时
> 应用案例:Redis
多线程模型
也是只有一个Reactor线程,但是它只负责事件的监听和响应,另外有一个线程池创建多个NIO线程去处理IO。虽然多线程模型从一定程度上减少了单Reactor线程的压力,但是还是无法解决高并发下单Reactor线程的的性能瓶颈
主从多线程模型
主从线程模型:一组线程池接收请求,一组线程池处理IO
原理是将Reactor线程拆分了mainReactor和subReactor两个部分,mainReactor只处理连接事件,读写事件交给subReactor来处理。业务逻辑还是由线程池来处理
这种模型使各个模块职责单一,降低耦合度,性能和稳定性都有提高
应用案例:netty
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。相当于是一对多的关系
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是Netty中用来传输数据的通道,每个Channel代表了客户端的连接
核心API一览:
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);
...
...
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();
}
}
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()
的倒序来的