千家信息网

netty源码分析之服务端启动

发表于:2025-02-23 作者:千家信息网编辑
千家信息网最后更新 2025年02月23日,ServerBootstrap与Bootstrap分别是netty中服务端与客户端的引导类,主要负责服务端与客户端初始化、配置及启动引导等工作,接下来我们就通过netty源码中的示例对ServerBo
千家信息网最后更新 2025年02月23日netty源码分析之服务端启动

ServerBootstrap与Bootstrap分别是netty中服务端与客户端的引导类,主要负责服务端与客户端初始化、配置及启动引导等工作,接下来我们就通过netty源码中的示例对ServerBootstrap与Bootstrap的源码进行一个简单的分析。首先我们知道这两个类都继承自AbstractBootstrap类

接下来我们就通过netty源码中ServerBootstrap的实例入手对其进行一个简单的分析。

// Configure the server.
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();
final EchoServerHandler serverHandler = new EchoServerHandler();
try {
//初始化一个服务端引导类
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup) //设置线程组
.channel(NioServerSocketChannel.class)//设置ServerSocketChannel的IO模型 分为epoll与Nio
.option(ChannelOption.SO_BACKLOG, 100)//设置option参数,保存成一个LinkedHashMap, Object>()
.handler(new LoggingHandler(LogLevel.INFO))//这个hanlder 只专属于 ServerSocketChannel 而不是 SocketChannel。
.childHandler(new ChannelInitializer() { //这个handler 将会在每个客户端连接的时候调用。供 SocketChannel 使用。@Override
br/>@Override

ChannelPipeline p = ch.pipeline();
if (sslCtx != null) {
p.addLast(sslCtx.newHandler(ch.alloc()));
}
//p.addLast(new LoggingHandler(LogLevel.INFO));
p.addLast(serverHandler);
}
});

        // Start the server. 启动服务        ChannelFuture f = b.bind(PORT).sync();        // Wait until the server socket is closed.        f.channel().closeFuture().sync();    } finally {        // Shut down all event loops to terminate all threads.        bossGroup.shutdownGracefully();        workerGroup.shutdownGracefully();    }            接下来我们主要从服务端的socket在哪里初始化与哪里accept连接这两个问题入手对netty服务端启动的流程进行分析;

我们首先要知道,netty服务的启动其实可以分为以下四步:

创建服务端Channel
初始化服务端Channel
注册Selector
端口绑定
一、创建服务端Channel

1、服务端Channel的创建,主要为以下流程

我们通过跟踪代码能够看到

final ChannelFuture regFuture = initAndRegister();// 初始化并创建 NioServerSocketChannel

我们在initAndRegister()中可以看到channel的初始化。

channel = channelFactory.newChannel(); // 通过 反射工厂创建一个 NioServerSocketChannel

我进一步看newChannel()中的源码,在ReflectiveChannelFactory这个反射工厂中,通过clazz这个类的反射创建了一个服务端的channel。

@Override
public T newChannel() {
try {
return clazz.getConstructor().newInstance();//反射创建
} catch (Throwable t) {
throw new ChannelException("Unable to create Channel from class " + clazz, t);
}
}

    既然通过反射,我们就要知道clazz类是什么,那么我我们来看下channelFactory这个工厂类是在哪里初始化的,初始化的时候我们传入了哪个channel。

这里我们需要看下demo实例中初始化ServerBootstrap时.channel(NioServerSocketChannel.class)这里的具体实现,我们看下源码

public B channel(Class channelClass) {
if (channelClass == null) {
throw new NullPointerException("channelClass");
}
return channelFactory(new ReflectiveChannelFactory(channelClass));
}

    通过上面的代码我可以直观的看出正是在这里我们通过NioServerSocketChannel这个类构造了一个反射工厂。

那么到这里就很清楚了,我们创建的Channel就是一个NioServerSocketChannel,那么具体的创建我们就需要看下这个类的构造函数。首先我们看下一个NioServerSocketChannel创建的具体流程

首先是newsocket(),我们先看下具体的代码,在NioServerSocketChannel的构造函数中我们创建了一个jdk原生的ServerSocketChannel

/**

  • Create a new instance
    */
    public NioServerSocketChannel() {
    this(newSocket(DEFAULT_SELECTOR_PROVIDER));//传入默认的SelectorProvider
    }
private static ServerSocketChannel newSocket(SelectorProvider provider) {    try {        /**         *  Use the {@link SelectorProvider} to open {@link SocketChannel} and so remove condition in         *  {@link SelectorProvider#provider()} which is called by each ServerSocketChannel.open() otherwise.         *         *  See #2308.         */        return provider.openServerSocketChannel();//可以看到创建的是jdk底层的ServerSocketChannel     } catch (IOException e) {        throw new ChannelException(                "Failed to open a server socket.", e);    }}    第二步是通过NioServerSocketChannelConfig配置服务端Channel的构造函数,在代码中我们可以看到我们把NioServerSocketChannel这个类传入到了NioServerSocketChannelConfig的构造函数中进行配置

/**

  • Create a new instance using the given {@link ServerSocketChannel}.
    */
    public NioServerSocketChannel(ServerSocketChannel channel) {
    super(null, channel, SelectionKey.OP_ACCEPT);//调用父类构造函数,传入创建的channel
    config = new NioServerSocketChannelConfig(this, javaChannel().socket());
    }
    第三步在父类AbstractNioChannel的构造函数中把创建服务端的Channel设置为非阻塞模式
  /**
  • Create a new instance
  • @param parent the parent {@link Channel} by which this instance was created. May be {@code null}
  • @param ch the underlying {@link SelectableChannel} on which it operates
  • @param readInterestOp the ops to set to receive data from the {@link SelectableChannel}
    */
    protected AbstractNioChannel(Channel parent, SelectableChannel ch, int readInterestOp) {
    super(parent);
    this.ch = ch;//这个ch就是传入的通过jdk创建的Channel
    this.readInterestOp = readInterestOp;
    try {
    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);    }}    第四步调用AbstractChannel这个抽象类的构造函数设置Channel的id(每个Channel都有一个id,唯一标识),unsafe(tcp相关底层操作),pipeline(逻辑链)等,而不管是服务的Channel还是客户端的Channel都继承自这个抽象类,他们也都会有上述相应的属性。我们看下AbstractChannel的构造函数
  /**
  • Creates a new instance.
  • @param parent
  • the parent of this channel. {@code null} if there's no parent.
    */
    protected AbstractChannel(Channel parent) {
    this.parent = parent;
    id = newId();//创建Channel唯一标识
    unsafe = newUnsafe();//netty封装的TCP 相关操作类
    pipeline = newChannelPipeline();//逻辑链
    }
     2、初始化服务端创建的Channel
   init(channel);// 初始化这个 NioServerSocketChannel

我们首先列举下init(channel)中具体都做了哪了些功能:

设置ChannelOptions、ChannelAttrs ,配置服务端Channel的相关属性;
设置ChildOptions、ChildAttrs,配置每个新连接的Channel的相关属性;
Config handler,配置服务端pipeline;
add ServerBootstrapAcceptor,添加连接器,对accpet接受到的新连接进行处理,添加一个nio线程;
那么接下来我们通过代码,对每一步设置进行一下分析:

首先是在SeverBootstrap的init()方法中对ChannelOptions、ChannelAttrs 的配置的关键代码

final Map, Object> options = options0();//拿到你设置的option
synchronized (options) {
setChannelOptions(channel, options, logger);//设置NioServerSocketChannel相应的TCP参数,其实这一步就是把options设置到channel的config中
}

    final Map, Object> attrs = attrs0();    synchronized (attrs) {        for (Entry, Object> e: attrs.entrySet()) {            @SuppressWarnings("unchecked")            AttributeKey key = (AttributeKey) e.getKey();            channel.attr(key).set(e.getValue());        }    }            然后是对ChildOptions、ChildAttrs配置的关键代码
          //可以看到两个都是局部变量,会在下面设置pipeline时用到

final Entry, Object>[] currentChildOptions;
final Entry, Object>[] currentChildAttrs;
synchronized (childOptions) {
currentChildOptions = childOptions.entrySet().toArray(newOptionArray(0));
}
synchronized (childAttrs) {
currentChildAttrs = childAttrs.entrySet().toArray(newAttrArray(0));
}

            第三步对服务端Channel的handler进行配置

p.addLast(new ChannelInitializer() {@Override
br/>@Override

final ChannelPipeline pipeline = ch.pipeline();
ChannelHandler handler = config.handler();//拿到我们自定义的hanler
if (handler != null) {
pipeline.addLast(handler);
}

            ch.eventLoop().execute(new Runnable() {                @Override                public void run() {                    pipeline.addLast(new ServerBootstrapAcceptor(                            ch, currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs));                }            });        }    });            第四步添加ServerBootstrapAcceptor连接器,这个是netty向服务端Channel自定义添加的一个handler,用来处理新连接的添加与属性配置,我们来看下关键代码
          ch.eventLoop().execute(new Runnable() {

@Override
public void run() {
//在这里会把我们自定义的ChildGroup、ChildHandler、ChildOptions、ChildAttrs相关配置传入到ServerBootstrapAcceptor构造函数中,并绑定到新的连接上
pipeline.addLast(new ServerBootstrapAcceptor(
ch, currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs));
}
});

三、注册Selector

一个服务端的Channel创建完毕后,下一步就是要把它注册到一个事件轮询器Selector上,在initAndRegister()中我们把上面初始化的Channel进行注册

ChannelFuture regFuture = config().group().register(channel);//注册我们已经初始化过的Channel
而这个register具体实现是在AbstractChannel中的AbstractUnsafe抽象类中的
/**

  • 1、先是一系列的判断。
  • 2、判断当前线程是否是给定的 eventLoop 线程。注意:这点很重要,Netty 线程模型的高性能取决于对于当前执行的Thread 的身份的确定。如果不在当前线程,那么就需要很多同步措施(比如加锁),上下文切换等耗费性能的操作。
  • 3、异步(因为我们这里直到现在还是 main 线程在执行,不属于当前线程)的执行 register0 方法。*/
    @Override
    br/>*/
    @Override

    if (eventLoop == null) {
    throw new NullPointerException("eventLoop");
    }
    if (isRegistered()) {
    promise.setFailure(new IllegalStateException("registered to an event loop already"));
    return;
    }
    if (!isCompatible(eventLoop)) {
    promise.setFailure(
    new IllegalStateException("incompatible event loop type: " + eventLoop.getClass().getName()));
    return;
    }
        AbstractChannel.this.eventLoop = eventLoop;//绑定线程        if (eventLoop.inEventLoop()) {            register0(promise);//实际的注册过程        } else {            try {                eventLoop.execute(new Runnable() {                    @Override                    public void run() {                        register0(promise);                    }                });            } catch (Throwable t) {                logger.warn(                        "Force-closing a channel whose registration task was not accepted by an event loop: {}",                        AbstractChannel.this, t);                closeForcibly();                closeFuture.setClosed();                safeSetFailure(promise, t);            }        }    }

首先我们对整个注册的流程做一个梳理

接下来我们进入register0()方法看下注册过程的具体实现

private void register0(ChannelPromise promise) {
try {
// check if the channel is still open as it could be closed in the mean time when the register
// call was outside of the eventLoop
if (!promise.setUncancellable() || !ensureOpen(promise)) {
return;
}
boolean firstRegistration = neverRegistered;
doRegister();//jdk channel的底层注册
neverRegistered = false;
registered = true;

            // 触发绑定的handler事件            // Ensure we call handlerAdded(...) before we actually notify the promise. This is needed as the            // user may already fire events through the pipeline in the ChannelFutureListener.            pipeline.invokeHandlerAddedIfNeeded();            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 (isActive()) {                if (firstRegistration) {                    pipeline.fireChannelActive();                } else if (config().isAutoRead()) {                    // This channel was registered before and autoRead() is set. This means we need to begin read                    // again so that we process inbound data.                    //                    // See https://github.com/netty/netty/issues/4805                    beginRead();                }            }        } catch (Throwable t) {            // Close the channel directly to avoid FD leak.            closeForcibly();            closeFuture.setClosed();            safeSetFailure(promise, t);        }    }AbstractNioChannel中doRegister()的具体实现就是把jdk底层的channel绑定到eventLoop的selecor上

@Override
protected void doRegister() throws Exception {
boolean selected = false;
for (;;) {
try {
//把channel注册到eventLoop上的selector上
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;
}
}
}
}

    到这里netty就把服务端的channel注册到了指定的selector上,下面就是服务端口的邦迪

三、端口绑定

首先我们梳理下netty中服务端口绑定的流程

我们来看下AbstarctUnsafe中bind()方法的具体实现

@Override
public final void bind(final SocketAddress localAddress, final ChannelPromise promise) {
assertEventLoop();

        if (!promise.setUncancellable() || !ensureOpen(promise)) {            return;        }        // See: https://github.com/netty/netty/issues/576        if (Boolean.TRUE.equals(config().getOption(ChannelOption.SO_BROADCAST)) &&            localAddress instanceof InetSocketAddress &&            !((InetSocketAddress) localAddress).getAddress().isAnyLocalAddress() &&            !PlatformDependent.isWindows() && !PlatformDependent.maybeSuperUser()) {            // Warn a user about the fact that a non-root user can't receive a            // broadcast packet on *nix if the socket is bound on non-wildcard address.            logger.warn(                    "A non-root user can't receive a broadcast packet if the socket " +                    "is not bound to a wildcard address; binding to a non-wildcard " +                    "address (" + localAddress + ") anyway as requested.");        }        boolean wasActive = isActive();//判断绑定是否完成        try {            doBind(localAddress);//底层jdk绑定端口        } catch (Throwable t) {            safeSetFailure(promise, t);            closeIfClosed();            return;        }        if (!wasActive && isActive()) {            invokeLater(new Runnable() {                @Override                public void run() {                    pipeline.fireChannelActive();//触发ChannelActive事件                }            });        }        safeSetSuccess(promise);    }            在doBind(localAddress)中netty实现了jdk底层端口的绑定
          @Override

protected void doBind(SocketAddress localAddress) throws Exception {
if (PlatformDependent.javaVersion() >= 7) {
javaChannel().bind(localAddress, config.getBacklog());
} else {
javaChannel().socket().bind(localAddress, config.getBacklog());
}
}

在 pipeline.fireChannelActive()中会触发pipeline中的channelActive()方法

@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
ctx.fireChannelActive();

        readIfIsAutoRead();    }

在channelActive中首先会把ChannelActive事件往下传播,然后调用readIfIsAutoRead()方法出触发channel的read事件,而它最终调用AbstractNioChannel中的doBeginRead()方法

@Override
protected void doBeginRead() throws Exception {
// Channel.read() or ChannelHandlerContext.read() was called
final SelectionKey selectionKey = this.selectionKey;
if (!selectionKey.isValid()) {
return;
}

    readPending = true;    final int interestOps = selectionKey.interestOps();    if ((interestOps & readInterestOp) == 0) {        selectionKey.interestOps(interestOps | readInterestOp);//readInterestOp为  SelectionKey.OP_ACCEPT    }}

在doBeginRead()方法,netty会把accept事件注册到Selector上。

到此我们对netty服务端的启动流程有了一个大致的了解,整体可以概括为下面四步:

1、channelFactory.newChannel(),其实就是创建jdk底层channel,并初始化id、piepline等属性;

2、init(channel),添加option、attr等属性,并添加ServerBootstrapAcceptor连接器;

3、config().group().register(channel),把jdk底层的channel注册到eventLoop上的selector上;

4、doBind0(regFuture, channel, localAddress, promise),完成服务端端口的监听,并把accept事件注册到selector上;

以上就是对netty服务端启动流程进行的一个简单分析,有很多细节没有关注与深入,其中如有不足与不正确的地方还望指出与海涵。

服务 配置 函数 线程 代码 就是 底层 方法 端的 事件 流程 属性 端口 反射 源码 分析 接下来 客户 工厂 两个 数据库的安全要保护哪些东西 数据库安全各自的含义是什么 生产安全数据库录入 数据库的安全性及管理 数据库安全策略包含哪些 海淀数据库安全审计系统 建立农村房屋安全信息数据库 易用的数据库客户端支持安全管理 连接数据库失败ssl安全错误 数据库的锁怎样保障安全 广州服务器维护 无线网络安全通知 北京华信傲天网络技术产品 服务器riser提升板 网络安全公司的商业计划书 北京管理软件开发如何收费 网络安全法适用港澳台吗 计算机软硬件服务器及网络技术 数据库sqlserve下载 以网络安全为主题的纪录表 dell服务器绿红灯闪 数据库文件的扩展名为 建立人才数据库解决招工难题 数据库如何加密解密 网站服务器返回错误8104 三星服务器内存检测不出来 益阳正规软件开发中介 网络安全宣传开头 系统某个功能连接不上数据库 惊天动地手游韩服连接不上服务器 邮件服务器如何锁定任天堂 北京存储服务器机箱生产虚拟主机 两数据库同步 企业软件开发服务介绍 书香之家是什么类的数据库 活字格平台大型数据库 刚开服就服务器炸了的游戏有哪些 数据库文件的扩展名为 泰安联想服务器代理哪家服务好 郴州学计算机软件开发工资
0