Netty 学习(五):服务端启动核心流程源码说明

作者: Grey

原文地址:

博客园:Netty 学习(五):服务端启动核心流程源码说明

CSDN:Netty 学习(五):服务端启动核心流程源码说明

说明

本文使用的 Netty 版本是 4.1.82.Final,

        <dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.82.Final</version>
</dependency>

服务端在启动的时候,主要流程有如下几个

  1. 创建服务端的 Channel

  2. 初始化服务端的 Channel

  3. 注册 Selector

  4. 端口绑定

我们可以写一个简单的服务端代码,通过 Debug 的方式查看这几个关键流程的核心代码。

package source;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel; /**
* 代码阅读
*
* @author <a href="mailto:410486047@qq.com">Grey</a>
* @date 2022/9/12
* @since
*/
public final class SimpleServer {
public static void main(String[] args) throws InterruptedException {
// EventLoopGroup: 服务端的线程模型外观类。这个线程要做的事情
// 就是不停地检测IO事件,处理IO事件,执行任务。
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
// 服务端的一个启动辅助类。通过给它设置一系列参数来绑定端口启动服务。
ServerBootstrap b = new ServerBootstrap();
b
// 设置服务端的线程模型。
// bossGroup 负责不断接收新的连接,将新的连接交给 workerGroup 来处理。
.group(bossGroup, workerGroup)
// 设置服务端的 IO 类型是 NIO。Netty 通过指定 Channel 的类型来指定 IO 类型。
.channel(NioServerSocketChannel.class)
// 服务端启动过程中,需要经过哪些流程。
.handler(new ChannelInboundHandlerAdapter() {
@Override
public void channelActive(ChannelHandlerContext ctx) {
System.out.println("channelActive");
} @Override
public void channelRegistered(ChannelHandlerContext ctx) {
System.out.println("channelRegistered");
} @Override
public void handlerAdded(ChannelHandlerContext ctx) {
System.out.println("handlerAdded");
}
})
// 用于设置一系列 Handler 来处理每个连接的数据
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) { }
});
// 绑定端口同步等待。等服务端启动完毕,才会进入下一行代码
ChannelFuture f = b.bind(8888).sync();
// 等待服务端关闭端口绑定,这里的作用是让程序不会退出
f.channel().closeFuture().sync();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}

通过

ChannelFuture f = b.bind(8888).sync();

bind方法,进入源码进行查看。

首先,进入的是AbstractBootstrap中,调用的最关键的方法是如下两个:

……
private ChannelFuture doBind(final SocketAddress localAddress) {
……
final ChannelFuture regFuture = initAndRegister();
……
doBind0(regFuture, channel, localAddress, promise);
……
}
……

进入initAndResgister()方法中

……
final ChannelFuture initAndRegister() {
……
// channel 的新建
channel = channelFactory.newChannel();
// channel 的初始化
init(channel);
……
}
……

这里完成了 Channel 的新建和初始化,Debug 进去,发现channelFactory.newChannel()实际上是调用了ReflectiveChannelFactorynewChannel方法,

public class ReflectiveChannelFactory<T extends Channel> implements ChannelFactory<T> {
……
private final Constructor<? extends T> constructor; public ReflectiveChannelFactory(Class<? extends T> clazz) {
……
this.constructor = clazz.getConstructor();
……
} @Override
public T newChannel() {
……
return constructor.newInstance();
……
}
……
}

这里调用了反射方法,其实就是将服务端代码中的这一行.channel(NioServerSocketChannel.class)中的NioServerSocketChannel.class传入进行对象创建,创建一个NioServerSocketChannel实例。

在创建NioServerSocketChannel的时候,调用了NioServerSocketChannel的构造方法,构造方法的主要逻辑如下

……
public NioServerSocketChannel(SelectorProvider provider, InternetProtocolFamily family) {
this(newChannel(provider, family));
}
public NioServerSocketChannel(ServerSocketChannel channel) {
super(null, channel, SelectionKey.OP_ACCEPT);
config = new NioServerSocketChannelConfig(this, javaChannel().socket());
}
private static ServerSocketChannel newChannel(SelectorProvider provider, InternetProtocolFamily family) {
……
ServerSocketChannel channel =
SelectorProviderUtil.newChannel(OPEN_SERVER_SOCKET_CHANNEL_WITH_FAMILY, provider, family);
return channel == null ? provider.openServerSocketChannel() : channel;
……
}
……

其中provider.openServerSocketChannel()就是调用底层 JDK 的 API,获取了 JDK 底层的java.nio.channels.ServerSocketChannel

通过super(null, channel, SelectionKey.OP_ACCEPT);一路跟踪进去,进入AbstractNioChannel中,

   protected AbstractNioChannel(Channel parent, SelectableChannel ch, int readInterestOp) {
super(parent);
……
ch.configureBlocking(false);
……
}

关键代码是ch.configureBlocking(false),设置 I/O 模型为非阻塞模式。

通过super(parent)跟踪上去,

    protected AbstractChannel(Channel parent) {
this.parent = parent;
id = newId();
unsafe = newUnsafe();
pipeline = newChannelPipeline();
}

其中 id 是 Netty 中每条 Channel 的唯一标识。

以上就是服务端 Channel 的创建过程。

接下来是服务端 Channel 的初始化过程,回到AbstractBootstrap.initAndResgister()方法

……
final ChannelFuture initAndRegister() {
……
// channel 的新建
channel = channelFactory.newChannel();
// channel 的初始化
init(channel);
……
}
……

其中的init(channel)方法就是服务端的 Channel 的初始化过程,Debug 进入,发现是调用了ServerBootstrap.init(channel)方法,


@Override
void init(Channel channel) {
……
// 设置一些 Channel 的属性和配置信息
……
p.addLast(new ChannelInitializer<Channel>() {
@Override
public void initChannel(final Channel ch) {
final ChannelPipeline pipeline = ch.pipeline();
ChannelHandler handler = config.handler();
if (handler != null) {
pipeline.addLast(handler);
} ch.eventLoop().execute(new Runnable() {
@Override
public void run() {
pipeline.addLast(new ServerBootstrapAcceptor(
ch, currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs));
}
});
}
});
}

其核心代码如上,主要用于定义服务端启动过程中需要执行哪些逻辑。主要分为两块:

  1. 一块是添加用户自定义的处理逻辑到服务端启动流程。

  2. 另一块是添加一个特殊的处理逻辑,ServerBootstrapAcceptor 是一个接入器,接受新请求,把新的请求传递给某个事件循环器。

以上就是服务端的 Channel 的初始化过程。接下来是服务端 Channel 的注册 Selector 的过程。

    @Override
protected void doRegister() throws Exception {
boolean selected = false;
for (;;) {
try {
selectionKey = javaChannel().register(eventLoop().unwrappedSelector(), 0, this);
return;
} catch (CancelledKeyException e) {
if (!selected) {
// Force the Selector to select now as the "canceled" SelectionKey may still be
// cached and not removed because no Select.select(..) operation was called yet.
eventLoop().selectNow();
selected = true;
} else {
// We forced a select operation on the selector before but the SelectionKey is still cached
// for whatever reason. JDK bug ?
throw e;
}
}
}
}

在这个步骤中,我们可以看到关于 JDK 底层的操作

selectionKey = javaChannel().register(eventLoop().unwrappedSelector(), 0, this);

首先拿到在前面过程中创建的 JDK 底层的 Channel,然后调用 JDK 的 register() 方法,将 this 也即 NioServerSocketChannel 对象当作 attachment 绑定到 JDK 的 Selector 上,这样后续从 Selector 拿到对应的事件之后,就可以把 Netty 领域的 Channel 拿出来。

接下来是服务端绑定端口的逻辑,见AbstractBootstrap中的doBind0方法

    private static void doBind0(
final ChannelFuture regFuture, final Channel channel,
final SocketAddress localAddress, final ChannelPromise promise) { // This method is invoked before channelRegistered() is triggered. Give user handlers a chance to set up
// the pipeline in its channelRegistered() implementation.
channel.eventLoop().execute(new Runnable() {
@Override
public void run() {
if (regFuture.isSuccess()) {
channel.bind(localAddress, promise).addListener(ChannelFutureListener.CLOSE_ON_FAILURE);
} else {
promise.setFailure(regFuture.cause());
}
}
});
}

图例

本文所有图例见:processon: Netty学习笔记

代码

hello-netty

更多内容见:Netty专栏

参考资料

跟闪电侠学 Netty:Netty 即时聊天实战与底层原理

深度解析Netty源码

Netty 学习(五):服务端启动核心流程源码说明的更多相关文章

  1. [Android]从Launcher开始启动App流程源码分析

    以下内容为原创,欢迎转载,转载请注明 来自天天博客:http://www.cnblogs.com/tiantianbyconan/p/5017056.html 从Launcher开始启动App流程源码 ...

  2. Netty服务端启动过程相关源码分析

    1.Netty 是怎么创建服务端Channel的呢? 我们在使用ServerBootstrap.bind(端口)方法时,最终调用其父类AbstractBootstrap中的doBind方法,相关源码如 ...

  3. Spring IOC容器核心流程源码分析

    简单介绍 Spring IOC的核心方法就在于refresh方法,这个方法里面完成了Spring的初始化.准备bean.实例化bean和扩展功能的实现. 这个方法的作用是什么? 它是如何完成这些功能的 ...

  4. Netty源码解析 -- 服务端启动过程

    本文通过阅读Netty源码,解析Netty服务端启动过程. 源码分析基于Netty 4.1 Netty是一个高性能的网络通信框架,支持NIO,OIO等多种IO模式.通常,我们都是使用NIO模式,该系列 ...

  5. (二)Netty源码学习笔记之服务端启动

    尊重原创,转载注明出处,原文地址:http://www.cnblogs.com/cishengchongyan/p/6129971.html  本文将不会对netty中每个点分类讲解,而是一个服务端启 ...

  6. Netty 学习(一):服务端启动 & 客户端启动

    Netty 学习(一):服务端启动 & 客户端启动 作者: Grey 原文地址: 博客园:Netty 学习(一):服务端启动 & 客户端启动 CSDN:Netty 学习(一):服务端启 ...

  7. Netty之旅三:Netty服务端启动源码分析,一梭子带走!

    Netty服务端启动流程源码分析 前记 哈喽,自从上篇<Netty之旅二:口口相传的高性能Netty到底是什么?>后,迟迟两周才开启今天的Netty源码系列.源码分析的第一篇文章,下一篇我 ...

  8. Netty 4源码解析:服务端启动

    Netty 4源码解析:服务端启动 1.基础知识 1.1 Netty 4示例 因为Netty 5还处于测试版,所以选择了目前比较稳定的Netty 4作为学习对象.而且5.0的变化也不像4.0这么大,好 ...

  9. 原理剖析-Netty之服务端启动工作原理分析(下)

    一.大致介绍 1.由于篇幅过长难以发布,所以本章节接着上一节来的,上一章节为[原理剖析(第 010 篇)Netty之服务端启动工作原理分析(上)]: 2.那么本章节就继续分析Netty的服务端启动,分 ...

随机推荐

  1. height,min-height,max-heigth的作用机制问答

    1.min-height和height同时存在,子元素高度100%,以哪个高度为准? 答:min-height 2.height存在,子元素高度100%,子元素内容高度大于100%,子元素高度为多少? ...

  2. rust里的内存对齐

    内存对齐主要遵循下面三个原则: 结构体变量的起始地址能够被其最宽的成员大小整除 结构体每个成员相对于起始地址的偏移能够被其自身大小整除,如果不能则在前一个成员后面补充字节 结构体总体大小能够被最宽的成 ...

  3. 在阿里云Centos7.6上面配置Mysql主从数据库(master/slave),实现读写分离

    原文转载自「刘悦的技术博客」https://v3u.cn/a_id_85 在之前的一篇文章中,阐述了如何在高并发高负载的场景下使用nginx做后台服务的负载均衡:在阿里云Centos上配置nginx+ ...

  4. 万字长文:从计算机本源深入探寻volatile和Java内存模型

    万字长文:从计算机本源深入探寻volatile和Java内存模型 前言 在本篇文章当中,主要给大家深入介绍Volatile关键字和Java内存模型.在文章当中首先先介绍volatile的作用和Java ...

  5. FutureTask源码深度剖析

    FutureTask源码深度剖析 前言 在前面的文章自己动手写FutureTask当中我们已经仔细分析了FutureTask给我们提供的功能,并且深入分析了我们该如何实现它的功能,并且给出了使用Ree ...

  6. 前端监控系列1| 字节的前端监控SDK是怎样设计的

    作者:彭莉,火山引擎 APM 研发工程师,2020年加入字节,负责前端监控 SDK 的开发维护.平台数据消费的探索和落地. 摘要 字节内部应用环境多样( Web 应用.小程序.Electron 应用. ...

  7. Linux 06 用户组管理

    参考源 https://www.bilibili.com/video/BV187411y7hF?spm_id_from=333.999.0.0 版本 本文章基于 CentOS 7.6 概述 每个用户都 ...

  8. Windows 电脑杀毒简单有效的方式

    Windows 电脑杀毒通常会选择杀毒软件,这样太笨重,且容易占内存和存在流氓软件侵入. 推荐使用 Windows 自带的恶意软件删除工具 按住 Win + R 键,弹出运行窗口,输入 mrt. 系统 ...

  9. 记录Java类型推断关键字var的一种特殊用法

    关于Java的var类型推断,有一种特殊用法,如下: 没有var类型之前声明一个匿名类对象,并调用它的方法. Object obj = new Object() { public void test( ...

  10. 一个监控工具monit

    最近看了一个问题,monit监控在读取配置文件之后,访问的文件是残留在容器中的,导致认为服务异常,其实一开始容器启动,并没有对应的服务. [root@10-36-235-119.fin-wealth- ...