netty 源码分析之服务端启动

本文详细解析了Netty服务端的启动流程,包括从配置文件读取端口、设置服务线程组、初始化通道处理器、绑定端口直至监听状态的全过程。深入探讨了NioEventLoopGroup的作用,ServerBootstrap的配置,以及Channel、Pipeline和Handler的联动机制。

 

IDE

服务端netty启动代码

 /**
     * 开启及服务线程
     */
    public void start() {
        // 从配置文件中(application.yml)获取服务端监听端口号
        int port = nettyConfig.getPort();
        serverBootstrap.group(boss, work)
                .channel(NioServerSocketChannel.class)
                .option(ChannelOption.SO_BACKLOG, 100)
                .handler(new LoggingHandler(LogLevel.INFO));
        try {
            //设置事件处理
            serverBootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
                @Override
                protected void initChannel(SocketChannel ch) throws Exception {
                    ChannelPipeline pipeline = ch.pipeline();
                    pipeline.addLast("ping", new IdleStateHandler(60, 20, 600, TimeUnit.SECONDS));
                    pipeline.addLast(new StringEncoder(Charset.forName("UTF-8")));
                    pipeline.addLast(new StringDecoder(Charset.forName("UTF-8")));
                    pipeline.addLast(channelHandlerAdapter);
                }
            });
            LOGGER.info("netty服务器在[{}]端口启动监听", port);
            ChannelFuture f = serverBootstrap.bind(port).sync();
            f.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            LOGGER.info("[出现异常] 释放资源");
            boss.shutdownGracefully();
            work.shutdownGracefully();
        }


        DatagramChannel channel = new NioDatagramChannel();

    }
serverBootstrap.group(boss, work)中的boss ,work 都是NioEventLoopGroup对象,依据官方的文档只要设置就好 后面解释
 /**
     * 创建bootstrap
     */
    ServerBootstrap serverBootstrap = new ServerBootstrap();
    /**
     * BOSS
     */
    EventLoopGroup boss = new NioEventLoopGroup();
    /**
     * Worker
     */
    EventLoopGroup work = new NioEventLoopGroup();
    /**
     * 通道适配器
     */
    @Resource
    private ServerChannelHandlerAdapter channelHandlerAdapter;
    /**
     * NETT服务器配置类
     */
    @Resource
    private NettyConfig nettyConfig;
ServerChannelHandlerAdapter 
是自定义的用户处理消息类 继承于ChannelHandlerAdapter 用户可以从这个adapter的方法做处理
channelActive:连接成功 channelRead是获取管道信息...

这里贴上自己处理的adapter 增加了群发功能

package com.ruoyi.framework.netty;

import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelPromise;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;
import io.netty.util.CharsetUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;

@Component
@ChannelHandler.Sharable
public class ServerChannelHandlerAdapter extends ChannelHandlerAdapter {

    /**
     * 日志处理
     */
    private Logger logger = LoggerFactory.getLogger(ServerChannelHandlerAdapter.class);
    /**
     * 注入请求分排器
     */
    @Resource
    private RequestDispatcher dispatcher;


    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        super.channelActive(ctx);
        logger.info("channelActive");
        ctx.writeAndFlush(Unpooled.copiedBuffer("Heartbeat", CharsetUtil.UTF_8));
        String uuid = ctx.channel().id().asLongText();
        logger.info("uuid" + uuid);
        GroupChannelMap.getInstance().addUUIDChannel(uuid, ctx.channel());
        logger.info("uuid  hashCode" + GroupChannelMap.getInstance().getChannelMap().hashCode());
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        cause.printStackTrace();
        ctx.close();
        GroupChannelMap.getInstance().removeUUIDChannel(ctx.channel().id().asLongText());
    }

    @Override
    public void disconnect(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception {
        super.disconnect(ctx, promise);
        logger.info("disconnect");
        GroupChannelMap.getInstance().removeUUIDChannel(ctx.channel().id().asLongText());
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        logger.info("channelRead 收到数据:" + msg);
      /*  MethodInvokeMeta invokeMeta = (MethodInvokeMeta) msg;
        // 屏蔽toString()方法
        if (invokeMeta.getMethodName().endsWith("toString()")
                && !"class java.lang.String".equals(invokeMeta.getReturnType().toString()))
            logger.info("客户端传入参数 :{},返回值:{}",
                    invokeMeta.getArgs(), invokeMeta.getReturnType());
        dispatcher.dispatcher(ctx, invokeMeta);*/
    }

    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        super.userEventTriggered(ctx, evt);
        if (evt instanceof IdleStateEvent) {
            IdleStateEvent event = (IdleStateEvent) evt;
            if (event.state() == IdleState.WRITER_IDLE) {
                //时间内没有写
                ctx.writeAndFlush(Unpooled.copiedBuffer("Heartbeat", CharsetUtil.UTF_8));
            } else if (event.state() == IdleState.READER_IDLE) {    //时间内没有读
                logger.info("READER_IDLE"+ctx.channel().id().asLongText());
                GroupChannelMap.getInstance().removeUUIDChannel(ctx.channel().id().asLongText());
            } else if (event.state() == IdleState.ALL_IDLE) {       //时间内没有写和读
                logger.info("ALL_IDLE");
                GroupChannelMap.getInstance().removeUUIDChannel(ctx.channel().id().asLongText());
            }
        }
    }
}

源码跟进

serverBootstrap.bind(port).sync()的bind方法可进入到 
AbstractBootstrap
  public ChannelFuture bind(int inetPort) {
        return this.bind(new InetSocketAddress(inetPort));
    }
    public ChannelFuture bind(SocketAddress localAddress) {
        this.validate();
        if (localAddress == null) {
            throw new NullPointerException("localAddress");
        } else {
            return this.doBind(localAddress);
        }
    }

此处调用方法validate 起到验证作用,校验bind之前的参数是否设置有误

   public B validate() {
        if (this.group == null) {
            throw new IllegalStateException("group not set");
        } else if (this.channelFactory == null) {
            throw new IllegalStateException("channel or channelFactory not set");
        } else {
            return this;
        }
    }

之后我们再进入dobind 方法 有initAndRegister()和doBind0() 进入initAndRegister 会发现 在这里创建了关键的管道channel 并随后调用init 初始化 最后再注册(group注册到channel中 后面解释)

    final ChannelFuture initAndRegister() {
        Channel channel = this.channelFactory().newChannel();

        try {
            this.init(channel);
        } catch (Throwable var3) {
            channel.unsafe().closeForcibly();
            return (new DefaultChannelPromise(channel, GlobalEventExecutor.INSTANCE)).setFailure(var3);
        }

        ChannelFuture regFuture = this.group().register(channel);
        if (regFuture.cause() != null) {
            if (channel.isRegistered()) {
                channel.close();
            } else {
                channel.unsafe().closeForcibly();
            }
        }

        return regFuture;
    }

下面看看Channel是如何被创建的,依据方法名可知是通过工厂类创建的,再该类发现 channelFactory赋值的时候是在  AbstractBootstrap

的构造函数中或 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)));
        }
    }

    /** @deprecated */
    @Deprecated
    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;
        }
    }

进入工厂类 发现是通过反射创建的Channel,而这个clazz 正是我们刚开始调用的 NioServerSocketChannel.class 。结果一圈发现Channel早已设置好,就等bind方法一触发。

    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);
        }
    }

NioServerSocketChannel的创建过程

继续追踪channel是如何创建的 附带的又创建了什么 ?他们又有什么作用?

首先进入类 代码

依据3个构造方法可以知道,由provider创建了ServerSocketChannel  provider 暂且不理睬,还有一个channelConfige也被创建了

依据类名 推测得知这个是关于channel的配置清单。

继续跟踪super方法 会进入到messageChannel 和abstractChannel

public class NioServerSocketChannel extends AbstractNioMessageChannel implements ServerSocketChannel {
    private static final ChannelMetadata METADATA = new ChannelMetadata(false);
    private static final SelectorProvider DEFAULT_SELECTOR_PROVIDER = SelectorProvider.provider();
    private static final InternalLogger logger = InternalLoggerFactory.getInstance(NioServerSocketChannel.class);
    private final ServerSocketChannelConfig config;

    private static java.nio.channels.ServerSocketChannel newSocket(SelectorProvider provider) {
        try {
            return provider.openServerSocketChannel();
        } catch (IOException var2) {
            throw new ChannelException("Failed to open a server socket.", var2);
        }
    }

    public NioServerSocketChannel() {
        this(newSocket(DEFAULT_SELECTOR_PROVIDER));
    }

    public NioServerSocketChannel(SelectorProvider provider) {
        this(newSocket(provider));
    }

    public NioServerSocketChannel(java.nio.channels.ServerSocketChannel channel) {
        super((Channel)null, channel, 16);
        this.config = new NioServerSocketChannel.NioServerSocketChannelConfig(this, this.javaChannel().socket());
    }
}

AbstractChannel中的构造方法中 发现生成了 id、unsafe、pipeline。id 是channel的唯一标识,unsafe进入接口上层发现是 管理channel的生命周期。发现其内部方法有(注册,绑定,连接,断开,开始读写....)而pipleline 可以理解为channel管道设置的关卡, 方便用户包装流体 如加密解密,自定义协议,编码格式.....

    protected AbstractChannel(Channel parent) {
        this.parent = parent;
        this.id = DefaultChannelId.newInstance();
        this.unsafe = this.newUnsafe();
        this.pipeline = new DefaultChannelPipeline(this);
    }

至此 ,我们总算把一个服务端channel创建完毕了,顺带把其他重大组件也给番出来了

  • channel
  • channelConfig
  • channelid
  • unsafe
  • pipeline
  • channelhandler

在newchannel 之后 进入initandregister的init方法

init方法

大致可以理解为 channel的属性设置。options,attrrs,以及 加入一个ServerBootstrapAcceptor。

options,atrtrs 具体干啥 暂且放过它,反正也被扔进config内部去了。void initChannel 中新增加一个连接处理器,再往内部注入一个什么什么接收处理器....也暂且放过他。


    void init(Channel channel) throws Exception {
        Map<ChannelOption<?>, Object> options = this.options();
        synchronized(options) {
            channel.config().setOptions(options);
        }

        Map<AttributeKey<?>, Object> attrs = this.attrs();
        synchronized(attrs) {
            Iterator i$ = attrs.entrySet().iterator();

            while(true) {
                if (!i$.hasNext()) {
                    break;
                }

                Entry<AttributeKey<?>, Object> e = (Entry)i$.next();
                AttributeKey<Object> key = (AttributeKey)e.getKey();
                channel.attr(key).set(e.getValue());
            }
        }

        ChannelPipeline p = channel.pipeline();
        if (this.handler() != null) {
            p.addLast(new ChannelHandler[]{this.handler()});
        }

        final EventLoopGroup currentChildGroup = this.childGroup;
        final ChannelHandler currentChildHandler = this.childHandler;
        final Entry[] currentChildOptions;
        synchronized(this.childOptions) {
            currentChildOptions = (Entry[])this.childOptions.entrySet().toArray(newOptionArray(this.childOptions.size()));
        }

        final Entry[] currentChildAttrs;
        synchronized(this.childAttrs) {
            currentChildAttrs = (Entry[])this.childAttrs.entrySet().toArray(newAttrArray(this.childAttrs.size()));
        }

        p.addLast(new ChannelHandler[]{new ChannelInitializer<Channel>() {
            public void initChannel(Channel ch) throws Exception {
                ch.pipeline().addLast(new ChannelHandler[]{new ServerBootstrap.ServerBootstrapAcceptor(currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs)});
            }
        }});
    }

register0()

再次进入到 initandRegister的 register方法中去 饶一圈我们再回来进入doBind0 就会发现开始调用jdk的方法绑定端口号了。

 Channel channel = this.channelFactory().newChannel();

 ChannelFuture regFuture = this.group().register(channel);

往上层走入EventLoopGroup 再往下实现走入 EmbeddedEventLoop中

public interface EventLoopGroup extends EventExecutorGroup {
    EventLoop next();

    ChannelFuture register(Channel var1);

    ChannelFuture register(Channel var1, ChannelPromise var2);
}
   public ChannelFuture register(Channel channel) {
        return this.register(channel, new DefaultChannelPromise(channel, this));
    }


    public ChannelFuture register(Channel channel, ChannelPromise promise) {
        channel.unsafe().register(this, promise);
        return promise;
    }

突然发现又进入到了之前创建的channel 番出来的unsafe 进入到他的实现类去AbstractChanel

public final void register(EventLoop eventLoop, final ChannelPromise promise) {
            if (eventLoop == null) {
                throw new NullPointerException("eventLoop");
            } else if (promise == null) {
                throw new NullPointerException("promise");
            } else if (AbstractChannel.this.isRegistered()) {
                promise.setFailure(new IllegalStateException("registered to an event loop already"));
            } else if (!AbstractChannel.this.isCompatible(eventLoop)) {
                promise.setFailure(new IllegalStateException("incompatible event loop type: " + eventLoop.getClass().getName()));
            } else {
                if (AbstractChannel.this.eventLoop == null) {
                    AbstractChannel.this.eventLoop = AbstractChannel.this.new PausableChannelEventLoop(eventLoop);
                } else {
                    AbstractChannel.this.eventLoop.unwrapped = eventLoop;
                }

                if (eventLoop.inEventLoop()) {
                    this.register0(promise);
                } else {
                    try {
                        eventLoop.execute(new OneTimeTask() {
                            public void run() {
                                AbstractUnsafe.this.register0(promise);
                            }
                        });
                    } catch (Throwable var4) {
                        AbstractChannel.logger.warn("Force-closing a channel whose registration task was not accepted by an event loop: {}", AbstractChannel.this, var4);
                        this.closeForcibly();
                        AbstractChannel.this.closeFuture.setClosed();
                        this.safeSetFailure(promise, var4);
                    }
                }

            }
        }

进入到register0中一路跟下去 会发现最终会回调到 用户自定义的adapter 监听处理事件中去 这期间 在register0方法中开始 调用 isActive()方法,这个是jdk的socket方法了。判断是否channel已被激活(socket 绑定address)。

 private void register0(ChannelPromise promise) {
            try {
                if (!promise.setUncancellable() || !this.ensureOpen(promise)) {
                    return;
                }

                boolean firstRegistration = this.neverRegistered;
                AbstractChannel.this.doRegister();
                this.neverRegistered = false;
                AbstractChannel.this.registered = true;
                AbstractChannel.this.eventLoop.acceptNewTasks();
                this.safeSetSuccess(promise);
                AbstractChannel.this.pipeline.fireChannelRegistered();
                if (firstRegistration && AbstractChannel.this.isActive()) {
                    AbstractChannel.this.pipeline.fireChannelActive();
                }
            } catch (Throwable var3) {
                this.closeForcibly();
                AbstractChannel.this.closeFuture.setClosed();
                this.safeSetFailure(promise, var3);
            }

        }

isActive() 调用jdk 在NioServerSocketChannel类中

    public boolean isActive() {
        return this.javaChannel().socket().isBound();
    }

doBind0()

继续跟进会进入到 abstractChannel 类 (可以用debug追溯)  调用doBind方法 开始调用jdk的socket绑定ip 成功之后再调用fireChannelActivie 即回调到我们自定义的adapter中去。此时整个channel 创建完毕 连接成功。

        public final void bind(SocketAddress localAddress, ChannelPromise promise) {
            if (promise.setUncancellable() && this.ensureOpen(promise)) {
                if (Boolean.TRUE.equals(AbstractChannel.this.config().getOption(ChannelOption.SO_BROADCAST)) && localAddress instanceof InetSocketAddress && !((InetSocketAddress)localAddress).getAddress().isAnyLocalAddress() && !PlatformDependent.isWindows() && !PlatformDependent.isRoot()) {
                    AbstractChannel.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 = AbstractChannel.this.isActive();

                try {
                    AbstractChannel.this.doBind(localAddress);
                } catch (Throwable var5) {
                    this.safeSetFailure(promise, var5);
                    this.closeIfClosed();
                    return;
                }

                if (!wasActive && AbstractChannel.this.isActive()) {
                    this.invokeLater(new OneTimeTask() {
                        public void run() {
                            AbstractChannel.this.pipeline.fireChannelActive();
                        }
                    });
                }

                this.safeSetSuccess(promise);
            }
        }

    protected void doBind(SocketAddress localAddress) throws Exception {
        this.javaChannel().socket().bind(localAddress, this.config.getBacklog());
    }

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值