源码阅读-Netty Overview and Channel

Posted by keys961 on July 6, 2019

1. Overview

先上一个Echo的例子:

example

上述例子中,左侧是服务端,右侧是客户端。

可以看到,代码中涉及到6部分:

  • Bootstrap:启动服务端/客户端的引导类,服务端需要使用ServerBootstrap

  • EventLoopGroup:事件处理线程池,服务端需要bossworker2个线程池(Netty线程模型,本质为多路复用),而客户端只需要1个

  • Channel:两端全双工的通信通道,可支持多种协议,这里分别是NioServerSocketChannelNioSocketChannel

  • Handler:用于处理每个新请求

    • InboundHandler:用于处理请求的接收
    • OutboundHandler:用于处理响应的发送

    服务端上:

    • handler:用于处理新连接

    • childHandler:用于处理连接后的请求

  • ChannelPipeline:当多个handler配置后,会组成一个pipeline,用于决定handler的执行顺序

  • ChannelFuture:类似于Java的Future

首先我们将上面几个组件,然后过一遍connectbind的流程。

2. Netty Channel

这里只讨论最常用了TCP的Channel,它和JDK NIO的Channel拥有相同的含义。

Netty中,和Java NIO一样,服务端和客户端使用自己的Channel

  • 服务端:NioServerSocketChannel,对应NIO的ServerSocketChannel
  • 客户端:NioSocketChannel,对应NIO的SocketChannel

在创建服务端/客户端时,通过channel()配置,它会创建一个工厂,连接创建时,对应的Channel也会被创建(通过反射):

  • 服务端:bind()
  • 客户端:connect()

2.1. 客户端Channel

它在connect()时创建:

1
2
3
4
5
6
7
8
public ChannelFuture connect(SocketAddress remoteAddress) {
    if (remoteAddress == null) {
        throw new NullPointerException("remoteAddress");
    }
    validate();
    // 这一步
    return doResolveAndConnect(remoteAddress, config.localAddress());
}

最后调入doResolveAndConnect方法:

1
2
3
4
5
6
7
private ChannelFuture doResolveAndConnect(final SocketAddress remoteAddress, final SocketAddress localAddress) {
    // Channel创建主要是这一步: initAndRegister
	final ChannelFuture regFuture = initAndRegister();
    final Channel channel = regFuture.channel();
    // 后面之后再讲
    // ...
}

这里进入initAndRegister

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
final ChannelFuture initAndRegister() {
	Channel channel = null;
    try {
        // 1. channelFactory通过反射创建Channel
        channel = channelFactory.newChannel();
        // 2. 涉及channelPipeline的配置初始化
        init(channel);
    } catch(Throwable t) {
        // ...
    }
    // ...
    // 3. 将channel注册到event group中
    ChannelFuture regFuture = group().register(channel);
    // ...
    return regFuture
}

这里只讲第一步,后两步之后讲。

channelFactory反射调用的NioSocketChannel的构造方法:

  • 首先,创建socket句柄:调用NioSocketChannelnewSocket方法,它最后调用参数中的SelectorProvider#openSocketChannel()方法,创建了一个SocketChannelImpl实例,实例初始化时,做了下面几件事:

    SocketChannelImplSocketChannel的实现

    • 调用Net#socket0创建socket句柄
    • 记录socket句柄
  • 然后,初始化channel,步骤如下:

    • AbstractChannel,创建channelIdChannelId), unsafeUnsafe), pipelinePipeline),这些东西以后说
    • AbstractNioChannel中,设置channel为非阻塞,且设置监听事件SelectionKey.OP_READ(即客户端只监听接收数据的事件
    • NioSocketChannel中创建配置,这步无关紧要

2.2. 服务端Channel

它在bind()时创建,会进入doBind(addr)方法:

1
2
3
4
5
private ChannelFuture doBind(final SocketAddress localAddress) {
    final ChannelFuture regFuture = initAndRegister();
    final Channel channel = regFuture.channel();
    // ...
}

依旧和客户端类似,会通过工厂创建(反射),这里是创建NioServerSocketChannel

  • 首先,和客户端类似,创建socket句柄:调用NioServerSocketChannelnewSocket方法,它最后调用参数中的SelectorProvider#openServerSocketChannel()方法,创建了一个ServerSocketChannelImpl实例,实例初始化时,做了下面几件事:

    ServerSocketChannelImplServerSocketChannel的实现

    • 调用Net#socket0创建socket句柄(和客户端的不同,这里reusetrue
    • 记录socket句柄
  • 然后,也和客户端类似,初始化channel

    • AbstractChannel,创建channelIdChannelId), unsafeUnsafe), pipelinePipeline),这些东西以后说
    • AbstractNioChannel中,设置channel为非阻塞,且设置监听事件SelectionKey.OP_ACCEPT(即服务端关心连接事件
    • NioServerSocketChannel中创建配置

2.3. 总结

从上面可知,很简单,Netty中,和Java NIO一样,服务端和客户端使用自己的Channel,但是它们只是包装了JDK NIO的Channel实现:

  • 服务端:NioServerSocketChannel,对应NIO的ServerSocketChannel,监听ACCEPT
  • 客户端:NioSocketChannel,对应NIO的SocketChannel,监听READ

2.4. 下一篇文章

上述创建中涉及到2个很重要的组件:

  • EventGroup
  • ChannelPipeline

但在此之前,先说明一下Netty的FuturePromise,代表性的就是ChannelFuture