前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >小满读源码 · 从demo里找真相

小满读源码 · 从demo里找真相

作者头像
简熵
发布2023-03-06 21:34:54
2540
发布2023-03-06 21:34:54
举报
文章被收录于专栏:逆熵逆熵

目录

1、netty的核心组件概述

2、服务端启动之channel的诞生

3、NioServerSocketChannel的创建

终于有一天可以不是SSM,CRUD,不在业务上枯燥且乏味。用netty去构建公司的聊天系统,想想还有些兴奋呢。要用netty,那必须要去深入地研究它,学,方以致用。 网络中大佬们也对netty做了各种深入浅出的剖析解读,留下了很多宝贵的经验,值得学习和思考。小满也在这里记录下自己探索netty的行程,期待自己也能像大佬们,学习到属于自己的一套手撕源码的方法。

1、netty的核心组件概述

首先,把netty的源码拉到本地。netty的源码包里有一个netty-example,里面有很多测试示例,使用netty第一步就从启动用例echo开始体验一下。如下图就是一个服务端启动的代码示例,看起来很清爽。

以下是一个客户端的基本代码

从图中我们可以提取出来一些关键的组件:

  • EventLoopGroup&EventLoop
  • ServerBootstrap&Bootstrap
  • ChannelHandler
  • ChannelPipeline
  • ChannelFuture

初步启动,看到这些类和对象会想到这些组件,会有疑惑,也会有猜测,脑子可以试着想想他是怎么流转的,可能是怎么运作的。

还记得我们上文【小满寻秘境 · Reactor线程模型】讨论的Reactor线程模型么?如图所示,这是一个主从Reactor多线程模型。我们联想一下,Netty的实例代码和这个模型特别像。

EventLoopGroup,线程组的概念,一个EventLoopGroup包含多个EventLoop,提供了迭代EventLoop的能力,当一个连接到来,创建channel,EventLoopGroup会分配一个EventLoop给这个连接对应的channel,在这个连接整个生命周期中都只会和这个EventLoop绑定。

ServerBootstrap/Bootstrap,启动器,可以是客户端启动,通过connect()连接远程服务器;也可以是服务端启动,通过bind()的方法去监听本地端口。我们可以看到服务端是有俩个EventLoopGroup,而客户端只有一个,因为netty是基于事件驱动模型的,服务端既要处理客户端发起的连接请求,还要处理各个客户端建立的连接的I/O事件;而客户端只要一个线程组去处理I/O事件就可以了。

ChannelHandler,为啥netty这么好用?不管是编解码,还是各种转换,数据处理,ChannelHandler都能游刃有余的去处理,可插拔,流程很清晰,还有很多开箱即用的功能。

ChannelPipeline,处理链,就像责任链模式,正是这根链子将各种输入输出,数据处理的Handler类串起来,一个Handler处理完就传给下一个。

ChannelFuture,Future这个单词很容易就联想到异步回调,Netty的连接和绑定端口都是异步的,可以通过Future去做一些回调处理或者转换成同步等待。

02

服务端启动之channel的诞生

我们从服务端demo入手,可以看到调用bind()的方法是服务端启动的一个入口。

代码语言:javascript
复制
io.netty.bootstrap.AbstractBootstrap
代码语言:javascript
复制
/**
 * Create a new {@link Channel} and bind it.
 */
public ChannelFuture bind(SocketAddress localAddress) {
     //校验一些参数是否存在    validate();
    if (localAddress == null) {
        throw new NullPointerException("localAddress");
    }
    return doBind(localAddress);
}
代码语言:javascript
复制
private ChannelFuture doBind(final SocketAddress localAddress) {
//1 初始化和注册   final ChannelFuture regFuture = initAndRegister();final Channel channel = regFuture.channel();
    if (regFuture.cause() != null) {
        return regFuture;
    }

initAndRegister(),我们跟进去这个方法,可以看到这里通过channelFactory创建了Channel,当然这里的channel不是jdk中的对象,而是netty自己封装的。

代码语言:javascript
复制
io.netty.bootstrap.AbstractBootstrap

Q1:可以看到这里其实是利用了clazz的反射去创建了Channel对象,那么这个clazz是什么呢?是在哪里传入的呢?

代码语言:javascript
复制
io.netty.channel.ReflectiveChannelFactory

Q2:这个channelFactory是哪一个对象?

A2:我们找找看,还记得服务端demo中的一行配置,

代码语言:javascript
复制
       //配置serverchannel的类型,此处是netty自己封装的一个channel对象
.channel(NioServerSocketChannel.class)

这里我们传入了一个NioServerSocketChannel.class,并且我们在下面的代码中找到了答案,是ReflectiveChannelFactory,这个工厂类。

io.netty.bootstrap.AbstractBootstrap

return channelFactory(new ReflectiveChannelFactory<C>(channelClass));

A1:ReflectiveChannelFactory工厂类的构造函数中也只是做了一次赋值,那么我们也可以得出答案,clazz是NioServerSocketChannel.class。

io.netty.channel.ReflectiveChannelFactory

this.clazz = clazz;

而channelFactory()方法其实只是把ReflectiveChannelFactory对象赋值给了this.channelFactory。

代码语言:javascript
复制
io.netty.channel.ReflectiveChannelFactory@Deprecatedpublic B channelFactory(ChannelFactory<? extends C> channelFactory) {
    if (channelFactory == null) {
        throw new NullPointerException("channelFactory");
    }
    if (this.channelFactory != null) {
        throw new IllegalStateException("channelFactory set already");
    }
    this.channelFactory = channelFactory;
    return self();
}

03

NioServerSocketChannel的创建

首先我们可以看一下有哪些成员变量,看到有熟悉的SelectorProvider,netty是基于java的原生的NIO API封装开发的,那么我们就可以猜测接下来netty是如何与jdk的NIO API产生交集的。

代码语言:javascript
复制
private static final ChannelMetadata METADATA = new ChannelMetadata(false, 16);private static final SelectorProvider DEFAULT_SELECTOR_PROVIDER =
代码语言:javascript
复制
SelectorProvider.provider();

那么上面提到channel的创建是基于反射构造函数创建的,我们看看代码,这里调用了一个newSocket()方法,还将SelectorProvider传进去。

代码语言:javascript
复制
io.netty.channel.socket.nio.NioServerSocketChannel/** * Create a new instance
 */
public NioServerSocketChannel() {
    this(newSocket(DEFAULT_SELECTOR_PROVIDER));
}

果然,就是在这里我们通过一个openServerSocketChannel方法创建了一个原生的ServerSocketChannel对象。

代码语言:javascript
复制
io.netty.channel.socket.nio.NioServerSocketChannelprivate static ServerSocketChannel newSocket(SelectorProvider provider) {
    try {
        /**
         *  Use the {@link SelectorProvider} to open {@link SocketChannel}
代码语言:javascript
复制
            and so remove condition in
         *  {@link SelectorProvider#provider()} which is called by
代码语言:javascript
复制
           each ServerSocketChannel.open() otherwise.
         *
         *  See <a href="https://github.com/netty/netty/issues/2308">#2308</a>.
         */
        return provider.openServerSocketChannel();
    } catch (IOException e) {
        throw new ChannelException(
                "Failed to open a server socket.", e);
    }
}

我们来看一下NioServerSocketChannel的构造函数,这里面初始化了一个channelconfig的对象,并且把channel本身和原生的serversocket赋值给他,我们这里可以先猜测一下,前面我们设置可选TCP参数,肯定和这个对象有关。

代码语言:javascript
复制
io.netty.channel.socket.nio.NioServerSocketChannel/** * Create a new instance using the given {@link ServerSocketChannel}.
 */
public NioServerSocketChannel(ServerSocketChannel channel) {
    super(null, channel, SelectionKey.OP_ACCEPT);
    config = new NioServerSocketChannelConfig(this, javaChannel().socket());
}

首先,先调用了父类函数,在父类函数中将java原生的serverSocketChannel赋值给了成员变量ch,还将ch设置为非阻塞模式,这么一看,确实和我们写NIO的代码是一样一样的。

代码语言:javascript
复制
AbstractNioChannel.javasuper(parent);this.ch = ch;this.readInterestOp = readInterestOp;
try {
    ch.configureBlocking(false);

紧接着我们看到他又调用了父类的构造函数,并且还构造了一个id对象,一个unsafe对象,一个ChannelPipeline对象。

代码语言:javascript
复制
io.netty.channel.AbstractChannel#AbstractChannelprivate final ChannelId id;private final Unsafe unsafe;
private final DefaultChannelPipeline pipeline;
代码语言:javascript
复制
protected AbstractChannel(Channel parent) {
    this.parent = parent;
    id = newId();
    unsafe = newUnsafe();
    pipeline = newChannelPipeline();
}

至此,NioServerSocketChannel的构造就完成了。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-10-30,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 逆熵架构 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档