美文网首页Java知识netty
《跟闪电侠学Netty》01: 服务端启动流程介绍[附视频]

《跟闪电侠学Netty》01: 服务端启动流程介绍[附视频]

作者: 简书闪电侠 | 来源:发表于2018-06-24 10:18 被阅读2121次

    最小化服务端启动demo

    这篇文章通过图文+视频的方式介绍Netty服务端启动流程,习惯看视频操作,体验敲代码快感的小伙伴可以直接翻到文末哦~

    我们先来看一下最小化服务端启动demo,我们直接来上代码,然后逐个解释

    NettyServer.java

    public class NettyServer {
        public static void main(String[] args) {
            NioEventLoopGroup boosGroup = new NioEventLoopGroup();
            NioEventLoopGroup workerGroup = new NioEventLoopGroup();
    
            ServerBootstrap serverBootstrap = new ServerBootstrap();
            serverBootstrap
                    .group(boosGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .childHandler(new ChannelInitializer<NioSocketChannel>() {
                        protected void initChannel(NioSocketChannel ch) {
                        }
                    });
    
            serverBootstrap.bind(8000);
        }
    }
    
    • 首先看到,我们创建了两个NioEventLoopGroup,这两个对象可以看做是传统IO编程模型的两大线程组,boosGroup表示监听端口,创建新连接的线程组,workerGroup表示处理每一条连接的数据读写的线程组,不理解的同学可以看一下上一篇博文《跟闪电侠学Netty》开篇:Netty是什么?。用生活中的例子来讲就是,一个工厂要运作,必然要有一个老板负责从外面接活,然后有很多员工,负责具体干活,老板就是boosGroup,员工们就是workerGroupboosGroup接收完连接,扔给workerGroup去处理。
    • 接下来 我们创建了一个引导类 ServerBootstrap,这个类将引导我们进行服务端的启动工作,直接new出来开搞。
    • 我们通过.group(boosGroup, workerGroup)给引导类配置两大线程,这个引导类的线程模型也就定型了。
    • 然后,我们指定我们服务端的IO模型为NIO,我们通过.channel(NioServerSocketChannel.class)来指定IO模型,当然,这里也有其他的选择,如果你想指定IO模型为BIO,那么这里配置上OioServerSocketChannel.class类型即可,当然通常我们也不会这么做,因为Netty的优势就在于NIO。
    • 接着,我们调用childHandler()方法,给这个引导类创建一个ChannelInitializer,这里主要就是定义后续每条连接的数据读写,业务处理逻辑,不理解没关系,在后面我们会详细分析。ChannelInitializer这个类中,我们注意到有一个泛型参数NioSocketChannel,这个类呢,就是Netty对NIO类型的连接的抽象,而我们前面NioServerSocketChannel也是对NIO类型的连接的抽象,NioServerSocketChannelNioSocketChannel的概念可以和BIO编程模型中的ServerSocket以及Socket两个概念对应上
    • 我们的最小化参数配置到这里就完成了,我们总结一下就是,要启动一个Netty服务端,必须要指定三类属性,分别是线程模型、IO模型、连接读写处理逻辑,有了这三者,之后在调用bind(8000),我们就可以在本地绑定一个8000端口启动起来,以上这段代码读者可以直接拷贝到你的IDE中运行。

    自动绑定递增端口

    在上面代码中我们绑定了8000端口,接下来我们实现一个稍微复杂一点的逻辑,我们指定一个起始端口号,比如1000,然后呢,我们从1000号端口往上找一个端口,直到这个端口能够绑定成功,比如1000端口不可用,我们就尝试绑定1001,然后1002,依次类推。

    serverBootstrap.bind(8000);这个方法呢,它是一个异步的方法,调用之后是立即返回的,他的返回值是一个ChannelFuture,我们可以给这个ChannelFuture添加一个监听器GenericFutureListener,然后我们在GenericFutureListeneroperationComplete方法里面,我们可以监听端口是否绑定成功,接下来是监测端口是否绑定成功的代码片段

    serverBootstrap.bind(8000).addListener(new GenericFutureListener<Future<? super Void>>() {
        public void operationComplete(Future<? super Void> future) {
            if (future.isSuccess()) {
                System.out.println("端口绑定成功!");
            } else {
                System.err.println("端口绑定失败!");
            }
        }
    });
    
    

    我们接下来从1000端口号,开始往上找端口号,直到端口绑定成功,我们要做的就是在 if (future.isSuccess())的else逻辑里面重新绑定一个递增的端口号,接下来,我们把这段绑定逻辑抽取出一个bind方法

    private static void bind(final ServerBootstrap serverBootstrap, final int port) {
        serverBootstrap.bind(port).addListener(new GenericFutureListener<Future<? super Void>>() {
            public void operationComplete(Future<? super Void> future) {
                if (future.isSuccess()) {
                    System.out.println("端口[" + port + "]绑定成功!");
                } else {
                    System.err.println("端口[" + port + "]绑定失败!");
                    bind(serverBootstrap, port + 1);
                }
            }
        });
    }
    
    

    然后呢,以上代码中最关键的就是在端口绑定失败之后,重新调用自身方法,并且把端口号加一,然后,在我们的主流程里面,我们就可以直接调用

    bind(serverBootstrap, 1000)
    

    读者可以自定修改代码,运行之后可以看到效果,最终会发现,端口成功绑定了在1024,从1000开始到1023,端口均绑定失败了,这是因为在我的MAC系统下,1023以下的端口号都是被系统保留了,需要ROOT权限才能绑定。

    以上就是自动绑定递增端口的逻辑,接下来,我们来一起学习一下,服务端启动,我们的引导类ServerBootstrap除了指定线程模型,IO模型,连接读写处理逻辑之外,他还可以干哪些事情?

    服务端启动其他方法

    handler() 方法

    serverBootstrap.handler(new ChannelInitializer<NioServerSocketChannel>() {
        protected void initChannel(NioServerSocketChannel ch) {
            System.out.println("服务端启动中");
        }
    })
    

    handler()方法呢,可以和我们前面分析的childHandler()方法对应起来,childHandler()用于指定处理新连接数据的读写处理逻辑,handler()用于指定在服务端启动过程中的一些逻辑,通常情况下呢,我们用不着这个方法。

    attr() 方法

    serverBootstrap.attr(AttributeKey.newInstance("serverName"), "nettyServer")
    

    attr()方法可以给服务端的channel,也就是NioServerSocketChannel指定一些自定义属性,然后我们可以通过channel.attr()取出这个属性,比如,上面的代码我们指定我们服务端channel的一个serverName属性,属性值为nettyServer,其实说白了就是给NioServerSocketChannel维护一个map而已,通常情况下,我们也用不上这个方法。

    那么,当然,除了可以给服务端channel NioServerSocketChannel指定一些自定义属性之外,我们还可以给每一条连接指定自定义属性

    childAttr() 方法

    serverBootstrap.childAttr(AttributeKey.newInstance("clientKey"), "clientValue")
    

    上面的childAttr可以给每一条连接指定自定义属性,然后后续我们可以通过channel.attr()取出该属性,详情请看视频演示

    childOption() 方法

    serverBootstrap
            .childOption(ChannelOption.SO_KEEPALIVE, true)
            .childOption(ChannelOption.TCP_NODELAY, true)
    
    

    childOption()可以给每条连接设置一些TCP底层相关的属性,比如上面,我们设置了三种TCP属性,其中

    • ChannelOption.SO_KEEPALIVE表示是否开启TCP底层心跳机制,true为开启
    • ChannelOption.TCP_NODELAY表示是否开始Nagle算法,true表示关闭,false表示开启,通俗地说,如果要求高实时性,有数据发送时就马上发送,就关闭,如果需要减少发送次数减少网络交互,就开启。

    其他的参数这里就不一一讲解,有兴趣的同学可以去这个类里面自行研究。

    option()

    除了给每个连接设置这一系列属性之外,我们还可以给服务端channel设置一些属性,最常见的就是so_backlog,如下设置

    serverBootstrap.option(ChannelOption.SO_BACKLOG, 1024)
    

    表示系统用于临时存放已完成三次握手的请求的队列的最大长度,如果连接建立频繁,服务器处理创建新连接较慢,可以适当调大这个参数

    总结

    • 本文中,我们首先学习了Netty服务端启动的流程,一句话来说就是:创建一个引导类,然后给他指定线程模型,IO模型,连接读写处理逻辑,绑定端口之后,服务端就启动起来了。
    • 然后,我们学习到bind方法是异步的,我们可以通过这个异步机制来实现端口递增绑定。
    • 最后呢,我们讨论了Netty服务端启动额外的参数,主要包括给服务端channel或者channel设置属性值,设置底层TCP参数。

    相信初学者跟着这篇文章敲打代码之后,能够独立地启动服务端,更多精彩,可以见视频
    如果,你觉得这个过程比较简单,想深入学习,了解服务端启动的底层原理,可以在imooc.com上搜索netty,找到我的源码分析视频,对应的第三章有对本节的最强原理解释。

    如果你没有学习过Netty,那么本系列将会是你零基础入门最好的资料,如果你对Netty有所掌握,想深入了解Netty的细节,摸透Netty的底层原理,那么我的源码分析视频(imooc.com搜索"Netty")将会是你最好的选择,截止目前,已经有400多位小伙伴加入。

    附录 《跟闪电侠学Netty》目录,不定期更新,欢迎关注

    • 《跟闪电侠学Netty》01: 服务端启动流程介绍 [图文+视频]
    • 《跟闪电侠学Netty》02: 客户端启动流程介绍 [图文+视频]
    • 《跟闪电侠学Netty》03: Pipeline及ChannelHandler介绍及其分类 [图文+视频]
    • 《跟闪电侠学Netty》04: ChannelInboundHandler及其生命周期 [图文+视频]
    • 《跟闪电侠学Netty》05: ChannelOutBoundHandler及其生命周期 [图文+视频]
    • 《跟闪电侠学Netty》06: Pipeline的inbound事件传播机制 [图文+视频]
    • 《跟闪电侠学Netty》07: Pipeline的outbound事件传播机制 [图文+视频]
    • 《跟闪电侠学Netty》08: Pipeline的异常传播机制 [图文+视频]
    • 《跟闪电侠学Netty》09: 数据传输载体ByteBuf介绍 [图文+视频]
    • 《跟闪电侠学Netty》10: ByteBuf三维度分类 [图文+视频]
    • 《跟闪电侠学Netty》11: ByteBuf分配器介绍 [图文+视频]
    • 《跟闪电侠学Netty》12: 客户端与服务端简单通信 [图文+视频]
    • 《跟闪电侠学Netty》13: 服务端粘包问题以及解决方案 [图文+视频]
    • 《跟闪电侠学Netty》14: 服务端发送push, 基于MessageToByteEncoder实现自定义编码器 [图文+视频]
    • 《跟闪电侠学Netty》15: 编码转换器MessageToMessageEncoder介绍 [图文+视频]
    • 《跟闪电侠学Netty》16: 客户端收push, 基于ByteToMessageDecoder实现自定义协议解码器 [图文+视频]
    • 《跟闪电侠学Netty》17: 解码转换器MessageToMessageDecoder [图文+视频]
    • 《跟闪电侠学Netty》18: 客户端发送ack与服务端处理ack [图文+视频]
    • 《跟闪电侠学Netty》19: Netty中的异步处理及回调机制 [图文+视频]
    • 《跟闪电侠学Netty》20: NioEventLoop以及业务线程隔离 [图文+视频]
    • 《跟闪电侠学Netty》21: NioEventLoop实现客户端定时心跳 [图文+视频]
    • 《跟闪电侠学Netty》22: 服务端空闲监测与超时关闭客户端连接 [图文+视频]
    • 《跟闪电侠学Netty》23: 编写一个WebSocket应用程序[视频]
    • 《跟闪电侠学Netty》24: 编写一个Http Proxy与性能优化[视频]
    • 《跟闪电侠学Netty》25: 编写一个简单的RPC通信服务[视频]
    • 《跟闪电侠学Netty》26: Netty性能优化最佳实践 [视频]

    相关文章

      网友评论

      • 糖雪球233:大佬请教一个问题啊 客户端发送过来一个pb消息 服务端的channelRegistered 接受到了 但是channelRead并没有反应 这是为什么?
      • d8c424478728:非常感谢,来早了,不能一次看个爽😂
        zhyang0918:@简书闪电侠 已买了大佬的小册:smile:
        简书闪电侠:已转移掘金小册,十月发布
      • 阿群1986:boss拼成boos了
      • 夏保森:期待第二讲
      • 鋒Nic:* 01: 服务端启动流程介绍
        * 要启动Netty服务端,必须要指定三类属性,分别是线程模型、IO模型、连接读写处理逻辑
        * Netty服务端启动的流程是创建引导类给引导类指定线程模型,IO模型,连接读写处理逻辑,绑定端口之后服务端就启动起来
        * bind方法是异步的通过异步机制来实现端口递增绑定
        * Netty服务端启动额外的参数,主要包括给服务端channel或者channel设置属性值,设置底层TCP参数
        * 创建两个NioEventLoopGroup,这两个对象可以看做是传统IO编程模型的两大线程组,boosGroup表示监听端口,创建新连接的线程组,workerGroup表示处理每一条连接的数据读写的线程组
        * 创建引导类 ServerBootstrap进行服务端的启动工作,通过.group(boosGroup, workerGroup)给引导类配置两大线程定型引导类的线程模型指定服务端的IO模型为NIO,通过.channel(NioServerSocketChannel.class)来指定IO模型
        * 调用childHandler()方法给引导类创建ChannelInitializer定义后续每条连接的数据读写,业务处理逻辑,泛型参数NioSocketChannel是Netty对NIO类型的连接的抽象,而NioServerSocketChannel也是对NIO类型的连接的抽象
        * serverBootstrap.bind()是异步的方法调用之后是立即返回的,返回值是ChannelFuture,给ChannelFuture添加监听器GenericFutureListener,在GenericFutureListener的operationComplete方法里面监听端口是否绑定成功
        * childHandler()用于指定处理新连接数据的读写处理逻辑,handler()用于指定在服务端启动过程中的一些逻辑
        * attr()方法给服务端的channel即NioServerSocketChannel指定一些自定义属性,通过channel.attr()取出该属性,给NioServerSocketChannel维护一个map
        * childAttr()方法给每一条连接指定自定义属性,通过channel.attr()取出该属性
        * childOption()方法给每条连接设置一些TCP底层相关的属性:
        * ChannelOption.SO_KEEPALIVE表示是否开启TCP底层心跳机制,true为开启
        * ChannelOption.SO_REUSEADDR表示端口释放后立即就可以被再次使用,因为一般来说,一个端口释放后会等待两分钟之后才能再被使用。
        * ChannelOption.TCP_NODELAY表示是否开始Nagle算法,true表示关闭,false表示开启,通俗地说,如果要求高实时性,有数据发送时就马上发送,就关闭,如果需要减少发送次数减少网络交互,就开启。
        * option()方法给服务端channel一些TCP底层相关的属性:
        * ChannelOption.SO_BACKLOG表示系统用于临时存放已完成三次握手的请求的队列的最大长度,如果连接建立频繁,服务器处理创建新连接较慢,适当调大这个参数
        闪电侠第五季第一集很好看,第二集是不是在路上了~
        简书闪电侠:@万国辉_8e75 谢谢支持
        万国辉_8e75:写的非常赞,通俗易懂
        简书闪电侠:在路上了,哈哈

      本文标题:《跟闪电侠学Netty》01: 服务端启动流程介绍[附视频]

      本文链接:https://www.haomeiwen.com/subject/kzzdyftx.html