前言

本文就来来记录一下我们在启动Netty时,底层大概都做了哪些事情。不过因为我对Netty还只是刚刚入门,可能会跳过某些细节,也有可能会有写错的地方,如果漏了或错了,日后发现会进行补充或改正。

我将Netty启动简单分为了四个阶段,每一篇记录一个。

Netty version:4.1.6


先贴下代码

Server

java
  • 01
  • 02
  • 03
  • 04
  • 05
  • 06
  • 07
  • 08
  • 09
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.*; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioServerSocketChannel; import io.netty.util.AttributeKey; public final class Server { public static void main(String[] args) throws Exception { // 两大线程 // 对应Socket中Server的启动监听线程 EventLoopGroup bossGroup = new NioEventLoopGroup(1); // 对应Socket中Client中主函数的线程 EventLoopGroup workerGroup = new NioEventLoopGroup(); try { // 辅助类 ServerBootstrap server = new ServerBootstrap(); // 装配线程 server.group(bossGroup, workerGroup) // 设置Channel的类型 .channel(NioServerSocketChannel.class) // 给每个客户端的连接设置一些tcp的基本属性 .childOption(ChannelOption.TCP_NODELAY, true) // 每次创建客户端时,绑定一些基本的属性 .childAttr(AttributeKey.newInstance("childAttr"), "childAttrValue") // 服务端启动时做的逻辑 .handler(new ServerHandler()) // 给pipeline配置handler,Channel发生某种变化时对应的处理逻辑,todo 幕信里就写了一条链 .childHandler(new ChannelInitializer<SocketChannel>() { @Override public void initChannel(SocketChannel ch) { // 往pipeline中添加Handler //ch.pipeline().addLast(new AuthHandler()); } }); // 辅助类,绑定端口,这里使用了同步 ChannelFuture future = server.bind(8888).sync(); // 关闭 future.channel().closeFuture().sync(); } finally { bossGroup.shutdownGracefully(); workerGroup.shutdownGracefully(); } } }

代码中的ServerHandler可以无视,因为追踪时暂时不会用到,所以暂时不贴。


下面的代码分析记录都是根据上面的代码。

启动的四个阶段


创建服务端Channel

大致流程:



从上面Server代码中的bind()这一段开始追踪:

java
  • 01
ChannelFuture future = server.bind(8888).sync();

进入bind()函数后,连跳,看到doBind():

进入doBind(),看到第一行如下,找到了流程第二步:

java
  • 01
final ChannelFuture regFuture = initAndRegister();

再进入initAndRegister(),找到如下一段,这段就是创建Channel的核心:

java
  • 01
channel = channelFactory.newChannel();

再进入newChannel(ReflectiveChannelFactory),发现底层是通过反射调用无参构造函数的:

java
  • 01
return clazz.newInstance();

虽然知道是通过反射构造的,但具体是哪个类呢?其实这个在我们写代码的时候自定义了,视角回到上面的Server的一段代码:

java
  • 01
.channel(NioServerSocketChannel.class)

我们进入channel(),可以看到:

java
  • 01
  • 02
  • 03
  • 04
  • 05
  • 06
public B channel(Class<? extends C> channelClass) { if (channelClass == null) { throw new NullPointerException("channelClass"); } return channelFactory(new ReflectiveChannelFactory<C>(channelClass)); }

可以看到:

  • 其实是将我们设置的类,经几度包装最终返回一个channelFactory,也就是上面使用反射时构造的类。
  • channelFactory()函数没有什么特别操作,就是转下类型,所以我们应该将重点放到ReflectiveChannelFactory的构造函数上

服务端创建Channel其实到反射调用构造那里就已经结束了,下面只是继续探究ReflectiveChannelFactory的构造函数到底做了什么。



ReflectiveChannelFactory构造函数

先展示一下大致流程:

图中的方法暂时先忽略参数。


现在,我们先进入NioServerSocketChannel的构造函数:

java
  • 01
  • 02
  • 03
public NioServerSocketChannel() { this(newSocket(DEFAULT_SELECTOR_PROVIDER)); }

再进入上面的newSocket()函数:

java
  • 01
  • 02
  • 03
  • 04
  • 05
  • 06
  • 07
  • 08
private static ServerSocketChannel newSocket(SelectorProvider provider) { try { return provider.openServerSocketChannel(); } catch (IOException e) { throw new ChannelException( "Failed to open a server socket.", e); } }

可以看到方法参数是SelectorProvider,返回值是ServerSocketChannel,这两是jdk的类,说明此处Netty是调用jdk的接口去创建类的,只是后来再进一步封装。(两个class都是jdk 1.4后引入的nio)


现在将视角转回最初的NioServerSocketChannel无参构造函数,进入this(),我们可以看见config类NioServerSocketChannelConfig,由于我们上面的Server代码设置了tcp,所以这里的配置类会帮我们初始化tcp的一些参数:

java
  • 01
  • 02
  • 03
  • 04
  • 05
  • 06
  • 07
/** * 位置:io.netty.channel.socket.nio.NioServerSocketChannel#NioServerSocketChannel(java.nio.channels.ServerSocketChannel) **/ public NioServerSocketChannel(ServerSocketChannel channel) { super(null, channel, SelectionKey.OP_ACCEPT); config = new NioServerSocketChannelConfig(this, javaChannel().socket()); }

然后,我们进入super函数:

java
  • 01
  • 02
  • 03
protected AbstractNioMessageChannel(Channel parent, SelectableChannel ch, int readInterestOp) { super(parent, ch, readInterestOp); }

继续super:

java
  • 01
  • 02
  • 03
  • 04
  • 05
  • 06
  • 07
  • 08
  • 09
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
protected AbstractNioChannel(Channel parent, SelectableChannel ch, int readInterestOp) { // parent为null,parent是给客户端channel存储服务端channel的 super(parent); this.ch = ch; this.readInterestOp = readInterestOp; try { // nio关键,就是这里设置了非阻塞I/O ch.configureBlocking(false); } catch (IOException e) { try { ch.close(); } catch (IOException e2) { if (logger.isWarnEnabled()) { logger.warn( "Failed to close a partially initialized socket.", e2); } } throw new ChannelException("Failed to enter non-blocking mode.", e); } }

其中ch.configureBlocking(false);就是负责配置非阻塞I/O。

再super,可以看到此处最终完成了AbstractChannel的构造:

java
  • 01
  • 02
  • 03
  • 04
  • 05
  • 06
  • 07
  • 08
protected AbstractChannel(Channel parent) { // parent为null,如果是客户端channel则不同 this.parent = parent; id = newId(); // 返回NioMessageUnsafe(服务端),如果是构造客户端channel,则返回NioSocketChannelUnsafe unsafe = newUnsafe(); pipeline = newChannelPipeline(); }

以上就是Netty服务端创建Channel的过程了,虽然暂时忽略了一些细节,不过算是对流程有一个大概的认识,如果之后有更多参考资料,会继续补全。

关于提到的客户端channel,其实就是是NioSocketChannel,有兴趣的可以自己追下它的构造方法,我会在后面遇到的时候再写博客记录。