Netty源码系列 之 ChannelPipeline & IO处理回顾 源码

目录

ChannelPipeline【包含AbstractUnsafe.write的源码流程,比之前更加深化了,必看】

ChannelPipeline概念回顾

ChannelPipeline的创建

Inbound(输入Handler)所对应的事件传播

Outbound(输出Handler)所对应的事件传播【包含AbstractUnsafe.write的源码流程,比之前更加深化了,必看】


ChannelPipeline【包含AbstractUnsafe.write的源码流程,比之前更加深化了,必看】

在细致剖析ChannelPipeline之前,我们拿pipeline管道add的一个最常见的Handler:帧解码器类LineBasedFrameDecoder,来进行debug源码展示其流程,进而为后续清晰描绘ChannelPipeline做铺垫

所有Handler,无论是是自定义的Handler还是netty原生的Handler,都是通过ChannelPipeline.addLast进行添加的,LineBasedFrameDecoder也不例外。

pipeline.addLast(new LineBasedFrameDecoder(1024));

以如下案例进行debug源码演示其流程

  • 以下测试案例以及源码过程演示的是消息数据出现粘包现象时,帧解码器LineBasedFrameDecoder的处理
package com.messi.netty_source_03.Test06;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.handler.logging.LoggingHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.net.InetSocketAddress;

public class MyNettyClient1 {
    private static final Logger log = LoggerFactory.getLogger(MyNettyClient1.class);

    public static void main(String[] args) throws InterruptedException {
        log.debug("myNettyClientStarter------");
        EventLoopGroup eventLoopGroup = new NioEventLoopGroup();
        Bootstrap bootstrap = new Bootstrap();
        bootstrap.channel(NioSocketChannel.class);
        Bootstrap group = bootstrap.group(eventLoopGroup);//32 ---> 1 IO操作 31线程
        bootstrap.handler(new ChannelInitializer() {
            @Override
            protected void initChannel(NioSocketChannel ch) throws Exception {
                ch.pipeline().addLast(new LoggingHandler());
                ch.pipeline().addLast(new StringEncoder());
            }
        });

        Channel channel = bootstrap.connect(new InetSocketAddress(8000)).sync().channel();
        //半包?粘包?  1  0
        //粘包 --->ByteBuf(1024) ---> socket 65535 --- server
        channel.writeAndFlush("sunshuai\nxiaohei\nxiaojr\n");
//        channel.writeAndFlush("sunshuaixiaohei666\n");

    }
}
package com.messi.netty_source_03.Test06;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.AdaptiveRecvByteBufAllocator;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.LineBasedFrameDecoder;
import io.netty.handler.logging.LoggingHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class MyNettyServer1 {


    private static final Logger log = LoggerFactory.getLogger(MyNettyServer1.class);

    public static void main(String[] args) {

        ServerBootstrap serverBootstrap = new ServerBootstrap();
        serverBootstrap.channel(NioServerSocketChannel.class);
        serverBootstrap.group(new NioEventLoopGroup());
        //接受socket缓冲区大小 等同于 滑动窗口的初始值 65535
        //serverBootstrap.option(ChannelOption.SO_RCVBUF, 100);
        //netty创建ByteBuf时 执行大小 默认1024 child ScoketChannel相关
//        serverBootstrap.childOption(ChannelOption.RCVBUF_ALLOCATOR,new AdaptiveRecvByteBufAllocator(16,16,16));
        serverBootstrap.childHandler(new ChannelInitializer() {
            @Override
            //
            protected void initChannel(NioSocketChannel ch) throws Exception {
                ChannelPipeline pipeline = ch.pipeline();
                //sunshuai\ni love you\n
                //xxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n
                //最大长度 指的是 如果超过最大长度,还没有发现分隔符,不处理。
                pipeline.addLast(new LineBasedFrameDecoder(1024));
                pipeline.addLast(new LoggingHandler());

            }
        });
        //
        serverBootstrap.bind(8000);
    }
}
  • debug源码流程 【之前总结过的源码流程一笔带过】

1.先debug启动服务端,完成服务端初始化操作后,会在NioEventLoop中Selector阻塞等待客户端连接,IO事件的触发

Netty源码系列 之 ChannelPipeline & IO处理回顾 源码_第1张图片

Netty源码系列 之 ChannelPipeline & IO处理回顾 源码_第2张图片

2.以运行方式进行启动客户端

3.服务端停止阻塞,开始处理Accept事件

Netty源码系列 之 ChannelPipeline & IO处理回顾 源码_第3张图片

Netty源码系列 之 ChannelPipeline & IO处理回顾 源码_第4张图片

虽然本次调用的也是read方法,但是触发的是Accept连接事件,客户端连接上来,服务端先处理连接,然后开启一个新NioEventLoop线程去完成客户端的注册和后续该SocketChannel所对应的IO事件的处理。

Netty源码系列 之 ChannelPipeline & IO处理回顾 源码_第5张图片

4.

Netty源码系列 之 ChannelPipeline & IO处理回顾 源码_第6张图片

5.客户端注册register的逻辑和服务端注册的逻辑是一致的

Netty源码系列 之 ChannelPipeline & IO处理回顾 源码_第7张图片

Netty源码系列 之 ChannelPipeline & IO处理回顾 源码_第8张图片

Netty源码系列 之 ChannelPipeline & IO处理回顾 源码_第9张图片

6.初始化工作

Netty源码系列 之 ChannelPipeline & IO处理回顾 源码_第10张图片

最终会回调到initChannel方法,完成自定义Handler的add

Netty源码系列 之 ChannelPipeline & IO处理回顾 源码_第11张图片

7.Accept连接事件处理完后,服务端接下来进行处理当前NioSocketChannel所对应的IO事件

Netty源码系列 之 ChannelPipeline & IO处理回顾 源码_第12张图片

服务端触发read事件,读取客户端发送过来的数据

Netty源码系列 之 ChannelPipeline & IO处理回顾 源码_第13张图片

前面的逻辑之前分析过,这里重点关注fireChannelRead中产生的逻辑:帧解码器Handler类的作用操作

Netty源码系列 之 ChannelPipeline & IO处理回顾 源码_第14张图片

回调帧解码器LineBasedFrameDecoder这一Handler的channelRead方法

Netty源码系列 之 ChannelPipeline & IO处理回顾 源码_第15张图片

ByteToMessageDecoder类:

Netty源码系列 之 ChannelPipeline & IO处理回顾 源码_第16张图片

callDecode方法:

Netty源码系列 之 ChannelPipeline & IO处理回顾 源码_第17张图片

Netty源码系列 之 ChannelPipeline & IO处理回顾 源码_第18张图片

一定注意一点:每一次通过帧解码器类进行解码消息,无论消息多长多短,一次只能解码出一条完整的消息,啥叫完整的消息?每遇见一个分隔符被称之为一条完整的消息数据

Netty源码系列 之 ChannelPipeline & IO处理回顾 源码_第19张图片

Netty源码系列 之 ChannelPipeline & IO处理回顾 源码_第20张图片

解码出一条完整的消息后:

Netty源码系列 之 ChannelPipeline & IO处理回顾 源码_第21张图片

Netty源码系列 之 ChannelPipeline & IO处理回顾 源码_第22张图片

Netty源码系列 之 ChannelPipeline & IO处理回顾 源码_第23张图片

直接看最后一条消息数据的处理

Netty源码系列 之 ChannelPipeline & IO处理回顾 源码_第24张图片

  • 修改一下测试案例,测试一下半包情况下,帧解码器的处理情况
package com.messi.netty_source_03.Test06;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.AdaptiveRecvByteBufAllocator;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.LineBasedFrameDecoder;
import io.netty.handler.logging.LoggingHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class MyNettyServer1 {


    private static final Logger log = LoggerFactory.getLogger(MyNettyServer1.class);

    public static void main(String[] args) {

        ServerBootstrap serverBootstrap = new ServerBootstrap();
        serverBootstrap.channel(NioServerSocketChannel.class);
        serverBootstrap.group(new NioEventLoopGroup());
        //接受socket缓冲区大小 等同于 滑动窗口的初始值 65535
        //serverBootstrap.option(ChannelOption.SO_RCVBUF, 100);
        //netty创建ByteBuf时 执行大小 默认1024 child ScoketChannel相关
        serverBootstrap.childOption(ChannelOption.RCVBUF_ALLOCATOR,new AdaptiveRecvByteBufAllocator(16,16,16));
        serverBootstrap.childHandler(new ChannelInitializer() {
            @Override
            //
            protected void initChannel(NioSocketChannel ch) throws Exception {
                ChannelPipeline pipeline = ch.pipeline();
                //sunshuai\ni love you\n
                //xxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n
                //最大长度 指的是 如果超过最大长度,还没有发现分隔符,不处理。
                pipeline.addLast(new LineBasedFrameDecoder(1024));
                pipeline.addLast(new LoggingHandler());

            }
        });
        //
        serverBootstrap.bind(8000);
    }
}
package com.messi.netty_source_03.Test06;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.handler.logging.LoggingHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.net.InetSocketAddress;

public class MyNettyClient1 {
    private static final Logger log = LoggerFactory.getLogger(MyNettyClient1.class);

    public static void main(String[] args) throws InterruptedException {
        log.debug("myNettyClientStarter------");
        EventLoopGroup eventLoopGroup = new NioEventLoopGroup();
        Bootstrap bootstrap = new Bootstrap();
        bootstrap.channel(NioSocketChannel.class);
        Bootstrap group = bootstrap.group(eventLoopGroup);//32 ---> 1 IO操作 31线程
        bootstrap.handler(new ChannelInitializer() {
            @Override
            protected void initChannel(NioSocketChannel ch) throws Exception {
                ch.pipeline().addLast(new LoggingHandler());
                ch.pipeline().addLast(new StringEncoder());
            }
        });

        Channel channel = bootstrap.connect(new InetSocketAddress(8000)).sync().channel();
        //半包?粘包?  1  0
        //粘包 --->ByteBuf(1024) ---> socket 65535 --- server
//        channel.writeAndFlush("sunshuai\nxiaohei\nxiaojr\n");
        channel.writeAndFlush("sunshuaixiaohei666\n");

    }
}

改动之处:

服务端:

Netty源码系列 之 ChannelPipeline & IO处理回顾 源码_第25张图片

客户端:

Netty源码系列 之 ChannelPipeline & IO处理回顾 源码_第26张图片

  • debug测试

以同样的方式启动服务端与客户端

相同的逻辑不再记录:

Netty源码系列 之 ChannelPipeline & IO处理回顾 源码_第27张图片

Netty源码系列 之 ChannelPipeline & IO处理回顾 源码_第28张图片

Netty源码系列 之 ChannelPipeline & IO处理回顾 源码_第29张图片

Netty源码系列 之 ChannelPipeline & IO处理回顾 源码_第30张图片

Netty源码系列 之 ChannelPipeline & IO处理回顾 源码_第31张图片

return null:

Netty源码系列 之 ChannelPipeline & IO处理回顾 源码_第32张图片

由于在第一轮decode后没有找到分隔符,所以out中没有数据

为什么?因为我们设置ByteBuf的最大值 初始值 最小值都为16,当read读取的数据长度一次最多为16,并且在读取到的ByteBuf中,数据没有发现分隔符,所以out输出集合中就没有add。

Netty源码系列 之 ChannelPipeline & IO处理回顾 源码_第33张图片

由于客户端写过来的消息数据还没有read完,所以会再一次让NioEventLoop线程进行处理read这一IO

Netty源码系列 之 ChannelPipeline & IO处理回顾 源码_第34张图片

Netty源码系列 之 ChannelPipeline & IO处理回顾 源码_第35张图片

Netty源码系列 之 ChannelPipeline & IO处理回顾 源码_第36张图片

Netty源码系列 之 ChannelPipeline & IO处理回顾 源码_第37张图片

Netty源码系列 之 ChannelPipeline & IO处理回顾 源码_第38张图片

Netty源码系列 之 ChannelPipeline & IO处理回顾 源码_第39张图片

这一次才找到分隔符,这样才可以返回一条完整的消息数据,并且在out集合中也会add这一条完整的消息数据

Netty源码系列 之 ChannelPipeline & IO处理回顾 源码_第40张图片

Netty源码系列 之 ChannelPipeline & IO处理回顾 源码_第41张图片

Netty源码系列 之 ChannelPipeline & IO处理回顾 源码_第42张图片

ChannelPipeline概念回顾

回顾ChannelPipeline相关的概念:

1.每一个客户端(SocketChannel | 线程)都独立享有一套pipeline管道,这样以空间换安全的方式,避免了多线程共享临界区资源导致并发安全问题。

2.Pipeline管道维护的是一组addLast进来的Handler,Pipeline的结构为双向链表。

3.Pipeline所维护的Handler的类型有哪些?ChannelInbound(输入[读]类型),ChannelOutbound(输出[写]类型) 。注释:head,tail这两个Handler是Pipeline自带的Handler。

Netty源码系列 之 ChannelPipeline & IO处理回顾 源码_第43张图片

4.channel.writeAndFlush() :从tail尾部这一Handler往前找,一直找到第一个ChannelOutBoundHandler 进行写输出处理

ctx.writeAndFlush():从当前Handler往前找,一直找到第一个ChannelOutBoundHandler 进行写输出处理

5.每一个ChannelHandler(无论输入还是输出,只要是存在于Pipeline管道中的),这些Handler都存在于ChannelContext中。ChannelContext提供了ByteBuf的内存分配器,Handler事件传播功能等。

ChannelPipeline的创建

1.创建NioServerSocketChannel或NioSocketChannel时

Netty源码系列 之 ChannelPipeline & IO处理回顾 源码_第44张图片

2.

Netty源码系列 之 ChannelPipeline & IO处理回顾 源码_第45张图片

3.初始化ChannelPipeline管道,Pipeline管道中默认带有head,tail这两个内置的Handler。

Netty源码系列 之 ChannelPipeline & IO处理回顾 源码_第46张图片

  • 除了每一个ChannelPipeline默认自带的Handler:head,tail之外,我还可以手工Pipeline.addLast(xxxHandler)添加自定义的Handler
package com.messi.netty_source_03.Test06;

import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import io.netty.channel.Channel;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.handler.logging.LoggingHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.net.InetSocketAddress;

public class MyNettyClient1 {
    private static final Logger log = LoggerFactory.getLogger(MyNettyClient1.class);

    public static void main(String[] args) throws InterruptedException {
        log.debug("myNettyClientStarter------");
        EventLoopGroup eventLoopGroup = new NioEventLoopGroup();
        Bootstrap bootstrap = new Bootstrap();
        bootstrap.channel(NioSocketChannel.class);
        Bootstrap group = bootstrap.group(eventLoopGroup);//32 ---> 1 IO操作 31线程
        bootstrap.handler(new ChannelInitializer() {
            @Override
            protected void initChannel(NioSocketChannel ch) throws Exception {
                ch.pipeline().addLast(new LoggingHandler());
                ch.pipeline().addLast(new StringEncoder());
            }
        });
        ByteBuf byteBuf = ByteBufAllocator.DEFAULT.directBuffer(10);
        Channel channel = bootstrap.connect(new InetSocketAddress(8000)).sync().channel();
        //半包?粘包?  1  0
        //粘包 --->ByteBuf(1024) ---> socket 65535 --- server
//        channel.writeAndFlush("sunshuai\nxiaohei\nxiaojr\n");
        channel.writeAndFlush("sunshuaixiaohei666sunshuaixiaohei666\n");

    }
}
package com.messi.netty_source_03.Test06;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.LineBasedFrameDecoder;
import io.netty.handler.logging.LoggingHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class MyNettyServer1 {


    private static final Logger log = LoggerFactory.getLogger(MyNettyServer1.class);

    public static void main(String[] args) {

        ServerBootstrap serverBootstrap = new ServerBootstrap();
        serverBootstrap.channel(NioServerSocketChannel.class);
        serverBootstrap.group(new NioEventLoopGroup());
        DefaultEventLoopGroup defaultEventLoopGroup = new DefaultEventLoopGroup();
        //接受socket缓冲区大小 等同于 滑动窗口的初始值 65535
        //serverBootstrap.option(ChannelOption.SO_RCVBUF, 100);
        //netty创建ByteBuf时 执行大小 默认1024 child ScoketChannel相关
        serverBootstrap.childOption(ChannelOption.RCVBUF_ALLOCATOR,new AdaptiveRecvByteBufAllocator(16,32,1024));
        serverBootstrap.childHandler(new ChannelInitializer() {
            @Override
            //
            protected void initChannel(NioSocketChannel ch) throws Exception {
                ChannelPipeline pipeline = ch.pipeline();
                //sunshuai\ni love you\n
                //xxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n
                //最大长度 指的是 如果超过最大长度,还没有发现分隔符,不处理。
                pipeline.addLast(defaultEventLoopGroup,"lineBased",new LineBasedFrameDecoder(1024));
                pipeline.addLast(new LoggingHandler());

            }
        });
        //
        serverBootstrap.bind(8000);
    }
}

debug追踪一下源码:【debug方式启动Server服务端,完成bind后再以运行的方式启动Client客户端】

1.pipeline.addLast添加自定义的Handler处理器类

Netty源码系列 之 ChannelPipeline & IO处理回顾 源码_第47张图片

(1)defaultEventLoopGroup:该参数传递的是额外创建的线程组。如果不传递该线程组呢?如果不传递该线程组,那么该Handler是由处理IO事件的线程去处理。但是假设说该自定义的Handler处理的逻辑过长,导致线程资源占用时间过久,那么是不是处理IO的线程资源就紧张了?对吧。所以我们额外补充了一组线程:defaultEventLoopGroup来用作该自定义Handler的逻辑处理。但是一定要显示配置呀。

defaultEventLoopGroup线程组和普通的Thread线程是一样的,只不过使用DefaultEventLoopGroup更加完美的和Netty体系相融合,共用同一份代码。

(2)lineBased:显示指定的Handler的名字。如果不指定,默认情况下Handler的名字为"类名#0"。比如这个Handler的name为:LineBasedFrameDecoder#0

(3)new LineBasedFrameDecoder(xxx):该参数传递的就是要添加到ChannelPipeline的Handler类啦

2.在客户端连接上服务端后,服务端会分配给客户端一个对应的NioSocketChannel,在NioSocketChannel初始化阶段会完成ChannelPipeline的创建和对应Handler的addLast添加【细致过程见之前的总结】

Netty源码系列 之 ChannelPipeline & IO处理回顾 源码_第48张图片

3.

Netty源码系列 之 ChannelPipeline & IO处理回顾 源码_第49张图片

4.checkMultiplicity方法:

当客户端连接数变多后,如果同一个Handler是非@Sharable 且 之前其他线程addLast添加过,那么直接抛出异常。避免了线程并发安全问题。

Netty源码系列 之 ChannelPipeline & IO处理回顾 源码_第50张图片

5.

Handler内部其实封装的有Context,Context真正的去封装一系列的参数:

Netty源码系列 之 ChannelPipeline & IO处理回顾 源码_第51张图片

Netty源码系列 之 ChannelPipeline & IO处理回顾 源码_第52张图片

Netty源码系列 之 ChannelPipeline & IO处理回顾 源码_第53张图片

filterName方法:

Netty源码系列 之 ChannelPipeline & IO处理回顾 源码_第54张图片

generateName方法:

构造名字name的时,啥时候容易会出现名字重复呢?

Netty源码系列 之 ChannelPipeline & IO处理回顾 源码_第55张图片

匿名内部类的情况很容易造成名字重复,因为匿名内部类都是一样的,为ChannelInbound HandlerAdapter,如果添加多个,会造成生成的名字重复,如果造成名字重复怎么办?

下面是解决方案:

如果说检测到name名字重复,假设重复的名字为ABCServer#0,那么重新生成的名字为ABCServer#1,但是会循环判断重新生成的名字是否还是重复的,直到最终context0(newName)==null退出循环为止。

Netty源码系列 之 ChannelPipeline & IO处理回顾 源码_第56张图片

context0方法:

Netty源码系列 之 ChannelPipeline & IO处理回顾 源码_第57张图片

6.addLast0方法:

Netty源码系列 之 ChannelPipeline & IO处理回顾 源码_第58张图片

7.callHandlerAddedInEventLoop(newCtx, executor)方法:

由于该Handler的线程组执行器使用的是自定义创建的DefaultEventLoopGroup,所以可能会之前IO事件处理线程组NioEventLoopGroup处理逻辑代码不太一样。其实DefaultEventLoopGroup更像是一种简化,因为Handler的逻辑处理视作是一种普通任务task的run执行,所以只把这部分的逻辑从NioEventLoop中抽离出来即可。

Netty源码系列 之 ChannelPipeline & IO处理回顾 源码_第59张图片

Netty源码系列 之 ChannelPipeline & IO处理回顾 源码_第60张图片

Netty源码系列 之 ChannelPipeline & IO处理回顾 源码_第61张图片

Netty源码系列 之 ChannelPipeline & IO处理回顾 源码_第62张图片

Netty源码系列 之 ChannelPipeline & IO处理回顾 源码_第63张图片

关键是这一个task的处理:

Netty源码系列 之 ChannelPipeline & IO处理回顾 源码_第64张图片

Netty源码系列 之 ChannelPipeline & IO处理回顾 源码_第65张图片

这不就和之前的逻辑重合到一起啦:

Netty源码系列 之 ChannelPipeline & IO处理回顾 源码_第66张图片

Inbound(输入Handler)所对应的事件传播

Inbound事件都对应有哪些?

Netty源码系列 之 ChannelPipeline & IO处理回顾 源码_第67张图片

其实说是事件,实际上各个事件方法也就是Handler的生命周期回调方法。当Handler执行到生命周期的某个步骤后,Handler就可以回调执行某一个事件方法。比如说:当创建完Channel管道后(完成生命周期中创建管道的阶段),那么就会回调ChannelActive这一方法。当Channel有输入流入时(完成生命周期中读入数据的阶段),就会回调ChannelRead这一方法。

Netty源码系列 之 ChannelPipeline & IO处理回顾 源码_第68张图片

图中所有的Handler:head,h1,h2,tail 都拥有输入Handler所对应的Inboud事件方法

Netty源码系列 之 ChannelPipeline & IO处理回顾 源码_第69张图片

head和tail所对应的Handler(Context)继承Inbound和Outbound,所以这俩具有输入,输出所对应的所有事件方法。

Inbound输入事件是通过什么api在多个Handler之间进行传播的?

ChannelPipeline通过一个双向链表的数据结构把多个Handler进行链接维护起来,但是msg数据,事件是如何在多个Handler之间传递的呢?是通过两类的API:1.ctx.fireChannelxxx(); 2.super.fireChannelxxx()

如果要传递channelActive事件,那么调用的方法就是:

1.ctx.fireChannelActive(); 2.super.fireChannelActive()

以channelRead为例:

channelRead事件的源头是什么?

NioByteUnsafe类的read方法--->pipeline.fireChannelRead()

debug流程如下:

1.

Netty源码系列 之 ChannelPipeline & IO处理回顾 源码_第70张图片

2.第一个Handler是HeadContext

Netty源码系列 之 ChannelPipeline & IO处理回顾 源码_第71张图片

3.

Netty源码系列 之 ChannelPipeline & IO处理回顾 源码_第72张图片

4.

Netty源码系列 之 ChannelPipeline & IO处理回顾 源码_第73张图片

5.

Netty源码系列 之 ChannelPipeline & IO处理回顾 源码_第74张图片

6.

从Head头handler开始往尾部方向去找,找到第一个发现的输入Inbound-Handler

本次找到的下一个输入handler为ServerBootstrapAcceptor#0

Netty源码系列 之 ChannelPipeline & IO处理回顾 源码_第75张图片

Netty源码系列 之 ChannelPipeline & IO处理回顾 源码_第76张图片

7.执行ServerBootstrapAcceptor#0这一输入handler,同理前面的流程即可。

Netty源码系列 之 ChannelPipeline & IO处理回顾 源码_第77张图片

Netty源码系列 之 ChannelPipeline & IO处理回顾 源码_第78张图片

8.

Netty源码系列 之 ChannelPipeline & IO处理回顾 源码_第79张图片

9.

注册完后,就会开始真正的读数据通信。过程很复杂,之前总结过,这里不再总结。参考之前的总结笔记去debug吧。

读数据通信时,首先是到HeadHandler#0,然后会把数据传递给LineBaseFrameDecoder这一封帧解码器去解码数据,并且解决半包粘包的问题。

下面简单记录一下:

首先会到HeadHandler#0:

Netty源码系列 之 ChannelPipeline & IO处理回顾 源码_第80张图片

接着会传递给LineBaseFrameDecoder所对应的Handler:

Netty源码系列 之 ChannelPipeline & IO处理回顾 源码_第81张图片

找到LineBaseFrameDecoder所对应的Handler去执行

Netty源码系列 之 ChannelPipeline & IO处理回顾 源码_第82张图片

Netty源码系列 之 ChannelPipeline & IO处理回顾 源码_第83张图片

【又回到解码器的流程啦,熟悉吧,之前总结过】

Netty源码系列 之 ChannelPipeline & IO处理回顾 源码_第84张图片

这个过程:在建立C-S连接,完成NioSocketChannel管道的注册后,开始read读通信操作,首先输入的数据会经过HeadHandler#0,其次读入的数据会传递给LineBasedFrameDecoder这一我们配置的解码Handler,无论是哪一个Handler都是回调其Handler重写的channelRead方法。在LineBasedFrameDecoder对应的Handler中,我们完成数据的封帧操作,解决半包粘包问题,并且完成解码操作。

封帧完毕后,把每一条完整的消息数据再一次通过fireChannelRead传递给下一个Handler:

Netty源码系列 之 ChannelPipeline & IO处理回顾 源码_第85张图片

Netty源码系列 之 ChannelPipeline & IO处理回顾 源码_第86张图片

会找到LoggingHandler#0完成对完整消息数据的控制台输出:

Netty源码系列 之 ChannelPipeline & IO处理回顾 源码_第87张图片

Netty源码系列 之 ChannelPipeline & IO处理回顾 源码_第88张图片

控制台输出:

Netty源码系列 之 ChannelPipeline & IO处理回顾 源码_第89张图片

Netty源码系列 之 ChannelPipeline & IO处理回顾 源码_第90张图片

Netty源码系列 之 ChannelPipeline & IO处理回顾 源码_第91张图片

Netty源码系列 之 ChannelPipeline & IO处理回顾 源码_第92张图片

Netty源码系列 之 ChannelPipeline & IO处理回顾 源码_第93张图片

onUnhandledInboundMessage方法:主要是完成ByteBuf资源的释放和循环再利用。如果不及时释放,那么内存溢出不是梦哈。

以下是释放ByteBuf内存的底层方法:

Netty源码系列 之 ChannelPipeline & IO处理回顾 源码_第94张图片

等所有Handler执行完毕后,NioEventLoop会再一次陷入阻塞等待:

Netty源码系列 之 ChannelPipeline & IO处理回顾 源码_第95张图片

Outbound(输出Handler)所对应的事件传播【包含AbstractUnsafe.write的源码流程,比之前更加深化了,必看】

Outbound事件都对应有哪些?

Netty源码系列 之 ChannelPipeline & IO处理回顾 源码_第96张图片

Outbound事件的传播的源头:

1.channel.writeAndFlush():

从tail这一Handler从后往前去找,直到找到第一个出现的Outbound-Handler

2.ctx.writeAndFlush():

从当前触发该操作的Handler往前寻找,直到找到第一个出现的Outbound-Handler

  • 测试案例
package com.messi.netty_source_03.Test06;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.LineBasedFrameDecoder;
import io.netty.handler.logging.LoggingHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class MyNettyServer1 {


    private static final Logger log = LoggerFactory.getLogger(MyNettyServer1.class);

    public static void main(String[] args) {

        ServerBootstrap serverBootstrap = new ServerBootstrap();
        serverBootstrap.channel(NioServerSocketChannel.class);
        serverBootstrap.group(new NioEventLoopGroup());
        DefaultEventLoopGroup defaultEventLoopGroup = new DefaultEventLoopGroup();
        //接受socket缓冲区大小 等同于 滑动窗口的初始值 65535
        //serverBootstrap.option(ChannelOption.SO_RCVBUF, 100);
        //netty创建ByteBuf时 执行大小 默认1024 child ScoketChannel相关
        serverBootstrap.childOption(ChannelOption.RCVBUF_ALLOCATOR,new AdaptiveRecvByteBufAllocator(16,32,1024));
        serverBootstrap.childHandler(new ChannelInitializer() {
            @Override
            //
            protected void initChannel(NioSocketChannel ch) throws Exception {
                ChannelPipeline pipeline = ch.pipeline();
                //sunshuai\ni love you\n
                //xxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n
                //最大长度 指的是 如果超过最大长度,还没有发现分隔符,不处理。
                pipeline.addLast(defaultEventLoopGroup,"lineBased",new LineBasedFrameDecoder(1024));
                pipeline.addLast(new LoggingHandler());

            }
        });
        //
        serverBootstrap.bind(8000);
    }
}
package com.messi.netty_source_03.Test06;

import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.handler.logging.LoggingHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.net.InetSocketAddress;

public class MyNettyClient1 {
    private static final Logger log = LoggerFactory.getLogger(MyNettyClient1.class);

    public static void main(String[] args) throws InterruptedException {
        log.debug("myNettyClientStarter------");
        EventLoopGroup eventLoopGroup = new NioEventLoopGroup();
        Bootstrap bootstrap = new Bootstrap();
        bootstrap.channel(NioSocketChannel.class);
        Bootstrap group = bootstrap.group(eventLoopGroup);//32 ---> 1 IO操作 31线程
        bootstrap.handler(new ChannelInitializer() {
            @Override
            protected void initChannel(NioSocketChannel ch) throws Exception {
                ch.pipeline().addLast(new LoggingHandler());
                ch.pipeline().addLast(new StringEncoder());
                ch.pipeline().addLast(new ChannelInboundHandlerAdapter(){
                    @Override
                    public void channelActive(ChannelHandlerContext ctx) throws Exception {
                        if(ctx.channel().isWritable()) {
                            ctx.writeAndFlush("sunshuaixiaohei666sunshuaixiaohei666\n");
                        }
                    }
                });
            }
        });
        Channel channel = bootstrap.connect(new InetSocketAddress(8000)).sync().channel();
        //半包?粘包?  1  0
        //粘包 --->ByteBuf(1024) ---> socket 65535 --- server
//        channel.writeAndFlush("sunshuai\nxiaohei\nxiaojr\n");
//        channel.writeAndFlush("sunshuaixiaohei666sunshuaixiaohei666\n");

    }
}
  • 测试

1.

Netty源码系列 之 ChannelPipeline & IO处理回顾 源码_第97张图片

2.省略很多步骤

3.从write写的源头开始debug追踪

Netty源码系列 之 ChannelPipeline & IO处理回顾 源码_第98张图片

4.

Netty源码系列 之 ChannelPipeline & IO处理回顾 源码_第99张图片

5.

Netty源码系列 之 ChannelPipeline & IO处理回顾 源码_第100张图片

findContextOutbound方法:

从当前触发该操作的Handler往前寻找,直到找到第一个出现的Outbound-Handler。当前找到的handler为StringEncoder,该handler是对写出的数据进行编码操作,编码转换成ByteBuf格式。实际上编码这个过程就是把消息数据写到应用层缓冲区ByteBuf(实际上封装的是ByteBuffer,最终底层还是写到ByteBuffer中)

Netty源码系列 之 ChannelPipeline & IO处理回顾 源码_第101张图片

6.

Netty源码系列 之 ChannelPipeline & IO处理回顾 源码_第102张图片

Netty源码系列 之 ChannelPipeline & IO处理回顾 源码_第103张图片

7.

Netty源码系列 之 ChannelPipeline & IO处理回顾 源码_第104张图片

这一过程称之为编码Encoder:

Netty源码系列 之 ChannelPipeline & IO处理回顾 源码_第105张图片

Netty源码系列 之 ChannelPipeline & IO处理回顾 源码_第106张图片

8.继续向后依次唤醒pipeline双向链表的每一个Handler

Netty源码系列 之 ChannelPipeline & IO处理回顾 源码_第107张图片

Netty源码系列 之 ChannelPipeline & IO处理回顾 源码_第108张图片

Netty源码系列 之 ChannelPipeline & IO处理回顾 源码_第109张图片

Netty源码系列 之 ChannelPipeline & IO处理回顾 源码_第110张图片

9.唤醒LoggingHandler#0

Netty源码系列 之 ChannelPipeline & IO处理回顾 源码_第111张图片

Netty源码系列 之 ChannelPipeline & IO处理回顾 源码_第112张图片

10.

Netty源码系列 之 ChannelPipeline & IO处理回顾 源码_第113张图片

这一次向前找到HeadContext(Handler)

Netty源码系列 之 ChannelPipeline & IO处理回顾 源码_第114张图片

11.

Netty源码系列 之 ChannelPipeline & IO处理回顾 源码_第115张图片

12.当HeadContext执行的write操作,才是真正的unsafe.write,直接把ByteBuf中存储封装的消息数据写出到OutboundBuffer,OutboundBuffer是由一个双向链表结构,链表的每一个元素为Entry类型,msg消息数据封装到该Entry中,Entry还会封装一些其他的元数据信息。此时数据状态为unflush

Netty源码系列 之 ChannelPipeline & IO处理回顾 源码_第116张图片

filterOutboundMessage方法:

Netty源码系列 之 ChannelPipeline & IO处理回顾 源码_第117张图片

Netty源码系列 之 ChannelPipeline & IO处理回顾 源码_第118张图片

Entry.newInstance方法:

Entry是ChannelOutboundBuffer的一个内部类,为什么设置成一个内部类?因为Entry只在该类中使用,所以创建成一个内部类。Entry中封装的有ByteBuf类型的msg消息数据。还有一系列关于消息数据的元数据信息

Netty源码系列 之 ChannelPipeline & IO处理回顾 源码_第119张图片

incrementPendingOutboundBytes方法:

如果累计写出的消息数据大小超过了高水位线,那么设置为Unwritable不可写状态。

12.flush操作:

HeadContext的flush操作才是真正的把应用层缓冲区ChannelOutboundBuffer的数据写出到socket-send缓冲区。

以下过程同理write,都是会一个个迭代遍历所有Handler

直到最终找到Head-Handler,HeadHandler(Context)会把应用层缓冲区的数据真正的写到socket-send缓冲区

Netty源码系列 之 ChannelPipeline & IO处理回顾 源码_第120张图片

Netty源码系列 之 ChannelPipeline & IO处理回顾 源码_第121张图片

Netty源码系列 之 ChannelPipeline & IO处理回顾 源码_第122张图片

Netty源码系列 之 ChannelPipeline & IO处理回顾 源码_第123张图片

LoggingHandler处理完flush操作后,会继续往前传递寻找下一个写出-Handler执行flush操作:

Netty源码系列 之 ChannelPipeline & IO处理回顾 源码_第124张图片

找到HeadContext#0:

Netty源码系列 之 ChannelPipeline & IO处理回顾 源码_第125张图片

Netty源码系列 之 ChannelPipeline & IO处理回顾 源码_第126张图片

真正的AbstractUnsafe.flush:

Netty源码系列 之 ChannelPipeline & IO处理回顾 源码_第127张图片

Netty源码系列 之 ChannelPipeline & IO处理回顾 源码_第128张图片

flush0方法:

Netty源码系列 之 ChannelPipeline & IO处理回顾 源码_第129张图片


doWrite方法:

Netty源码系列 之 ChannelPipeline & IO处理回顾 源码_第130张图片

Netty源码系列 之 ChannelPipeline & IO处理回顾 源码_第131张图片

((NioSocketChannelConfig) config).getMaxBytesPerGatheringWrite():

获取到socket-send缓冲区的大小

in.nioBuffers(1024, maxBytesPerGatheringWrite):

通过ChannelOutboundBuffer(in),把封装在该buffer中的所有Entry转换一个个对应的ByteBuffer。但是最大不超过1024个。为什么要把一个个转换成一个个对应的ByteBuffer?因为为了便于操作,如果把所有的Entry都放在一个ByteBuffer中,也可以,但是需要操作读写指针的难度将会大幅度提升。

nioBuffers方法中使用到的internalNioBuffer方法:

这个方法就是提取出ByteBuf中的ByteBuffer对象

Netty源码系列 之 ChannelPipeline & IO处理回顾 源码_第132张图片

incompleteWrite(true):

当localWrittenBytes <= 0时,说明没有写出数据到socket-send缓冲区,此时需要一个兜底操作:让SelectionKey监控WRITE写事件,使得下一次写操作时可以及时监控到。

adjustMaxBytesPerGatheringWrite(attemptedBytes, localWrittenBytes, maxBytesPerGatheringWrite)

实时的调整socket-send内核缓冲区大小

in.removeBytes(localWrittenBytes):

把已经发出去的数据从ChannelOutboundBuffer中更新删除

int writeSpinCount = config().getWriteSpinCount():

无论是读read还是write写,都会有一个次数限制,意思就是,占用线程资源的read或write操作最多连续执行16次。如果16次还没有完成读或写的任务时,线程就不会再被IO所占用,而是会切换到执行非IO的task,这是为了防止非IO-task被阻塞时间过长,可能非IO-task相对很快就执行完毕了,所以netty做了一个这样的设计。但是话说回来,16次循环IO,如果缓冲区设置的够大,可以实现16GB的IO转换,一般都是可以IO传输完毕的。

final int localWrittenBytes = ch.write(buffer):

拿到转换好的一个个的ByteBuffer数据,通过SocketChannel管道写出到socket-send内核缓冲区

你可能感兴趣的:(Netty源码,java,后端,netty)