揭秘Netty之——快速启动netty

netty作为当今最为流行的网络通信框架之一,包括RPC、消息等中间件都在用,很有必要深入研究一下。下面从netty的快速启动开始分析。

netty的启动包括服务端的启动与客户端的启动,入口主要是ServerBootstrap、Bootstrap。ServerBootstrap启动服务端,Bootstrap启动客户端。服务端的启动比较复杂,客户端启动相对简单。

TCP握手过程

启动的过程其实就是建立网络连接的过程,所以首先得回顾一下TCP的三次握手和4次挥手。

建立连接的过程

断开连接的过程

1)如果建立连接后,客户端发生了故障,怎么办?

如果服务端发生故障,很简单所有的连接都会被动断开,客户端可以定时重试连接。

如果客户端发生故障,因为连接越多,服务端的压力也会越大,所以服务端不会允许无用的链接存在占用资源。这就涉及到 一个探活器(心跳设计)。服务端会定时发送心跳包到客户端,如果连续几次都没有回应,则认为该链接没意义,服务端会主动断开该连接。

服务端启动

服务端的启动从ServerBootstrap说起,这个类是用的流式设计,方便配置各种信息,先看一段简易的代码如下。

public static void main(String[] args) throws InterruptedException {
        ServerBootstrap serverBootstrap = new ServerBootstrap();

        NioEventLoopGroup boss = new NioEventLoopGroup(2);
        NioEventLoopGroup worker = new NioEventLoopGroup(5);

        serverBootstrap.channel(NioServerSocketChannel.class)
                .group(boss, worker)
                .option(ChannelOption.SO_BACKLOG, 5)
                .childOption(ChannelOption.SO_KEEPALIVE, true)
                .childHandler(new ChannelInitializer<NioSocketChannel>() {
                    @Override
                    protected void initChannel(NioSocketChannel ch) throws Exception {
                        ch.pipeline().addLast(new DelimiterBasedFrameDecoder(8192, Delimiters.lineDelimiter()));
                        ch.pipeline().addLast(new StringDecoder());
                        ch.pipeline().addLast(new StringEncoder());

                    }
                });

        ChannelFuture future = serverBootstrap.bind(18080).sync();
        future.channel().closeFuture().addListener(new GenericFutureListener() {

            @Override
            public void operationComplete(Future future) throws Exception {
                boss.shutdownGracefully();
                worker.shutdownGracefully();
            }
        }).sync();


    }

服务端启动的过程中,核心主要干了这么几件事:

1)指定channel类型,这决定了服务端创建什么类型的channel。

2)指定服务端运行的线程组,包括boss线程组、worker线程组,只有指定了线程组,后续服务端才能正常工作

3)为parentChannel与childChannel指定网络协议的配置

4)为channel指定相关的channelHandler,这是网络IO读写过程中会用到的逻辑处理类,主要是设置childChannel的Handler。

5)绑定端口。这算是服务端启动的最后一步了,服务端绑定了端口之后,就是等待客户端来建立连接了。

那绑定端口主要干啥了呢,我们通过源码可以发现,绑定端口的时候主要做以下几件事:

1)创建parentChannel——>初始化parentChannel的配置,包括网络协议的配置,pipeline的配置等等——>注册parentChannel,将parentChannel注册到boss线程组中的某个线程上,同时与selector关联,等待客户端的连接进来。从此该线程与selector便会跟随该parentChannel一辈子。这样做的好处就是不需要切换线程上下文了,提高服务器的性能。

2)当有客户端请求进来的时候,parentChannel关联的pipeline中会有对应的channelHandler处理,读取客户端的连接请求,生成childChannel,同时对于childChannel,又走一遍1的流程(创建channel、初始化channel、注册channel),唯一不同的就是,childChannel关联的是work线程组中的线程,这样的话boss线程组与worker线程组的职责是分明的。

总结一下,整个启动过程其实都是围绕channel来做的,主要就是:创建channel——初始化channel(其中包括channelPipeline、channelHandler的设置,往channelPipeline中添加channelHandler的时候,会自动触发handleAdded事件)——注册channel——doBind()——channelActive。

客户端启动

客户端的启动相对简单,同样的先看一段简易客户端启动的简易代码。

public static void main(String[] args) throws InterruptedException {
        Bootstrap bootstrap = new Bootstrap();

        NioEventLoopGroup worker = new NioEventLoopGroup(5);

        bootstrap.channel(NioSocketChannel.class)
                .group(worker)
                .handler(new ChannelInitializer<NioSocketChannel>() {
                    @Override
                    protected void initChannel(NioSocketChannel ch) throws Exception {
                        ch.pipeline().addLast(new DelimiterBasedFrameDecoder(8192, Delimiters.lineDelimiter()));
                        ch.pipeline().addLast(new StringDecoder());
                        ch.pipeline().addLast(new StringEncoder());

                    }
                })
                .remoteAddress("127.0.0.1", 18080);

        ChannelFuture future = bootstrap.connect().sync();
        future.channel().closeFuture().addListener(new GenericFutureListener() {

            @Override
            public void operationComplete(Future future) throws Exception {
                worker.shutdownGracefully();
            }
        }).sync();


    }

可见客户端的启动过程主要包括:

1)指定channel类型,这决定了客户端建立连接时创建什么类型的channel

2)指定线程组模型,用于客户端通信

3)指定channelHandler,用于通信时处理IO

4)指定ChannelOption网络协议配置

5)一切就绪后,就可以开始连接服务端了。连接成功后就可以开始通信了。

这里有点要注意的就是,无论时客户端还是服务端,对于channel的处理过程都是类似的。创建channel、初始化channel、注册channel。

下回分解

netty中线程组的概念是什么意思?

为什么有boss线性组与worker线程组?它们分别是怎么分工的?

parentChannel与childChannel是什么关系?在netty中分别代表什么?

ChannelHandler的工作机制是怎样的?netty具体是怎么实现的?

网络编程中的channel到底是啥玩意儿?channel、客户端、服务端之间是啥关系?

号称是极品中的精品的网络线程模型到底是长啥样的?netty中又是怎么实现的?

netty中到处都用的著名的pipeline模式是如何实现与应用的?

猜你喜欢

转载自blog.csdn.net/qq_42672856/article/details/115682937