<2021SC@SDUSC>ServerBootstrap绑定端口

2021SC@SDUSC

一、前言

在之前的博客中,大致分析了netty的NioEventLoopGroup的创建过程,在这篇博客中,将会分析ServerBootstrap的绑定方法,这是服务器端的方法。

二、ServerBootstrap类

ServerBootstrap是netty的服务器启动的引导类。

public class ServerBootstrap extends AbstractBootstrap<ServerBootstrap, ServerChannel>

在这里插入图片描述
图为ServerBootstrap的继承体系。
在之前博客中的服务器-客户端通信的实例中,服务器bootstrap是通过无参构造方式创建的。

            //netty的引导程序
            ServerBootstrap bootstrap = new ServerBootstrap();
    public ServerBootstrap() { }

可以看到,在netty中,ServerBootstrap的无参构造方法没有任何实现,之后,通过该对象的其它方法来指定一些配置,包括线程模型、Nio的channel类型、端口号等。本片博客主要会分析ServerBootstrap的bind方法,也就是绑定端口。

三、bind

在示例代码中,调用了参数为int类型的bind()方法,其具体实现是

    public ChannelFuture bind(int inetPort) {
        return bind(new InetSocketAddress(inetPort));
    }

可以看到,以传入的参数创建一个InetSocketAddress类的对象,顺便InetSocketAddress是SocketAddress类的子类,然后,再调用另外一个bind方法。该方法是

    public ChannelFuture bind(SocketAddress localAddress) {
        validate();
        return doBind(ObjectUtil.checkNotNull(localAddress, "localAddress"));
    }

在该方法中,实现调用了validate()方法,其作用是判断group、channelFactory、childHandler以及childGroup是否为空,如果是null,抛出异常。以上这些属性分别在group、channel、childHandler方法中指定。

	//ServerBootstrap中的validate方法
    @Override
    public ServerBootstrap validate() {
        super.validate();
        if (childHandler == null) {
            throw new IllegalStateException("childHandler not set");
        }
        if (childGroup == null) {
            logger.warn("childGroup is not set. Using parentGroup instead.");
            childGroup = config.group();
        }
        return this;
    }
	//AbstractBootstrap中地validate方法
    public B validate() {
        if (group == null) {
            throw new IllegalStateException("group not set");
        }
        if (channelFactory == null) {
            throw new IllegalStateException("channel or channelFactory not set");
        }
        return self();
    }

通过的话,返回到bind中,接着判断localAddress是否为空,如果是null,抛出空指针异常;通过的话,去执行doBind方法。
总之,在bind方法中,首先创建了InetSocketAddress类的对象,接着对一些变量进行判断,真正的绑定工作则要等到doBind方法中来完成。实际上,在netty源码中,存在许多的do开头的方法,而这些方法往往是负责实际内容的。

四、doBind

bind和doBind其实是AbstractBootstrap的方法。

    private ChannelFuture doBind(final SocketAddress localAddress) {
        final ChannelFuture regFuture = initAndRegister();
        final Channel channel = regFuture.channel();
        if (regFuture.cause() != null) {
            return regFuture;
        }

        if (regFuture.isDone()) {
            // At this point we know that the registration was complete and successful.
            ChannelPromise promise = channel.newPromise();
            doBind0(regFuture, channel, localAddress, promise);
            return promise;
        } else {
            // Registration future is almost always fulfilled already, but just in case it's not.
            final PendingRegistrationPromise promise = new PendingRegistrationPromise(channel);
            regFuture.addListener(new ChannelFutureListener() {
                @Override
                public void operationComplete(ChannelFuture future) throws Exception {
                    Throwable cause = future.cause();
                    if (cause != null) {
                        // Registration on the EventLoop failed so fail the ChannelPromise directly to not cause an
                        // IllegalStateException once we try to access the EventLoop of the Channel.
                        promise.setFailure(cause);
                    } else {
                        // Registration was successful, so set the correct executor to use.
                        // See https://github.com/netty/netty/issues/2586
                        promise.registered();

                        doBind0(regFuture, channel, localAddress, promise);
                    }
                }
            });
            return promise;
        }
    }

在方法的最开始,调用了initAndRegister方法,就是初始化与注册,具体的实现,会在下一篇博客中分析,现在,只需要知道,它完成了channel的创建,而之后通过regFuture获得了这个channel。
之后,先判断regFuture.cause()是否为空值,如果不是null,意味着在initAndRegister的过程中出现异常,直接返回。注意,在netty的代码中,往往是异步执行的,也就是说,有返回值并不等于执行过程已经完成了,所以,是否出现异常,应该等待之后判断。
接着,regFuture.isDone()判断是否执行完成,如果完成,调用doBind0()方法,增加一个监听器,等待完成,之后,再判断是否出现异常,如果没有,调用doBind0()方法。
关于doBind0方法,也会在下一篇博客中分析。

五、总结

在本篇博客中,分析了netty的服务器端绑定端口的过程,其中,包括initAndRegister和doBind0方法,有很多地方没有分析,只是简单地分析了过程,发现在bind方法中,只完成了一些基本过程,而实际地逻辑则要到doBind方法中完成,此外,bind、dobind、vaildate等方法其实是高层次地抽象方法,所以,具体实现可以由用户自定义。
此外,在netty地学习中,已经不止一次地出现了异步调用方法,这种方法的返回值往往是Future类型,也就是说,这类方法往往在执行过程中开启一个线程来完成具体逻辑,而返回一个Future对象,允许调用者在需要使用真正需要的数据时,可以通过Future对象获取。
但是,应该注意到,因为是异步调用的原因,在实际编写代码时,应该考虑到需要数据时,存在三种可能,一是,方法已经完成,数据已经存在;二是,方法执行过程中出现异常,这种时候,jdk的Future类型不能很好地处理;三是,方法还没有完成,在doBind方法中,添加一个监听器,等待方法完成,再接着处理接下来的逻辑。

将这个匿名内部类定义为lambda表达式:thread = new Thread(new Runnable() { @Override public void run() { //服务端要建立两个group,一个负责接收客户端的连接,一个负责处理数据传输 //连接处理group EventLoopGroup boss = new NioEventLoopGroup(); //事件处理group EventLoopGroup worker = new NioEventLoopGroup(); ServerBootstrap bootstrap = new ServerBootstrap(); // 绑定处理group bootstrap.group(boss, worker).channel(NioServerSocketChannel.class) //保持连接数 .option(ChannelOption.SO_BACKLOG, 300) //有数据立即发送 .option(ChannelOption.TCP_NODELAY, true) //保持连接 .childOption(ChannelOption.SO_KEEPALIVE, true) //处理新连接 .childHandler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel sc) throws Exception { // 增加任务处理 ChannelPipeline p = sc.pipeline(); p.addLast( // //使用了netty自带的编码器和解码器 // new StringDecoder(), // new StringEncoder(), //心跳检测,读超时,写超时,读写超时 new IdleStateHandler(0, 0, 3, TimeUnit.MINUTES), //自定义的处理器 new ServerHandler()); } }); //绑定端口,同步等待成功 ChannelFuture future; try { future = bootstrap.bind(port).sync(); if (future.isSuccess()) { serverSocketChannel = (ServerSocketChannel) future.channel(); log.debug("服务端启动成功,端口:"+port); } else { log.debug("服务端启动失败!"); } //等待服务监听端口关闭,就是由于这里会将线程阻塞,导致无法发送信息,所以我这里开了线程 future.channel().closeFuture().sync(); }catch (InterruptedException e) { Thread.currentThread().interrupt(); } catch (Exception e) { log.debug(e.getMessage()); } finally { //优雅地退出,释放线程池资源 boss.shutdownGracefully(); worker.shutdownGracefully(); } } });
06-08
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

东羚

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值