Netty 学习笔记(一)

前言

在学习了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用于创建客户端实例。
  • 两个EventLoopGroupbossworker,涉及Netty线程模型,服务端有两个group,客户端只有一个,这就是Netty中的线程池。
  • Netty中的channel,NioServerSocketChannel对应ServerSocketChannelNioSocketChannel对应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对应SocketChannelNioServerSocketChannel对应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接口。

来自javadoop.com

接下来看一个实现类: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()方法返回。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值