【第15篇】netty服务端初始化流程详细分析

1、 Channel与ChannelHandlerContext

  • Channel与ChannelHandlerContext都实现了AttributeMap,可以让用户附加一个或多个用户定义的属性,有时令用户干到疑惑,一个Channel与一个ChannelHandlerContext拥有各种的用户自定义的存储
  • Netty4.1的版本解决了Attr方法的改变,减少ChannelHandlerContext对Map的创建,从而减少了内存的使用(版本改良点)

New and noteworthy in 4.1 请截图

ChannelHandlerContext

ChannelHandlerContext

2、 初始化流程

  • ServerBootstrap类的init方法在初始化流程很重要
  • NioEventLoopGroup继承MutiEventLoopGroup类

服务端初始化的步骤
1、创建ServerBootstrap启动辅助类,通过Builder模式进行参数配置;
2、创建并绑定Reactor线程池EventLoopGroup;
4、设置并绑定服务端Channel通道类型;
5、绑定服务端通道数据处理器责任链Handler;

3、ServerBootstrap初始化

  • ServerBootstrap是netty启动辅助类,通过Builder模式进行参数设置初始化;ServerBootstrap继承AbstracBootstrap类,需要对EventLoopGroup,Channel和ChannelHandler等参数进行配置;
public class ServerBootstrap extends AbstractBootstrap<ServerBootstrap, ServerChannel> {
    private final Map<ChannelOption<?>, Object> childOptions = new LinkedHashMap();
    private final Map<AttributeKey<?>, Object> childAttrs = new LinkedHashMap();
    private final ServerBootstrapConfig config = new ServerBootstrapConfig(this);
    private volatile EventLoopGroup childGroup;
    private volatile ChannelHandler childHandler;
}

image.png
  • EventLoop线程池初始化
    EventLoopGroup初始化是创建创建两个NioEventLoopGroup类型的Reactor线程池bossGroup和workGroup分别用来处理客户端的连接请求和通道IO事件;
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
ServerBootstrap b= new ServerBootstrap();
b.group(boosGroup,workGroup)
  • 通过group()方法设置EventLoop将bossGroup传入到AbstractBootstrap中设置到group属性上,将workGroup设置到ServerBootstrap的childGroup属性上;如果只传入了一个EventLoopGroup则最后传入两个相同的group;

public ServerBootstrap group(EventLoopGroup group) {
    return this.group(group, group);
}

public ServerBootstrap group(EventLoopGroup parentGroup, EventLoopGroup childGroup) {
    super.group(parentGroup);
    if(childGroup == null) {
        throw new NullPointerException("childGroup");
    } else if(this.childGroup != null) {
        throw new IllegalStateException("childGroup set already");
    } else {
        this.childGroup = childGroup;
        return this;
    }
}
  • super.group(parentGroup)方法对AbstractBootstrap的group属性进行设置
public B group(EventLoopGroup group) {
    if(group == null) {
        throw new NullPointerException("group");
    } else if(this.group != null) {
        throw new IllegalStateException("group set already");
    } else {
        this.group = group;
        return this;
    }
}

4、Channel通道初始化

  • Channel初始化主要是指对通道类型进行设置,常见的通道类型主要有NioServerSocktChannel异步非阻塞服务端TCP通道,NioSocketChannel异步非阻塞客户端通道,OioServerSocketChannel同步阻塞服务端通道,OioSocketChannel同步阻塞客户端通道,NioDatagramChannel异步非阻塞UDP通道,OioDatagramChannel同步阻塞UDP通道等;
  • ChannelFactory通道工程类设置,在serverBootstrap初始化过程中通过调用channel()方法进行通道类型设置
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)));
    }
}
  • 根据传入的Channe类型初始化一个ChannelFactory类型的工厂类,工厂类中通过newChannel()方法创建Channel实例

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.newInstance();
    } catch (Throwable var2) {
        throw new ChannelException("Unable to create Channel from class " + this.clazz, var2);
    }
}
  • 通过channelFactory()方法将创建工厂类实例指向AbstractoryBootstrap的channelFactory属性
public B channelFactory(ChannelFactory<? extends C> channelFactory) {
    if(channelFactory == null) {
        throw new NullPointerException("channelFactory");
    } else if(this.channelFactory != null) {
        throw new IllegalStateException("channelFactory set already");
    } else {
        this.channelFactory = channelFactory;
        return this;
    }
}

5、Channel通道实例化

  • 配置好AbstractBootstrap的channelFactory工厂类,Channel的实例化通过ChannelFactory.newChannel()方法实现;具体的newChannel()方法的调用链是:
ServerBootstrap.bind() -> AbstractBootstrap.doBind() -> AbstractBootstrap.initAndRegister() -> ChannelFactory.newChannel();

public T newChannel() {
    try {
        return (Channel)this.clazz.newInstance();
    } catch (Throwable var2) {
        throw new ChannelException("Unable to create Channel from class " + this.clazz, var2);
    }
}
  • 通过clazz.newInstance()方法调用构造器创建NioServerSocketChannel实例
public NioServerSocketChannel() {
    this(newSocket(DEFAULT_SELECTOR_PROVIDER));
}
  • 调用newSocket()方法创建ServerSocketChannel实例,这里的ServerSocketChannel和NIO中的ServerSocketChannel是同一个东西,接下来会调用父类构造器对其进行外部封装和相关参数的配置;
public NioServerSocketChannel(java.nio.channels.ServerSocketChannel channel) {
    super((Channel)null, channel, 16);
    this.config = new NioServerSocketChannel.NioServerSocketChannelConfig(this, this.javaChannel().socket());
}
  • 在 NioServerSocketChannsl 实例化过程中, 所需要做的工作
    调用 NioServerSocketChannel.newSocket(DEFAULT_SELECTOR_PROVIDER) 打开一个新的 Java NIO ServerSocketChannel

AbstractNioChannel 中的属性:

  • SelectableChannel ch 被设置为 Java ServerSocketChannel, 即 NioServerSocketChannel#newSocket 返回的 Java NIO ServerSocketChannel.
  • readInterestOp 被设置为 SelectionKey.OP_ACCEPT
  • SelectableChannel ch 被配置为非阻塞的 ch.configureBlocking(false)=
  • AbstractChannel(Channel parent) 中初始化

AbstractChannel 的属性:

  • AbstractChannel 的属性parent 属性置为 null
  • unsafe 通过newUnsafe() 实例化一个unsafe对象, 它的类型是 AbstractNioMessageChannel#AbstractNioUnsafe 内部类
  • pipeline 是 new DefaultChannelPipeline(this) 新创建的绑定管道实例.

NioServerSocketChannel 中的属性:

ServerSocketChannelConfig config = new NioServerSocketChannelConfig(this, javaChannel().socket())

6、Channel通道注册

  • 在channel通道创建和初始化完毕后,会通过group.register()方法将channel通道注册到EventLoop线程池中;
final ChannelFuture initAndRegister() {
    // 去掉非关键代码
    final Channel channel = channelFactory().newChannel();
    init(channel);
    ChannelFuture regFuture = group().register(channel);
}
  • 通过一系列的注册方法调用:AbstractBootstrap.initAndRegister -> MultithreadEventLoopGroup.register -> SingleThreadEventLoop.register -> AbstractUnsafe.register,最终是通过Unsafe类的register0()方法
private void register0(ChannelPromise promise) {
    boolean firstRegistration = neverRegistered;
    doRegister();
    neverRegistered = false;
    registered = true;
    safeSetSuccess(promise);
    pipeline.fireChannelRegistered();
    // Only fire a channelActive if the channel has never been registered. This prevents firing
    // multiple channel actives if the channel is deregistered and re-registered.
    if (firstRegistration && isActive()) {
        pipeline.fireChannelActive();
    }
}
  • register0()方法调用了doRegister()方法实现通道注册到线程池中(EventLoop线程池会绑定一个selector选择器)
@Override
protected void doRegister() throws Exception {
    // 省略错误处理
    selectionKey = javaChannel().register(eventLoop().selector, 0, this);
}

7 handler处理器的添加过程

  • 我们可以自定义Handler处理器并将其加入到pipeline管道中,进而像插件一样自由组合各种handler完成具体的业务逻辑;添加handler的过程是获取与channel通道绑定的管道pipeline然后将自定义的handler添加进pipeline内部维护的一个双向链表;
bootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
                    @Override
 protected void initChannel(SocketChannel socketChannel) throws Exception {
    socketChannel.pipeline().addLast(new TimeServerHandler());
     }
});
  • Bootstrap.childerHandler方法接收一个 ChannelHandler, 而我们传递的是一个 派生于ChannelInitializer的匿名类,它正好也实现了 ChannelHandler接口,因此将ChannelHandler实例赋值给ServerBootstrap的childHandler属性;
public ServerBootstrap childHandler(ChannelHandler childHandler) {
    if(childHandler == null) {
        throw new NullPointerException("childHandler");
    } else {
        this.childHandler = childHandler;
        return this;
    }
}
  • 在启动服务端绑定端口时候最终通过调用initAndRegister()方法创建Channel实例,并将通过init()方法将系统定义的处理器ServerBootstrapAccptor添加到与channel绑定的pipeline通道中;
@Override
void init(Channel channel) throws Exception {
    ...
    ChannelPipeline p = channel.pipeline();

    final EventLoopGroup currentChildGroup = childGroup;
    final ChannelHandler currentChildHandler = childHandler;
    final Entry<ChannelOption<?>, Object>[] currentChildOptions;
    final Entry<AttributeKey<?>, Object>[] currentChildAttrs;

    p.addLast(new ChannelInitializer<Channel>() {
        @Override
        public void initChannel(Channel ch) throws Exception {
            ChannelPipeline pipeline = ch.pipeline();
            ChannelHandler handler = handler();
            if (handler != null) {
                pipeline.addLast(handler);
            }
            pipeline.addLast(new ServerBootstrapAcceptor(
                    currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs));
        }
    });
}
  • 在ServerBootstrapAcceptor中重写了channelRead()方法,将自定义的handler处理器添加到管道中;
public void channelRead(ChannelHandlerContext ctx, Object msg) {
    final Channel child = (Channel) msg;
    child.pipeline().addLast(childHandler);
    ...
    childGroup.register(child).addListener(...);
}
  • 服务器端的 handler 与 childHandler 的区别与联系:

  • 服务器 NioServerSocketChannel 的 pipeline 中添加的是 handler 与 ServerBootstrapAcceptor.

  • 当有新的客户端连接请求时, ServerBootstrapAcceptor.channelRead 中负责新建此连接的NioSocketChannel并添加 childHandler 到 NioSocketChannel 对应的pipeline中, 并将此channel绑定到workerGroup中的某个eventLoop中;

  • handler是在accept阶段起作用, 它处理客户端的连接请求,ServerBootstrap也能设置handler()方法添加ServerSocketChannel的自定义处理器;

8、 OIO(掌握点)

  • OIO是阻塞编程模型,一个连接过来服务端会启用一个线程专门服务这个连接,而客户端一直等待响应返回数据(有多少个就会创建多少个Thread)
  • OioServerSocketChannel同步阻塞服务端通道,OioSocketChannel同步阻塞客户端通道,NioDatagramChannel异步非阻塞UDP通道,OioDatagramChannel同步阻塞UDP通道等
  • OioServerSocketChannel与NioDatagramChannel类关系图


    OioServerSocketChannel

    NioDatagramChannel

9、 lsof命令

  • lsof -i :8080 查看8080端口进程命令,lsof 代表list open file 打开列表文件的意思

10、总结

Netty服务端的初始化主要是创建初始化辅助类ServerBootstrap,并对辅助类的相关参数进行初始化包括EventLoop线程池,Channle通道类型和ChannleHandler通道处理器等;在调用bind()方法进行端口绑定时,会根据ServerBootsrap中的初始化参数启动服务端,具体的启动流程为:

  • 创建ServerBootstrap启动辅助类实例,并对其Channel,EventLoopGroup,Handler等参数进行配置;

  • 调用bootstrap.bind()方法时触发启动,会根据配置的Channle类型创建Channel实例,比如NioServerSocketChannel等

  • 在实例化Channel时候会初始化Pipeline管道并与AbstractChannel绑定

  • 将channel管道注册到EventLoopGroup线程池中,从线程池中轮询获取一个线程EventLoop并与之绑定;

  • 启动线程,线程执行绑定的selector的select()方法监听注册的channel的状态,并执行定时任务

  • NioServerSocketChannel

推荐学习博客

此文章内容来自服务端初始化和个人学习过程中整理记录笔记的摘要

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容