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<NioSocketChannel>() {
            @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<NioSocketChannel>() {
            @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事件的触发

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

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

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

4.

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

6.初始化工作

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

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

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

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

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

ByteToMessageDecoder类:

callDecode方法:

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

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

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

  • 修改一下测试案例,测试一下半包情况下,帧解码器的处理情况
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<NioSocketChannel>() {
            @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<NioSocketChannel>() {
            @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");

    }
}

改动之处:

服务端:

客户端:

  • debug测试

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

相同的逻辑不再记录:

return null:

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

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

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

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

ChannelPipeline概念回顾

回顾ChannelPipeline相关的概念:

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

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

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

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

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

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

ChannelPipeline的创建

1.创建NioServerSocketChannel或NioSocketChannel时

2.

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

  • 除了每一个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<NioSocketChannel>() {
            @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<NioSocketChannel>() {
            @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处理器类

(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添加【细致过程见之前的总结】

3.

4.checkMultiplicity方法:

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

5.

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

filterName方法:

generateName方法:

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

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

下面是解决方案:

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

context0方法:

6.addLast0方法:

7.callHandlerAddedInEventLoop(newCtx, executor)方法:

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

关键是这一个task的处理:

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

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

Inbound事件都对应有哪些?

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

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

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.

2.第一个Handler是HeadContext

3.

4.

5.

6.

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

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

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

8.

9.

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

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

下面简单记录一下:

首先会到HeadHandler#0:

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

找到LineBaseFrameDecoder所对应的Handler去执行

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

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

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

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

控制台输出:

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

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

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

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

Outbound事件都对应有哪些?

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<NioSocketChannel>() {
            @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<NioSocketChannel>() {
            @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.

2.省略很多步骤

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

4.

5.

findContextOutbound方法:

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

6.

7.

这一过程称之为编码Encoder:

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

9.唤醒LoggingHandler#0

10.

这一次向前找到HeadContext(Handler)

11.

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

filterOutboundMessage方法:

Entry.newInstance方法:

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

incrementPendingOutboundBytes方法:

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

12.flush操作:

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

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

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

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

找到HeadContext#0:

真正的AbstractUnsafe.flush:

flush0方法:


doWrite方法:

((NioSocketChannelConfig) config).getMaxBytesPerGatheringWrite():

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

in.nioBuffers(1024, maxBytesPerGatheringWrite):

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

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

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

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内核缓冲区

  • 16
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值