前言
在学习了NIO相关API后,我们觉得,虽然NIO可以大幅提升网络传输的效率,但是它太复杂而且难以debug,埋着一些令人难以发现的坑。因此我们急需一个网络应用框架,封装一些底层细节(比如连接的实现,异常处理,线程池),让我们能专注于业务逻辑的实现,增强代码的可读性,甚至能更高效的做并发处理。
这就是Netty-一个异步事件驱动的网络应用框架。
Netty可以:
- 随意的切换IO模型和NIO模型
- 对底层线程、selector做了很多细小的优化,reactor线程模型做到更高效的并发处理
- 解决JDK的一些Bug,比如空轮询
- 不用手动实现常见的通信协议
- 健壮性有保证
例子
写一个简单的服务端:
public class EchoServer {
public static void main(String[] args) throws InterruptedException {
ServerBootstrap serverBootstrap = new ServerBootstrap();
NioEventLoopGroup boss = new NioEventLoopGroup();
NioEventLoopGroup worker = new NioEventLoopGroup();
ChannelFuture f = serverBootstrap.group(boss, worker)
.channel(NioServerSocketChannel.class)
.handler(new LoggingHandler(LogLevel.INFO))
.childHandler(new ChannelInitializer<NioSocketChannel>() {
@Override
protected void initChannel(NioSocketChannel nioSocketChannel) throws Exception {
nioSocketChannel.pipeline().addLast(new StringDecoder());
nioSocketChannel.pipeline().addLast(new SimpleChannelInboundHandler<String>() {
@Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext, String s) throws Exception {
System.out.println(s);
}
});
}
}).bind(8000).sync();
f.channel().closeFuture().sync();
boss.shutdownGracefully();
worker.shutdownGracefully();
}
}
客户端:
public class EchoClient {
public static void main(String[] args) throws InterruptedException {
Bootstrap bootStrap = new Bootstrap();
NioEventLoopGroup group = new NioEventLoopGroup();
bootStrap.group(group)
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<Channel>() {
@Override
protected void initChannel(Channel channel) throws Exception {
channel.pipeline().addLast(new StringEncoder());
}
});
Channel ch = bootStrap.connect("127.0.0.1", 8000).channel();
while (true) {
ch.writeAndFlush(new Date() + ": hello world!");
Thread.sleep(2000);
}
}
}
可以看到,需要我们开发的部分是handler()
和childHandler(...)
方法中指定的handler,还有Netty源码中提供的现成handler,如LoggingHandler
,这种handler可以直接拿过来用。
上述代码涉及到的内容:
ServerBootStrap
类用于创建服务端实例,BootStrap
用于创建客户端实例。- 两个
EventLoopGroup
:boss
和worker
,涉及Netty线程模型,服务端有两个group,客户端只有一个,这就是Netty中的线程池。 - Netty中的channel,
NioServerSocketChannel
对应ServerSocketChannel
,NioSocketChannel
对应SocketChannel
.当然还有其他协议例如UDP对应的NioDatagramChannel
。 - 服务端
handler(...)
方法指定的handler是给服务端收到新请求的时候处理用的。右面handler(...)
指定了客户端处理请求过程中需要使用的handlers.
本例子中,在childHandler里指定了多个handler,就使用了
ChannelInitializer
.如果不需要多个handler,直接写.childHandler(new xxxHandler())
即可。
-
pipeline:一个常见的“管道”概念,指定多个handler的情况(使用ChannelInitalizer),它们就会组成一个pipeline,可以理解为拦截器。每一个
NioSocketChannel
或者NioServerSocketChannel
实例内部都会有pipeline实例,内部涉及到handler的执行顺序。 -
ChannelFuture: 和JDK的Future接口类似,涉及到Netty中的异步编程.
Netty中的Channel
和NIO中的Channel是一对一的关系,NioSocketChannel
对应SocketChannel
, NioServerSocketChannel
对应ServerSocketChannel
。
在客户端和服务端的bootStrap启动过程中都会调用channel(...)
方法。
channel()
方法的源码:
//AbstractBootStrap.class
public B channel(Class<? extends C> channelClass) {
if(channelClass == null) {
throw new NullPointerException("channelClass");
} else {
return this.channelFactory((io.netty.channel.ChannelFactory)(new ReflectiveChannelFactory(channelClass)));
}
}
该方法只是设置了channelFactory为ReflectiveChannelFactory的一个实例,接下来看ReflectiveChannelFactory是什么:
public class ReflectiveChannelFactory<T extends Channel> implements ChannelFactory<T> {
private final Class<? extends T> clazz;
public ReflectiveChannelFactory(Class<? extends T> clazz) {
if(clazz == null) {
throw new NullPointerException("clazz");
} else {
this.clazz = clazz;
}
}
public T newChannel() {
try {
return (Channel)this.clazz.getConstructor(new Class[0]).newInstance(new Object[0]);
} catch (Throwable var2) {
throw new ChannelException("Unable to create Channel from class " + this.clazz, var2);
}
}
public String toString() {
return StringUtil.simpleClassName(this.clazz) + ".class";
}
}
ChannelFactory接口中的唯一方法是newChannel()
方法。在ReflectiveChannelFactory方法中使用了反射调用Channel的无参构造方法来创建Channel,这样只需要看ChannelFactory的newChannel()
方法什么时候被调用就可以了。
- 对于NioSocketChannel,它充当客户端的功能,创建时机在
connect(...)
的时候; - 对于NioServerSocketChannel,它充当服务端功能,创建时机在
bind(...)
的时候。
那么NioSocketChannel是如何与JDK的SocketChannel关联在一起的呢?
我们通过最终NioSocketChannel的创建过程,找到connect(...)
的时机:
//BootStrap.class
public ChannelFuture connect(String inetHost, int inetPort) {
return this.connect(InetSocketAddress.createUnresolved(inetHost, inetPort));
}
public ChannelFuture connect(SocketAddress remoteAddress) {
if(remoteAddress == null) {
throw new NullPointerException("remoteAddress");
} else {
this.validate();
return this.doResolveAndConnect(remoteAddress, this.config.localAddress());
}
}
private ChannelFuture doResolveAndConnect(final SocketAddress remoteAddress, final SocketAddress localAddress) {
ChannelFuture regFuture = this.initAndRegister();
final Channel channel = regFuture.channel();
......
}
点进去看initAndRegister()
方法:
final ChannelFuture initAndRegister() {
Channel channel = null;
try {
// 前面我们说过,这里会进行 Channel 的实例化
channel = channelFactory.newChannel();
init(channel);
} catch (Throwable t) {
...
}
...
return regFuture;
}
核心代码是channelFactory.newChannel()
,这里调用Channel的无参构造方法。
回头看NioSocketChannel的构造方法:
public NioSocketChannel() {
// SelectorProvider 实例用于创建 JDK 的 SocketChannel 实例
this(DEFAULT_SELECTOR_PROVIDER);
}
public NioSocketChannel(SelectorProvider provider) {
// 看这里,newSocket(provider) 方法会创建 JDK 的 SocketChannel
this(newSocket(provider));
}
点开newSocket(provider)
:
private static SocketChannel newSocket(SelectorProvider provider) {
try {
// 创建 SocketChannel 实例
return provider.openSocketChannel();
} catch (IOException e) {
throw new ChannelException("Failed to open a socket.", e);
}
}
所以NioSocketChannel在实例化过程中,会先实例化底层的SocketChannel实例,同理NioServerSocketChannel会实例化底层的ServerSocketChannel实例。
而NioSocketChannel在构造过程中,实例化了内部的NioSocketChannelConfig实例,用于保存channel配置信息。
public NioSocketChannel(Channel parent, SocketChannel socket) {
super(parent, socket);
config = new NioSocketChannelConfig(this, socket.socket());
}
super()方法指向AbstractNioByteChannel类的构造方法,第一行调用了父类构造器,之后设置了非阻塞,设置了SelectionKey.OP_READ事件。这个是SocketChannel(客户端)关心的:
protected AbstractNioByteChannel(Channel parent, SelectableChannel ch) {
// 毫无疑问,客户端关心的是 OP_READ 事件,等待读取服务端返回数据
super(parent, ch, SelectionKey.OP_READ);
}
// 然后是到这里
protected AbstractNioChannel(Channel parent, SelectableChannel ch, int readInterestOp) {
super(parent);
this.ch = ch;
// 我们看到这里只是保存了 SelectionKey.OP_READ 这个信息,在后面的时候会用到
this.readInterestOp = readInterestOp;
try {
// ******设置 channel 的非阻塞模式******
ch.configureBlocking(false);
} catch (IOException e) {
......
}
}
而对于NioServerSocketChannel而言,也设置了非阻塞,设置了SelectionKey.OP_ACCEPT事件,是服务端关心的。
Future Promise
在服务端和客户端代码中,ServerBootStrap、BootStrap的实例调用bind(PORT).sync()
方法,得到一个ChannelFuture
对象,是一个实现了Future的类。在线程池ThreadPoolExecutor
中经常使用,最常用的方法是isDone()
和get()
。
Netty的Future接口继承了concurrent包中的Future接口,并且增添了一些方法。
//java.util.concurrent.Future
public interface Future<V> {
// 取消该任务
boolean cancel(boolean mayInterruptIfRunning);
// 任务是否已取消
boolean isCancelled();
// 任务是否已完成
boolean isDone();
// 阻塞获取任务执行结果
V get() throws InterruptedException, ExecutionException;
// 带超时参数的获取任务执行结果
V get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
}
// io.netty.util.concurrent.Future
public interface Future<V> extends java.util.concurrent.Future<V> {
// 是否成功
boolean isSuccess();
// 是否可取消
boolean isCancellable();
// 如果任务执行失败,这个方法返回异常信息
Throwable cause();
// 添加 Listener 来进行回调
Future<V> addListener(GenericFutureListener<? extends Future<? super V>> listener);
Future<V> addListeners(GenericFutureListener<? extends Future<? super V>>... listeners);
Future<V> removeListener(GenericFutureListener<? extends Future<? super V>> listener);
Future<V> removeListeners(GenericFutureListener<? extends Future<? super V>>... listeners);
// 阻塞等待任务结束,如果任务失败,将“导致失败的异常”重新抛出来
Future<V> sync() throws InterruptedException;
// 不响应中断的 sync(),这个大家应该都很熟了
Future<V> syncUninterruptibly();
// 阻塞等待任务结束,和 sync() 功能是一样的,不过如果任务失败,它不会抛出执行过程中的异常
Future<V> await() throws InterruptedException;
Future<V> awaitUninterruptibly();
boolean await(long timeout, TimeUnit unit) throws InterruptedException;
boolean await(long timeoutMillis) throws InterruptedException;
boolean awaitUninterruptibly(long timeout, TimeUnit unit);
boolean awaitUninterruptibly(long timeoutMillis);
// 获取执行结果,不阻塞。我们都知道 java.util.concurrent.Future 中的 get() 是阻塞的
V getNow();
// 取消任务执行,如果取消成功,任务会因为 CancellationException 异常而导致失败
// 也就是 isSuccess()==false,同时上面的 cause() 方法返回 CancellationException 的实例。
// mayInterruptIfRunning 说的是:是否对正在执行该任务的线程进行中断(这样才能停止该任务的执行),
// 似乎 Netty 中 Future 接口的各个实现类,都没有使用这个参数
@Override
boolean cancel(boolean mayInterruptIfRunning);
}
任务结束,回调listener就可以了,不一定要主动调用isDone()
来获取状态,或通过get()
阻塞方法来获取值。
sync()
内部调用await()
方法,等await()
方法返回后,检查下这个任务是否失败,如果失败,重新将导致失败的异常抛出。单独使用await(),任务抛出异常后,await()方法会返回,但是不会抛出异常,而sync()
方法返回的同时会抛出异常。
接下来看和channel关联的,Future的子接口ChannelFuture
:
public interface ChannelFuture extends Future<Void> {
// ChannelFuture 关联的 Channel
Channel channel();
// 覆写以下几个方法,使得它们返回值为 ChannelFuture 类型
@Override
ChannelFuture addListener(GenericFutureListener<? extends Future<? super Void>> listener);
@Override
ChannelFuture addListeners(GenericFutureListener<? extends Future<? super Void>>... listeners);
@Override
ChannelFuture removeListener(GenericFutureListener<? extends Future<? super Void>> listener);
@Override
ChannelFuture removeListeners(GenericFutureListener<? extends Future<? super Void>>... listeners);
@Override
ChannelFuture sync() throws InterruptedException;
@Override
ChannelFuture syncUninterruptibly();
@Override
ChannelFuture await() throws InterruptedException;
@Override
ChannelFuture awaitUninterruptibly();
// 用来标记该 future 是 void 的,
// 这样就不允许使用 addListener(...), sync(), await() 以及它们的几个重载方法
boolean isVoid();
}
通过大量的方法重写,使得返回值从Future
变为ChannelFuture
。
Promise接口同样继承了Netty的Future
接口,增加了一些Promise的内容:
public interface Promise<V> extends Future<V> {
// 标记该 future 成功及设置其执行结果,并且会通知所有的 listeners。
// 如果该操作失败,将抛出异常(失败指的是该 future 已经有了结果了,成功的结果,或者失败的结果)
Promise<V> setSuccess(V result);
// 和 setSuccess 方法一样,只不过如果失败,它不抛异常,返回 false
boolean trySuccess(V result);
// 标记该 future 失败,及其失败原因。
// 如果失败,将抛出异常(失败指的是已经有了结果了)
Promise<V> setFailure(Throwable cause);
// 标记该 future 失败,及其失败原因。
// 如果已经有结果,返回 false,不抛出异常
boolean tryFailure(Throwable cause);
// 标记该 future 不可以被取消
boolean setUncancellable();
// 这里和 ChannelFuture 一样,对这几个方法进行覆写,目的是为了返回 Promise 类型的实例
@Override
Promise<V> addListener(GenericFutureListener<? extends Future<? super V>> listener);
@Override
Promise<V> addListeners(GenericFutureListener<? extends Future<? super V>>... listeners);
@Override
Promise<V> removeListener(GenericFutureListener<? extends Future<? super V>> listener);
@Override
Promise<V> removeListeners(GenericFutureListener<? extends Future<? super V>>... listeners);
@Override
Promise<V> await() throws InterruptedException;
@Override
Promise<V> awaitUninterruptibly();
@Override
Promise<V> sync() throws InterruptedException;
@Override
Promise<V> syncUninterruptibly();
}
Promise实例内部是一个任务,任务的执行往往是异步的,通常由一个线程池来执行。Promise提供的setSuccess(V result)或setFailure(Throwable t) 将来会被某个执行任务的线程在执行完成以后调用,同时那个线程在调用完后会回调listeners的回调函数(可以由自己执行,可以创建新的线程执行,可以将回调任务提交到某个线程池执行)。一旦调用了两个方法之一,await()或sync()的线程就会从等待中返回(还记得它们两个的区别吗?)。
所以有两种模式:一种是用await(),等await()方法返回得到promise的处理结果,然后处理它。
自然联想到AIO中的Future实例
另一种是提供Listener实例,然后执行了setSuccess或者SetFailure后回调listener中的处理方法。
而ChannelPromise继承了前面的ChannelFuture和Promise接口。
接下来看一个实现类:DefaultPromise。
DefaultPromise继承了AbstractFuture类和Promise接口。
有以下几个属性:
volatile Object result - 保存执行结果
EventExecutor executor - 执行任务的线程池
private Object listeners - 监听者,回调函数, 任务结束后执行
private short waiters - 调用sync()或await()进行等待的线程数量,等待promise的线程数
private boolean notifyingListeners - 是否正在唤醒等待线程,防止重复唤醒执行Listeners的回调方法。
该类没有实现ChannelFuture,所以不能和channel联系起来。后面会碰到另一个类DefaultChannelPromise,这个类综合ChannelFuture和Promise,但是它的实现大部分继承自DefaultPromise。
public Promise<V> setSuccess(V result) {
if(this.setSuccess0(result)) {
this.notifyListeners();
return this;
} else {
throw new IllegalStateException("complete already: " + this);
}
}
public boolean trySuccess(V result) {
if(this.setSuccess0(result)) {
this.notifyListeners();
return true;
} else {
return false;
}
}
@Override
public Promise<V> sync() throws InterruptedException {
await();
//如果任务是失败的,重新抛出相应的的异常
rethrowIfFailed();
return this;
}
实例代码:
public static void main(String[] args) {
//构造线程池
EventExecutor executor = new DefaultEventExecutor();
//创建DefaultPromise
Promise promise = new DefaultPromise(executor);
//下面给这个promise添加两个listener
promise.addListener(new GenericFutureListener<Future<Integer>>() {
@Override
public void operationComplete(Future future) throws Exception {
if (future.isSuccess()) {
System.out.println("任务结束,结果:" + future.get());
} else {
System.out.println("任务失败,异常:" + future.cause());
}
}
}).addListener(new GenericFutureListener<Future<Integer>>() {
@Override
public void operationComplete(Future future) throws Exception {
System.out.println("任务结束");
}
});
//提交任务到线程池 五秒后之行结束 设置执行promise的结果
executor.submit(new Runnable(){
@Override
public void run() {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
}
//设置promise的结果
//promise.setFailure(new RuntimeException());
promise.setSuccess(123456);
}
});
//main 线程阻塞等待执行结果
try {
promise.sync();
} catch (InterruptedException e) {
}
}
具体任务不一定在executor 中执行。任务结束之后,需要调用promise.setSuccess(result)作为通知。
promise代表的future是不需要和线程池搅在一起,Future只关心任务是否结束以及任务的处理结果,至于是哪个线程池结束的任务,future并不关心。
返回一开始的代码, 服务端部分:
try {
...
ChannelFuture f = b.bind(PORT).sync();
f.channel().closeFuture().sync();
} finally {
boss.shutdownGracelly();
worker.shutdownGracelly();
}
bind(PORT)返回一个ChannelFuture,这是个异步方法,sync()将其阻塞,执行成功后会返回success,此时sync()返回,进入下面一行:closeFuture()返回一个ChannelFuture,调用sync()方法,如果有线程关闭了NioServerSocketChannel,则线程设置future的状态(setSuccess或setFailure),sync()方法返回。