【Netty】Netty框架实现及学习

学习内容主要基于B站UP主,青空の霞光的视频,加上自己的理解。

Netty框架可以解决NIO框架的问题

Netty是由JBOSS提供的一个开源Java网络编程框架,针对Java的nio包进行了再次封装,提供了更加强大、稳定的功能和更易于使用的api。

可以用于游戏服务器,微服务之间的远程调用(比如Dubbo的RPC框架,包括最新的SpringWebFlux框架,也抛弃了内嵌的Tomcat而使用Netty作为通信框架)

导包

<dependency>
    <groupId>io.netty</groupId>
    <artifactId>netty-all</artifactId>
    <version>4.1.76.Final</version>
</dependency>

ByteBuf

Netty没有使用ByteBuffer来进行数据装载,而是自定义了一个ByteBuf

  • 写操作完成后无需进行flip()翻转
  • 具有比ByteBuffer更快的响应速度
  • 动态扩容
public abstract class AbstractByteBuf extends ByteBuf {
    ...
    int readerIndex;  //index分为读和写两个指针
    int writerIndex;
    private int markedReaderIndex; //mark操作也分为两种
    private int markedWriterIndex;
    private int maxCapacity; //最大容量,可以动态扩容

没写入依次,writeIndex向后移动一位;每读取依次,readerIndex向后移动一位。

readerIndex不能大于writeIndex,这样就不需要翻转了

在这里插入图片描述
readerIndexwriteIndex之间的部分就是可读的内容,而writeIndexcapacity都是可写的部分

public static void main(String[] args) {
    //创建一个初始容量为10的ByteBuf缓冲区,Unpooled是用于快速生成ByteBuf的工具类
    //ByteBuf有池化和非池化两种,区别在于对内存的复用
    ByteBuf buf = Unpooled.buffer(10);
    System.out.println("初始状态:" + Arrays.toString(buf.array()));

    buf.writeInt(-888888888); //写入一个Int数据
    System.out.println("写入int后:" + Arrays.toString(buf.array()));

    //无需反转,直接读取short数据
    buf.readShort();
    System.out.println("读取short后:" + Arrays.toString(buf.array()));

    //丢弃操作:将当前的可读部分内容丢到最前面,并且读写指针向前移动丢弃的距离
    buf.discardReadBytes();
    System.out.println("丢弃之后:" + Arrays.toString(buf.array()));

    //清空操作,清空后读写指针都归零
    buf.clear();
    System.out.println("清空之后:" + Arrays.toString(buf.array()));
}

初始状态:[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
写入int:[-53, 4, -95, -56, 0, 0, 0, 0, 0, 0]
读取short:[-53, 4, -95, -56, 0, 0, 0, 0, 0, 0]
丢弃之后:[-95, -56, -95, -56, 0, 0, 0, 0, 0, 0]
清空之后:[-95, -56, -95, -56, 0, 0, 0, 0, 0, 0]

划分操作

public static void main(String[] args) {
    ByteBuf buf = Unpooled.wrappedBuffer("abcdefg".getBytes());

    //复制数据,copiedBuffer()会将数据拷贝到一个新的缓冲区中
    buf.readByte(); //读取一个字节

    //现在指针位于1,进行划分
    ByteBuf slice = buf.slice();

    //得到划分出来的ByteBuf的偏移地址
    System.out.println(slice.arrayOffset());
    System.out.println(Arrays.toString(slice.array()));
}

1
[97, 98, 99, 100, 101, 102, 103]

动态扩容

在写入一个超出当前容量的数据时,会进行动态扩容,扩容从64开始,之后每次触发都会×2

public static void main(String[] args) {
    //申请一个容量为10的缓冲区
    ByteBuf buf = Unpooled.buffer(10);
    System.out.println(buf.capacity()); //10

    //写一个字符
    buf.writeCharSequence("睡觉的事件", StandardCharsets.UTF_8);
    System.out.println(buf.capacity()); //64
}

如果不想扩容,可以指定最大容量

public static void main(String[] args) {
    //申请一个容量为10的缓冲区
    ByteBuf buf = Unpooled.buffer(10, 10);
    System.out.println(buf.capacity());

    //写一个字符
    buf.writeCharSequence("睡觉的事件", StandardCharsets.UTF_8);
    System.out.println(buf.capacity());
}

10
Exception in thread "main" java.lang.IndexOutOfBoundsException: writerIndex(0) + minWritableBytes(15) exceeds maxCapacity(10): UnpooledByteBufAllocator$InstrumentedUnpooledUnsafeHeapByteBuf(ridx: 0, widx: 0, cap: 10/10)

缓冲区的三种实现模式

  • 堆缓冲区模式:申请数组
  • 直接缓冲区模式:使用堆外内存
  • 复合缓冲区模式
//直接缓冲区
ByteBuf buf = Unpooled.directBuffer(10);

复合模式,可以任意地拼凑组合其他缓冲区

在这里插入图片描述
比如要将两个缓冲区组合的内容进行操作,不用单独创建一个新的缓冲区了,而是将其直接进行拼接操作。相当于是作为多个缓冲区组合的视图

public static void main(String[] args) {
    //创建一个符合缓冲区
    CompositeByteBuf buf = Unpooled.compositeBuffer();

    buf.addComponent(Unpooled.copiedBuffer("abc".getBytes()));
    buf.addComponent(Unpooled.copiedBuffer("def".getBytes()));

    for (int i = 0; i < buf.capacity(); i++) {
        System.out.print((char) buf.getByte(i) + " ");
    }
}

a b c d e f 

池化缓冲区和非池化缓冲区的区别

Unpooled工具类如何创建buffer

public final class Unpooled {
    private static final ByteBufAllocator ALLOC; //内部使用ByteBufAllocator对象
    public static final ByteOrder BIG_ENDIAN;
    public static final ByteOrder LITTLE_ENDIAN;
    public static final ByteBuf EMPTY_BUFFER;

    public static ByteBuf buffer() {
        return ALLOC.heapBuffer(); //缓冲区创建操作依赖ByteBufAllocator
    }

    static {
        //ByteBufAllocator在静态代码块中进行指定,实际上真正的实现类是UnpooledByteBufAllocator
        ALLOC = UnpooledByteBufAllocator.DEFAULT;
        BIG_ENDIAN = ByteOrder.BIG_ENDIAN;
        LITTLE_ENDIAN = ByteOrder.LITTLE_ENDIAN;
        EMPTY_BUFFER = ALLOC.buffer(0, 0); //空缓冲区容量和最大容量都为0

        assert EMPTY_BUFFER instanceof EmptyByteBuf : "EMPTY_BUFFER must be an EmptyByteBuf.";

    }
}

ByteBufAllocator是负责分配缓冲区的。

它有两个具体实现类:

  • UnpooledByteBufAllocator:非池化缓冲区生成器
  • PooledByteBufAllocator:池化缓冲区生成器。利用了池化思想,将缓冲区通过设置内存池来进行内存块的复用,这样就不用频繁的进行内存的申请,尤其是使用堆外内存时,避免多次重复通过底层malloc()函数系统调用申请内存造成性能损失
public static void main(String[] args) {
    //创建一个池化缓冲区生成器
    PooledByteBufAllocator allocator = PooledByteBufAllocator.DEFAULT;
    //申请容量为10的直接缓冲区
    ByteBuf buf = allocator.directBuffer(10);
    buf.writeChar('T');
    System.out.println(buf.readChar()); //T
    buf.release();

    //重新申请一个同样大小的直接缓冲区
    ByteBuf buf2 = allocator.directBuffer(10);
    System.out.println(buf == buf2); //true  
}

PooledByteBufAllocator是将ByteBuf实例放在池中进行复用。

使用完一个缓冲区,将资源释放,再申请同样大小的缓冲区时,会直接得到之前申请好的缓冲区

零拷贝

零拷贝的三种方案:

  1. 使用虚拟内存
    让内核空间和用户空间的虚拟地址指向同一个物理地址,这样就相当于是共用了这一块区域,就不用拷贝了
    在这里插入图片描述
  2. 使用mmap/write内存映射
    将内核空间中的缓存直接映射到用户空间缓存,比如在NIO中使用的MappedByteBuffer,就是直接作为映射存在,当需要将数据发送到Socket缓冲区时,直接在内核空间中进行操作就行了
    在这里插入图片描述
  3. 使用sendfile方式
    可以直接告诉内核要把哪个文件数据拷贝到Socket上,直接在内核空间中一步到位
    在这里插入图片描述
    比如在NIO中使用的transforTo()方法

Netty工作模型

Netty的工作模型是以 主从Reactor多线程模型 为基础的

在这里插入图片描述
主从Reactor中,所有用户端需要连接到主Reactor完成Accept操作后,其他的操作由从Reactor完成

在Netty中:

  • Netty抽象出两组线程池BossGroupWorkGroup
    • BossGroup专门负责接受客户端的连接
    • WorkGroup专门负责读写
  • BossGroupWorkGroup都是使用EventLoop来进行事件监听的,整个Netty也是使用事件驱动来运作的,比如当前客户端已经准备好读写、建立连接时,都会进行事件通知,就像多路复用那样。
    • EventLoop:事件循环,一个循环,不断的进行事件通知
    • 如果有多个EventLoop,会存放在EventLoopGroup中,EventLoopGroup就是BossGroupWorkGroup的具体实现
  • BossGroup之后,会正常将SocketChannel绑定到WorkGroup中的其中一个EventLoop上,进行后续的读写操作监听。

创建Netty服务器

public static void main(String[] args) {
    //使用NioEventLoopGroup实现类创建bossGroup和workerGroup
    NioEventLoopGroup bossGroup = new NioEventLoopGroup();
    NioEventLoopGroup workerGroup = new NioEventLoopGroup();

    //创建服务端启动引导类
    ServerBootstrap bootstrap = new ServerBootstrap();
    //链式
    bootstrap
        .group(bossGroup, workerGroup)  //指定事件循环组
        .channel(NioServerSocketChannel.class)  //指定为NIO的ServerSocketChannel
        .childHandler(new ChannelInitializer<SocketChannel>() { //这里的SocketChannel是Netty的
            @Override
            protected void initChannel(SocketChannel channel) throws Exception {
                //获取流水线,当需要处理客户端的数据时,像流水线一样处理,流水线中有很多Handler
                channel.pipeline().addLast(new ChannelInboundHandlerAdapter() { //添加一个Handler
                    //ctx是上下文,msg是收到的消息,默认为ByteBuf形式
                    @Override
                    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
                        super.channelRead(ctx, msg);
                        ByteBuf buf = (ByteBuf) msg; //类型转换
                        System.out.println(Thread.currentThread().getName() + ">> 接收到客户端发送的数据" + buf.toString(StandardCharsets.UTF_8));
                        //通过上下文可以直接发送数据回去,注意要writeAndFlush才能让客户端立即收到
                        ctx.writeAndFlush(Unpooled.wrappedBuffer("已收到".getBytes()));
                    }
                });
            }
        });
    //绑定端口,启动
    bootstrap.bind(8080);
}

Netty中的Channel

通道Channel可以用来进行数据的传输,并且通道支持双向传输

public interface Channel extends AttributeMap, ChannelOutboundInvoker, Comparable<Channel> {
    ChannelId id(); //通道ID

    EventLoop eventLoop(); //通道所属的EventLoop,因为一个Channel在他的生命周期内只能注册到一个EventLoop中

    Channel parent(); //Channel是具有层级关系的,这里返回的是 父Channel

    ChannelConfig config();

    boolean isOpen(); //通道当前状态

    boolean isRegistered();

    boolean isActive();

    ChannelMetadata metadata(); //通道相关信息

    SocketAddress localAddress();

    SocketAddress remoteAddress();

    ChannelFuture closeFuture(); //关闭通道,

    boolean isWritable();

    long bytesBeforeUnwritable();

    long bytesBeforeWritable();

    Unsafe unsafe();

    ChannelPipeline pipeline(); //流水线

    ByteBufAllocator alloc(); //可以直接从Channel拿到ByteBufAllocator的实例,来分配ByteBuf

    Channel read();

    Channel flush(); //刷新

Netty中的Channel相比NIO功能多了许多。

  • 所有的IO操作都是异步的,并不是在当前线程同步运行,方法调用后就直接返回了
  • 如何获取操作结果?
    • 基于ChannelFuture,用来观察异步操作是否完成

Channel的父接口ChannelOutboundInvoker定义了大量IO操作

public interface ChannelOutboundInvoker { //通道出站调用(包含大量网络出站操作,比如写)
    //Socket绑定、连接、断开、关闭等操作
    ChannelFuture bind(SocketAddress var1);

    ChannelFuture connect(SocketAddress var1);

    ChannelFuture connect(SocketAddress var1, SocketAddress var2);

    ChannelFuture disconnect();

    ChannelFuture close();

    ChannelFuture deregister();
	//ChannelPromise是ChannelFuture的增强版
    ChannelFuture bind(SocketAddress var1, ChannelPromise var2);

    ChannelFuture connect(SocketAddress var1, ChannelPromise var2);

    ChannelFuture connect(SocketAddress var1, SocketAddress var2, ChannelPromise var3);

    ChannelFuture disconnect(ChannelPromise var1);

    ChannelFuture close(ChannelPromise var1);

    ChannelFuture deregister(ChannelPromise var1);

    ChannelOutboundInvoker read();
	//写操作,返回ChannelFuture,而不是直接给结果
    ChannelFuture write(Object var1);

    ChannelFuture write(Object var1, ChannelPromise var2);

    ChannelOutboundInvoker flush();

    ChannelFuture writeAndFlush(Object var1, ChannelPromise var2);

    ChannelFuture writeAndFlush(Object var1);

    ChannelPromise newPromise();

    ChannelProgressivePromise newProgressivePromise();

    ChannelFuture newSucceededFuture();

    ChannelFuture newFailedFuture(Throwable var1);

    ChannelPromise voidPromise();
}

还实现了AttributeMap,类似于Session,添加一些属性

public interface AttributeMap {
    <T> Attribute<T> attr(AttributeKey<T> var1);

    <T> boolean hasAttr(AttributeKey<T> var1);
}

有了通道之后,要怎么操作?将需要处理的操作放在ChannelHandler中,ChannelHandler充当了所有入站和出站数据的应用程序逻辑的容器,实际上就是之前Reactor模式中的Handler,用来处理读写操作

通过ChannelPipeline来进行流水线处理

ChannelHandler顶层接口:

public interface ChannelHandler {
    //当ChannelHandler被添加到流水线时调用
    void handlerAdded(ChannelHandlerContext var1) throws Exception;
	//当ChannelHandler从流水线中移出时调用
    void handlerRemoved(ChannelHandlerContext var1) throws Exception;

    /** @deprecated */
    @Deprecated
    void exceptionCaught(ChannelHandlerContext var1, Throwable var2) throws Exception;

    @Inherited
    @Documented
    @Target({ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    public @interface Sharable {
    }
}

下一级接口ChannelInboundHandler

//用于处理入站相关事件
public interface ChannelInboundHandler extends ChannelHandler {
    //当Channel已经注册到自己的EventLoop上时调用(一个Channel只会被注册到一个EventLoop上,注册后,才会在发生对应时间时被通知
    void channelRegistered(ChannelHandlerContext var1) throws Exception;
	//在EventLoop上取消注册时
    void channelUnregistered(ChannelHandlerContext var1) throws Exception;
	//Channel处于活跃状态时别调用,此时Channel已经连接/绑定,且已经就绪
    void channelActive(ChannelHandlerContext var1) throws Exception;
	//不活跃了
    void channelInactive(ChannelHandlerContext var1) throws Exception;
	//当Channel读取数据时被调用,数据包被自动包装成了一个Object(默认是ByteBuf)
    void channelRead(ChannelHandlerContext var1, Object var2) throws Exception;
	//上一个读取操作完成后调用
    void channelReadComplete(ChannelHandlerContext var1) throws Exception;

    void userEventTriggered(ChannelHandlerContext var1, Object var2) throws Exception;
	//Channel的可写状态发生改变时调用
    void channelWritabilityChanged(ChannelHandlerContext var1) throws Exception;
	//出现异常时调用
    void exceptionCaught(ChannelHandlerContext var1, Throwable var2) throws Exception;
}

ChannelInboundHandlerAdapter实际上就是对这些方法实现的抽象类,相比直接用接口,可以只重写我们需要的方法,没有重写的方法会默认向流水线下一个ChannelHander发送

public class TestChannelHandler extends ChannelInboundHandlerAdapter {

    @Override
    public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
        System.out.println("channelRegistered");
    }

    @Override
    public void channelUnregistered(ChannelHandlerContext ctx) throws Exception {
        System.out.println("channelUnRegistered");
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        System.out.println("channelActive");
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        System.out.println("channelInactive");
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        ByteBuf buf = (ByteBuf) msg;
        System.out.println(Thread.currentThread().getName() + ">>接收到客户端发送的数据" + buf.toString(StandardCharsets.UTF_8));
        //使用ctx.alloc()生成缓冲区
        ByteBuf back = ctx.alloc().buffer();
        back.writeCharSequence("已收到", StandardCharsets.UTF_8);
        ctx.writeAndFlush(back);
        System.out.println("channelRead");
    }

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        System.out.println("channelReadComplete");
    }

    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        System.out.println("userEventTriggered");
    }

    @Override
    public void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception {
        System.out.println("channelWritabilityChanged");
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        System.out.println("exceptionCaught" + cause);
    }
}

服务器端使用TestChannelHandler

public class Server {

    public static void main(String[] args) {
        //使用NioEventLoopGroup实现类创建bossGroup和workerGroup
        NioEventLoopGroup bossGroup = new NioEventLoopGroup();
        NioEventLoopGroup workerGroup = new NioEventLoopGroup();

        //创建服务端启动引导类
        ServerBootstrap bootstrap = new ServerBootstrap();
        //链式
        bootstrap
                .group(bossGroup, workerGroup)  //指定事件循环组
                .channel(NioServerSocketChannel.class)  //指定为NIO的ServerSocketChannel
                .childHandler(new ChannelInitializer<SocketChannel>() { //这里的SocketChannel是Netty的
                    @Override
                    protected void initChannel(SocketChannel channel) throws Exception {
                        //获取流水线,当需要处理客户端的数据时,像流水线一样处理,流水线中有很多Handler
                        channel.pipeline().addLast(new TestChannelHandler());
                    }
                });
        //绑定端口,启动
        bootstrap.bind(8080);
    }
}

在这里插入图片描述在这里插入图片描述
可以看到ChannelInboundHandler的生命周期:

  1. Channel注册成功
  2. 变为可用状态
  3. 等待客户端来数据
  4. 当客户端主动断开连接,会再次触发ChannelReadComplete
  5. 然后不可用
  6. 最后取消注册

接下来是ChannelPipeline,每个Channel都对应一个ChannelPipeline(在Channel初始化时就被创建了)
在这里插入图片描述
就像一条流水线,上面有多个Handler(包括入站和出站),整条流水线上的两端还有两个默认的处理器(用于预置操作和后续操作,比如释放资源等)。

如果现在希望创建两个入站ChannelHandler,一个用于接收请求并处理,还有一个用于处理当前接收请求过程中出现的异常:

public static void main(String[] args) {
    //使用NioEventLoopGroup实现类创建bossGroup和workerGroup
    NioEventLoopGroup bossGroup = new NioEventLoopGroup();
    NioEventLoopGroup workerGroup = new NioEventLoopGroup();

    //创建服务端启动引导类
    ServerBootstrap bootstrap = new ServerBootstrap();
    //链式
    bootstrap
        .group(bossGroup, workerGroup)  //指定事件循环组
        .channel(NioServerSocketChannel.class)  //指定为NIO的ServerSocketChannel
        .childHandler(new ChannelInitializer<SocketChannel>() { //这里的SocketChannel是Netty的
            @Override
            protected void initChannel(SocketChannel channel) throws Exception {
                //获取流水线,当需要处理客户端的数据时,像流水线一样处理,流水线中有很多Handler
                channel.pipeline()
                    .addLast(new ChannelInboundHandlerAdapter() { //第一个用于消息接收
                        @Override
                        public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
                            ByteBuf buf = (ByteBuf) msg;
                            System.out.println("接收客户端发送的数据:" + buf.toString(StandardCharsets.UTF_8));
                            throw new RuntimeException("发生异常");
                        }
                    })
                    .addLast(new ChannelInboundHandlerAdapter() { //第二个用于处理异常
                        @Override
                        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
                            System.out.println("异常处理:" + cause);
                        }
                    });
            }
        });
    //绑定端口,启动
    bootstrap.bind(8080);
}

那么他是如何运作的?

public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
    //通过ChannelHandlerContext来向下传递
    //ChannelHandlerContext是在Handler添加进Pipeline中时就被创建的
    ctx.fireExceptionCaught(cause);
}

比如现在需要将一个消息在两个Handler中进行处理

.childHandler(new ChannelInitializer<SocketChannel>() { //这里的SocketChannel是Netty的
    @Override
    protected void initChannel(SocketChannel channel) throws Exception {
        //添加两个Handler
        channel.pipeline()
            .addLast(new ChannelInboundHandlerAdapter() {
                @Override
                public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
                    ByteBuf buf = (ByteBuf) msg;
                    System.out.println("1-接收客户端发送的数据:" + buf.toString(StandardCharsets.UTF_8));
                    ctx.fireChannelRead(msg); //通过ChannelHandlerContext
                }
            })
            .addLast(new ChannelInboundHandlerAdapter() {
                @Override
                public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
                    ByteBuf buf = (ByteBuf) msg;
                    System.out.println("2-接收到客户端发送的数据:" + buf.toString(StandardCharsets.UTF_8));
                }
            });
    }
});

出站操作也可以加入到Pipeline上的,通过ChannelOutboundHandlerAdapter完成

出站操作应该在入站操作前面,当使用ChannelOutboundHandlerAdapterwrite方法时,是从流水线的当前位置倒着往前找下一个ChannelOutboundHandlerAdapter

而之前使用的ChannelInboundHandlerAdapter是从前往后找下一个。

如果使用的是Channel的write方法,那么会从整个流水线的最后倒着往前找ChannelOutboundHandlerAdapter

@Override
protected void initChannel(SocketChannel channel) throws Exception {
    //添加两个Handler
    channel.pipeline()
        .addLast(new ChannelOutboundHandlerAdapter() {
            //出站操作应该在入站操作的前面

            @Override
            public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
                System.out.println(msg); //写的内容
                //将其转为ByteBuf
                ctx.writeAndFlush(Unpooled.wrappedBuffer(msg.toString().getBytes()))
            }
        })
        .addLast(new ChannelInboundHandlerAdapter() {
            @Override
            public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
                ByteBuf buf = (ByteBuf) msg;
                System.out.println("1-接收到客户端发送的数据:" + buf.toString(StandardCharsets.UTF_8));
                ctx.fireChannelRead(msg);
            }
        })
        .addLast(new ChannelInboundHandlerAdapter() {
            @Override
            public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
                ByteBuf buf = (ByteBuf) msg;
                System.out.println("2-接收到客户端发送的数据:" + buf.toString(StandardCharsets.UTF_8));
                ctx.writeAndFlush("从当前位置往后"); //从当前位置往后
                // ctx.channel().writeAndFlush("从整个后面");
            }
        });
}

EventLoop和任务调度

EventLoop本质上就是一个事件等待/处理线程

在这里插入图片描述
一个EventLoopGroup,包含很多个EventLoop,每创建一个连接,就需要绑定到一个EventLoop上,之后EventLoop就会开始监听这个连接,而一个EventLoop可以同时监听很多个Channel,跟之前的Selector一样

public static void main(String[] args) {
    //限制以下线程数
    EventLoopGroup bossGroup = new NioEventLoopGroup(), workerGroup = new NioEventLoopGroup(1);
    ServerBootstrap bootstrap = new ServerBootstrap();

    bootstrap
        .group(bossGroup, workerGroup) //指定事件循环组
        .channel(NioServerSocketChannel.class)  //指定为NIO的ServerSocketChannel
        .childHandler(new ChannelInitializer<SocketChannel>() {
            @Override
            protected void initChannel(SocketChannel channel) throws Exception {
                channel.pipeline()
                    .addLast(new ChannelInboundHandlerAdapter() {
                        @Override
                        public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
                            ByteBuf buf = (ByteBuf) msg;
                            System.out.println("接收客户端发送的数据:" + buf.toString(StandardCharsets.UTF_8));
                            //卡10秒假装在处理
                            Thread.sleep(10000);
                            ctx.writeAndFlush(Unpooled.wrappedBuffer("已收到".getBytes()));
                        }
                    });
            }
        });
    bootstrap.bind(8080);
}

这里卡住了,就没法处理EventLoop绑定的其他Channel了,所以这里创建一个普通的EventLoop来专门处理读写之外的任务

public static void main(String[] args) {
    //限制以下线程数
    EventLoopGroup bossGroup = new NioEventLoopGroup(), workerGroup = new NioEventLoopGroup(1);
    //处理其他任务
    DefaultEventLoop handlerGroup = new DefaultEventLoop();

    ServerBootstrap bootstrap = new ServerBootstrap();

    bootstrap
        .group(bossGroup, workerGroup) //指定事件循环组
        .channel(NioServerSocketChannel.class)  //指定为NIO的ServerSocketChannel
        .childHandler(new ChannelInitializer<SocketChannel>() {
            @Override
            protected void initChannel(SocketChannel channel) throws Exception {
                channel.pipeline()
                    .addLast(new ChannelInboundHandlerAdapter() {
                        @Override
                        public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
                            ByteBuf buf = (ByteBuf) msg;
                            System.out.println("接收客户端发送的数据:" + buf.toString(StandardCharsets.UTF_8));
                            //使用handlerGroup来处理 耗时10秒钟的任务
                            handlerGroup.submit(() -> {
                                //由于继承自ScheduledExecutorServer,直接提交任务即可,相当于线程池
                                try {
                                    //卡10秒假装在处理
                                    Thread.sleep(10000);
                                } catch (InterruptedException e) {
                                    throw new RuntimeException(e);
                                }
                                ctx.writeAndFlush(Unpooled.wrappedBuffer("已收到".getBytes()));
                            });
                        }
                    });
            }
        });
    bootstrap.bind(8080);
}

也可以写成一条流水线,进一步的将EventLoop利用起来

@Override
protected void initChannel(SocketChannel channel) throws Exception {
    channel.pipeline()
        .addLast(new ChannelInboundHandlerAdapter() {
            @Override
            public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
                ByteBuf buf = (ByteBuf) msg;
                System.out.println("接收客户端发送的数据:" + buf.toString(StandardCharsets.UTF_8));
                ctx.fireChannelRead(msg);
            }
        })
        .addLast(handlerGroup,new ChannelInboundHandlerAdapter() { //添加时,可以直接指定使用哪个EventLoopGroup
            @Override
            public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
                ByteBuf buf = (ByteBuf) msg;
                System.out.println("接收客户端发送的数据:" + buf.toString(StandardCharsets.UTF_8));
                try {
                    //卡10秒假装在处理
                    Thread.sleep(10000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                ctx.writeAndFlush(Unpooled.wrappedBuffer("已收到".getBytes()));
            }
        });
}

按照前面服务端的方式,将Netty客户端也写了

public class Client {

    public static void main(String[] args) {
        //客户端使用Bootstrap来启动
        Bootstrap bootstrap = new Bootstrap();

        bootstrap
                .group(new NioEventLoopGroup()) //客户端一个EventLoop就行,用于处理发回来的数据
                .channel(NioSocketChannel.class) //客户端使用NioSocketChannel
                .handler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel channel) throws Exception {
                        channel.pipeline()
                                .addLast(new ChannelInboundHandlerAdapter() {
                                    @Override
                                    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
                                        ByteBuf buf = (ByteBuf) msg;
                                        System.out.println(">>接收服务端发送的数据:" + buf.toString(StandardCharsets.UTF_8));
                                    }
                                });
                    }
                });

        //连接后拿到对应的Channel对象
        Channel channel = bootstrap.connect("localhost", 8080).channel();
        //上面的操作都是异步的,调用后会往下走,
        //客户端给服务器端发送的数据
        try(Scanner scanner = new Scanner(System.in)) {
            while (true) {
                System.out.println("要发送给服务器端的内容:");
                String text = scanner.nextLine();
                if (text.isEmpty()) {
                    continue;
                }
                //通过Channel对象发送数据
                channel.writeAndFlush(Unpooled.wrappedBuffer(text.getBytes()));
            }
        }
    }
}

Future和Promise

ChannelFuture,Netty中的Channel的相关操作都是异步进行的,并不是在当前线程同步执行,无法立即的到结果,如果需要结果,就必须利用到Futrue

public interface ChannelFuture extends Future<Void> {
    //直接获取此任务的Channel
    Channel channel();
	//当任务完成时,会直接执行GenericFutureListener的任务,注意执行的位置也是在EventLoop中
    ChannelFuture addListener(GenericFutureListener<? extends Future<? super Void>> var1);

    ChannelFuture addListeners(GenericFutureListener<? extends Future<? super Void>>... var1);

    ChannelFuture removeListener(GenericFutureListener<? extends Future<? super Void>> var1);

    ChannelFuture removeListeners(GenericFutureListener<? extends Future<? super Void>>... var1);
	//当前线程同步等待异步任务完成,失败抛出异常
    ChannelFuture sync() throws InterruptedException;
	//同上,但无法响应中断
    ChannelFuture syncUninterruptibly();
	//同上,但任务终端不会抛出异常,需要手动判断
    ChannelFuture await() throws InterruptedException;

    ChannelFuture awaitUninterruptibly();
	//返回类型是否为void
    boolean isVoid();
}

此接口继承自Netty中的Future接口

public interface Future<V> extends java.util.concurrent.Future<V> {
    boolean isSuccess(); //用于判断任务是否执行成功

    boolean isCancellable();

    Throwable cause(); //获取导致任务失败的异常

	...

    V getNow(); //立即获取结果,如还未产生结果,得到Null

    boolean cancel(boolean var1); //取消任务
}

Channel的操作都是异步完成的,直接返回一个ChannelFuture,比如Channel的write操作

@Override
protected void initChannel(SocketChannel channel) throws Exception {
    channel.pipeline()
        .addLast(new ChannelInboundHandlerAdapter() {
            @Override
            public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
                ByteBuf buf = (ByteBuf) msg;
                System.out.println("接收到客户端发送的数据:" + buf.toString(StandardCharsets.UTF_8));
                ChannelFuture future = ctx.writeAndFlush(Unpooled.wrappedBuffer("已收到".getBytes()));
                //通过ChannelFuture获取相关的信息
                System.out.println("任务完成状态:" + future.isDone());
            }
        });
}

如果服务器端的启动比较慢,且需要等到服务器端启动完成后才能做一些事情,要怎么办?有两种方案:

  1. 第一种方案是直接让当前线程同步等待异步任务完成,可以使用sync()方法,这样当前线程会一直阻塞直到任务结束

            ChannelFuture future = bootstrap.bind(8080);
            future.sync();
            System.out.println("服务端启动完成" + future.isDone());
    
  2. 添加一个监听器,等待任务完成时通知

            ChannelFuture future = bootstrap.bind(8080);
            future.addListener((ChannelFutureListener) channelFuture -> {
                System.out.println("服务端启动完成" + future.isDone());
            });
    

包括客户端的关闭也是异步操作的

.handler(new ChannelInitializer<SocketChannel>() {
    @Override
    protected void initChannel(SocketChannel channel) throws Exception {
        channel.pipeline()
            .addLast(new ChannelInboundHandlerAdapter() {
                @Override
                public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
                    ByteBuf buf = (ByteBuf) msg;
                    System.out.println(">>接收服务端发送的数据:" + buf.toString(StandardCharsets.UTF_8));
                }
            });
    }
});

//连接后拿到对应的Channel对象
Channel channel = bootstrap.connect("localhost", 8080).channel();
//上面的操作都是异步的,调用后会往下走,
//客户端给服务器端发送的数据
try(Scanner scanner = new Scanner(System.in)) {
    while (true) {
        System.out.println("要发送给服务器端的内容:");
        String text = scanner.nextLine();
        if (text.isEmpty()) {
            continue;
        }
        if (text.equals("exit")) { //输入exit就退出
            ChannelFuture future = channel.close();
            future.sync(); //等待Channel完全关闭
            break;
        }
        //通过Channel对象发送数据
        channel.writeAndFlush(Unpooled.wrappedBuffer(text.getBytes()));
    }
} finally {
    loopGroup.shutdownGracefully(); //优雅退出
}

Promise接口,支持手动设定成功和失败的结果:

public interface Promise<V> extends Future<V> {
    Promise<V> setSuccess(V var1);

    boolean trySuccess(V var1); //手动设定成功

    Promise<V> setFailure(Throwable var1);

    boolean tryFailure(Throwable var1); //失败

    boolean setUncancellable();
	//其他与Future一样
    Promise<V> addListener(GenericFutureListener<? extends Future<? super V>> var1);

    Promise<V> addListeners(GenericFutureListener<? extends Future<? super V>>... var1);

    Promise<V> removeListener(GenericFutureListener<? extends Future<? super V>> var1);

    Promise<V> removeListeners(GenericFutureListener<? extends Future<? super V>>... var1);

    Promise<V> await() throws InterruptedException;

    Promise<V> awaitUninterruptibly();

    Promise<V> sync() throws InterruptedException;

    Promise<V> syncUninterruptibly();
}

测试以下

public static void main(String[] args) throws ExecutionException, InterruptedException {
    DefaultPromise<Object> promise = new DefaultPromise<>(new DefaultEventLoop());
    System.out.println(promise.isSuccess()); //false
    promise.setSuccess("hah");
    System.out.println(promise.isSuccess()); //true
    System.out.println(promise.get()); //hah
}

手动指定成功状态,包括ChannelOutboundInvoker中的一些基本操作

.addLast(new ChannelInboundHandlerAdapter() {
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        ByteBuf buf = (ByteBuf) msg;
        System.out.println("接收到客户端发送的数据:" + buf.toString(StandardCharsets.UTF_8));

        DefaultChannelPromise promise = new DefaultChannelPromise(channel);
        System.out.println(promise.isSuccess());
        ctx.writeAndFlush(Unpooled.wrappedBuffer("已收到".getBytes()), promise);
        promise.sync(); //同步等待
        System.out.println(promise.isSuccess()); //判断是否成功
    }
});

编码器和解码器

Netty内置的一些编码器和解码器

之前的数据发送和接收都是使用ByteBuf形式传输。

能否在处理数据之前,过滤一次,将数据转换成想要的类型,也可以将数据进行转换,这就用到了编码器和解码器

字符串

直接添加一个字符串编码器到流水线中

//解码器本质上也是一个ChannelInboundHandlerAdapter,用于处理入站请求
.addLast(new StringDecoder()) //当客户端发来的数据只是简单的字符串转换的ByteBuf时,直接使用StringDecoder即可
    .addLast(new ChannelInboundHandlerAdapter() {
        @Override
        public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
            //ByteBuf buf = (ByteBuf) msg;
            //经过StringDecoder转换后,msg就直接是一个字符串
            System.out.println(">>接收服务端发送的数据:" + msg);
        }
    });

在这里插入图片描述

他是继承自MessageToMessageDecoder,将传入的Message转为另一种类型。

可以自定义

public class TestDecoder extends MessageToMessageDecoder<ByteBuf> {

    @Override
    protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List<Object> list) throws Exception {
        System.out.println("数据已收到,正在解码..");
        String text = byteBuf.toString(StandardCharsets.UTF_8);
        //将解析的数据丢进list中
        list.add(text);
        list.add(text+"2"); //一条消息被解码成三条消息
        list.add(text+"3");
    }
}

后面的Handler会依次对三条数据进行处理

编码器

有了解码器处理发过来的数据,要发出去的数据也需要编码器来进行处理

//解码器本质上也是一个ChannelInboundHandlerAdapter,用于处理入站请求
.addLast(new StringDecoder()) //当客户端发来的数据只是简单的字符串转换的ByteBuf时,直接使用StringDecoder即可
    .addLast(new ChannelInboundHandlerAdapter() {
        @Override
        public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
            //ByteBuf buf = (ByteBuf) msg;
            //经过StringDecoder转换后,msg就直接是一个字符串
            System.out.println(">>接收服务端发送的数据:" + msg);
        }
    })
    .addLast(new StringEncoder());

StringEncoder本质上就是一个ChannelOutboundHandlerAdapter

在这里插入图片描述
当然还有 编解码器ChannelDuplexHandler
在这里插入图片描述
同时实现编码器和解码器,继承MessageToMessageCodec类即可

public class StringCodec extends MessageToMessageCodec<ByteBuf, String> {
    @Override
    protected void encode(ChannelHandlerContext channelHandlerContext, String s, List<Object> list) throws Exception {
        list.add(Unpooled.wrappedBuffer(s.getBytes()));
    }

    @Override
    protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List<Object> list) throws Exception {
        list.add(byteBuf.toString(StandardCharsets.UTF_8));
    }
}

拆包和粘包问题,也可以使用解码器解决

  • 定长数据包
.addLast(new FixedLengthFrameDecoder(10)) 
  • 指定分隔符
.addLast(new DelimiterBasedFrameDecoder(1024, Unpooled.wrappedBuffer("!".getBytes())))
  • 头部添加长度信息
.addLast(new LengthFieldBasedFrameDecoder(1024, 0 ,4))

实现Http协议通信

支持HTTP协议的编码器和解码器

.addLast(new HttpRequestDecoder()) //HTTP请求解码器
    .addLast(new ChannelInboundHandlerAdapter() {
        @Override
        public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
            System.out.println("收到客户端的数据:" + msg.getClass()); //看类型
            //收到浏览器请求后,返回一个响应
            //HTTP版本为1.1,状态码为ok
            DefaultFullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK);
            //直接向响应内容中写入数据
            response.content().writeCharSequence("Hello wordld", StandardCharsets.UTF_8);
            ctx.channel().writeAndFlush(response); //发送响应
            ctx.channel().close(); //HTTP请求是一次性的
        }
    })
    .addLast(new HttpResponseEncoder());

使用浏览器访问

在这里插入图片描述
浏览器成功接收服务器响应,然后打印了以下类型

在这里插入图片描述
可以看到请求和数据是分开的,将他们整合成一个

.addLast(new HttpRequestDecoder()) //HTTP请求解码器
    .addLast(new HttpObjectAggregator(Integer.MAX_VALUE)) //聚合器,将内容聚合成一个FullHttpRequest,参数是最大内容长度
    .addLast(new ChannelInboundHandlerAdapter() {
        @Override
        public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
            System.out.println("收到客户端的数据:" + msg.getClass()); //看类型
            FullHttpRequest request = (FullHttpRequest) msg;
            System.out.println("浏览器请求路径:" + request.uri()); //获取请求相关信息
            //收到浏览器请求后,返回一个响应
            //HTTP版本为1.1,状态码为ok
            DefaultFullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK);
            //直接向响应内容中写入数据
            response.content().writeCharSequence("Hello world", StandardCharsets.UTF_8);
            ctx.channel().writeAndFlush(response); //发送响应
            ctx.channel().close(); //HTTP请求是一次性的
        }
    })
    .addLast(new HttpResponseEncoder());

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dtNJyRHN-1689004709202)(image/image-20230710213450252.png)]

其他内置Handler

打印日志

.addLast(new HttpRequestDecoder()) //HTTP请求解码器
    .addLast(new HttpObjectAggregator(Integer.MAX_VALUE)) //聚合器,将内容聚合成一个FullHttpRequest,参数是最大内容长度
    .addLast(new LoggingHandler(LogLevel.INFO)) //添加要给日志Handler

对IP地址进行过滤,比如不希望某些IP地址连接服务器

.addLast(new HttpRequestDecoder()) //HTTP请求解码器
    .addLast(new HttpObjectAggregator(Integer.MAX_VALUE)) //聚合器,将内容聚合成一个FullHttpRequest,参数是最大内容长度
    .addLast(new RuleBasedIpFilter(new IpFilterRule() {
        @Override
        public boolean matches(InetSocketAddress inetSocketAddress) {
            //进行匹配,匹配失败返回false
            //若匹配失败,根据下面的类型决定干什么
            return !inetSocketAddress.getHostName().equals("127.0.0.1");
        }

        @Override
        public IpFilterRuleType ruleType() {
            return IpFilterRuleType.REJECT; //拒绝连接
        }
    }))

对长期处于空闲的连接进行处理:

.addLast(new StringDecoder())
    .addLast(new IdleStateHandler(10, 10, 0)) //IdleStateHandler能侦测空闲连接
    //第一个参数表示:连接多少秒没有读操作
    //第二个是写操作
    //第三个是读写操作,0表示禁用
    .addLast(new ChannelInboundHandlerAdapter() {
        @Override
        public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
            System.out.println("收到客户端数据"+msg);
            ctx.channel().writeAndFlush("已收到");
        }

        @Override
        public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
            if (evt instanceof IdleStateEvent) {
                IdleStateEvent event = (IdleStateEvent) evt;
                if (event.state() == IdleState.WRITER_IDLE) {
                    System.out.println("111");
                } else if (event.state() == IdleState.READER_IDLE) {
                    System.out.println("222");
                }
            }
        }
    });

启动流程源码

Netty是如何启动以及进行数据处理的

public static void main(String[] args) throws InterruptedException {
    //使用NioEventLoopGroup实现类创建bossGroup和workerGroup
    NioEventLoopGroup bossGroup = new NioEventLoopGroup();
    NioEventLoopGroup workerGroup = new NioEventLoopGroup();

    //创建服务端启动引导类
    ServerBootstrap bootstrap = new ServerBootstrap();
    //链式
    bootstrap
        .group(bossGroup, workerGroup)  //指定事件循环组
        .channel(NioServerSocketChannel.class)  //指定为NIO的ServerSocketChannel
        .childHandler(new ChannelInitializer<SocketChannel>() { //这里的SocketChannel是Netty的
            @Override
            protected void initChannel(SocketChannel channel) throws Exception {
                channel.pipeline()
                    .addLast(new StringDecoder())
                    .addLast(new IdleStateHandler(10, 10, 0)) //IdleStateHandler能侦测空闲连接
                    //第一个参数表示:连接多少秒没有读操作
                    //第二个是写操作
                    //第三个是读写操作,0表示禁用
                    .addLast(new ChannelInboundHandlerAdapter() {
                        @Override
                        public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
                            System.out.println("收到客户端数据"+msg);
                            ctx.channel().writeAndFlush("已收到");
                        }

                        @Override
                        public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
                            if (evt instanceof IdleStateEvent) {
                                IdleStateEvent event = (IdleStateEvent) evt;
                                if (event.state() == IdleState.WRITER_IDLE) {
                                    System.out.println("111");
                                } else if (event.state() == IdleState.READER_IDLE) {
                                    System.out.println("222");
                                }
                            }
                        }
                    });
            }
        });
    //绑定端口,启动
    ChannelFuture future = bootstrap.bind(8080);
    future.addListener((ChannelFutureListener) channelFuture -> {
        System.out.println("服务端启动完成" + future.isDone());
    });
}

首先,整个服务端是在bind之后启动的

public ChannelFuture bind(int inetPort) {
    // 转换成InetSocketAddress对象
    return this.bind(new InetSocketAddress(inetPort));
}

之后,调用其他绑定方法

对应.group(bossGroup, workerGroup) //指定事件循环组

public ChannelFuture bind(SocketAddress localAddress) {
    this.validate(); //验证,看EventLoopGroup和Channel指定了没有
    return this.doBind((SocketAddress)ObjectUtil.checkNotNull(localAddress, "localAddress"));
}

之后doBind

private ChannelFuture doBind(final SocketAddress localAddress) {
    //初始化然后注册
    final ChannelFuture regFuture = this.initAndRegister();
    //....
    final Channel channel = regFuture.channel();
    if (regFuture.cause() != null) {
        return regFuture;
    } else if (regFuture.isDone()) {
        ChannelPromise promise = channel.newPromise();
        doBind0(regFuture, channel, localAddress, promise);
        return promise;
    } else {
        final PendingRegistrationPromise promise = new PendingRegistrationPromise(channel);
        regFuture.addListener(new ChannelFutureListener() {
            public void operationComplete(ChannelFuture future) throws Exception {
                Throwable cause = future.cause();
                if (cause != null) {
                    promise.setFailure(cause);
                } else {
                    promise.registered();
                    AbstractBootstrap.doBind0(regFuture, channel, localAddress, promise);
                }

            }
        });
        return promise;
    }
}

如何注册:doBind->initAndRegister

对应.channel(NioServerSocketChannel.class),指定通道

final ChannelFuture initAndRegister() {
    Channel channel = null;

    try {
        //通过channelFactory创建新的Channel,即开始设定的NioServerSocketChannel
        channel = this.channelFactory.newChannel();
        //对创建好的NioServerSocketChannel进行初始化
        this.init(channel);
    ...
        
	//将通道注册到bossGroup中的一个EventLoop中
    ChannelFuture regFuture = this.config().group().register(channel);
	...
    return regFuture;
}

对创建好的ServerSocketChannel进行初始化:doBind->initAndRegister->init

void init(Channel channel) {
    setChannelOptions(channel, this.newOptionsArray(), logger);
    setAttributes(channel, this.newAttributesArray());
    ChannelPipeline p = channel.pipeline();
	...
    //在流水线上添加一个Handler,在Handler初始化的时候向EventLoop中提交一个任务,将ServerBootstrapAcceptor添加到流水线上
    //这样ServerSocketChannel在客户端连接时就能Accept了
    p.addLast(new ChannelHandler[]{new ChannelInitializer<Channel>() {
        public void initChannel(final Channel ch) {
            final ChannelPipeline pipeline = ch.pipeline();
            ChannelHandler handler = ServerBootstrap.this.config.handler();
            if (handler != null) {
                pipeline.addLast(new ChannelHandler[]{handler});
            }
			//向eventLoop提交任务
            ch.eventLoop().execute(new Runnable() {
                public void run() {
                    //这里提交一个任务,将ServerBootstrapAcceptor添加到ServerSocketChannel的pipeline中
                    pipeline.addLast(new ChannelHandler[]{new ServerBootstrapAcceptor(ch, currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs)});
                }
            });
        }
    }});
}

ServerBootstrapAcceptor怎么处理的

对应前面的

.childHandler(new ChannelInitializer() { //这里的SocketChannel是Netty的
@Override
protected void initChannel(SocketChannel channel) throws Exception {
channel.pipeline()
.addLast(new StringDecoder().addLast(…)

将childHandler的内容添加到流水线中

注册到EventLoop中(NIO底层将其注册到Selector中,Selector才可以去监听它的读写事件)

//当底层NIO的ServerSocketChannel的Selector有ACCEPT事件到达时,NIOEventLoop会接收客户端连接,创建SocketChannel,并触发channelRead回调
public void channelRead(ChannelHandlerContext ctx, Object msg) {
    //msg是Accept连接创建后的Channel对象
    final Channel child = (Channel)msg;
    //将childHandler添加到新创建的客户端连接的流水线中
    child.pipeline().addLast(new ChannelHandler[]{this.childHandler});
    AbstractBootstrap.setChannelOptions(child, this.childOptions, ServerBootstrap.logger);
    AbstractBootstrap.setAttributes(child, this.childAttrs);

    try {
        //直接向workGroup中的EventLoop注册新创建好的客户端连接Channel,等待读写事件
        this.childGroup.register(child).addListener(new ChannelFutureListener() {
            //异步操作后,如果注册没有成功,就强制关闭这个Channel
            public void operationComplete(ChannelFuture future) throws Exception {
                if (!future.isSuccess()) {
                    ServerBootstrap.ServerBootstrapAcceptor.forceClose(child, future.cause());
                }

            }
        });
    } catch (Throwable var5) {
        forceClose(child, var5);
    }

}

所以实际上就是前面的主从Reactor多线程模型

初始化完成后,进行注册,在NIO时需要将Channel注册到对应的Selector才可以开始选择:

回到前面的doBind->initAndRegister->register

final ChannelFuture initAndRegister() {
    Channel channel = null;

    try {
        //通过channelFactory创建新的Channel,即开始设定的NioServerSocketChannel
        channel = this.channelFactory.newChannel();
        //对创建好的NioServerSocketChannel进行初始化
        this.init(channel);
    ...
        
	//将通道注册到bossGroup中的一个EventLoop中
    ChannelFuture regFuture = this.config().group().register(channel);
	...
    return regFuture;
}
public ChannelFuture register(Channel channel) {
    //转换成ChannelPromise继续
    return this.register((ChannelPromise)(new DefaultChannelPromise(channel, this)));
}
public ChannelFuture register(ChannelPromise promise) {
    ObjectUtil.checkNotNull(promise, "promise");
    //调用Channel的Unsafe接口实现进行注册
    promise.channel().unsafe().register(this, promise);
    return promise;
}

找到unsafe的实现类Unsafe,找到里面void register(EventLoop var1, ChannelPromise var2);的实现AbstractUnsafe

public final void register(EventLoop eventLoop, final ChannelPromise promise) {
	...
        AbstractChannel.this.eventLoop = eventLoop;
        if (eventLoop.inEventLoop()) {
            //调用register0方法进行注册
            this.register0(promise);
        } 
	...
    }
}

继续register0

private void register0(ChannelPromise promise) {
    try {
		...
        boolean firstRegistration = this.neverRegistered;
        //这里开始执行AbstractNioChannel中的doRegister方法进行注册
        AbstractChannel.this.doRegister();
        this.neverRegistered = false;
        AbstractChannel.this.registered = true;
        AbstractChannel.this.pipeline.invokeHandlerAddedIfNeeded();
        this.safeSetSuccess(promise);
        AbstractChannel.this.pipeline.fireChannelRegistered();
        if (AbstractChannel.this.isActive()) {
            if (firstRegistration) {
                //关键,拿到selectionKey后继续向下传递事件
                AbstractChannel.this.pipeline.fireChannelActive();
            } else if (AbstractChannel.this.config().isAutoRead()) {
                this.beginRead();
            }
        }
		...

}

再进入到doRegister中,最后一级

protected void doRegister() throws Exception {
    boolean selected = false;

    while(true) {
        try {
            //这里真正进行了注册,javaChannel()得到NIO的Channel对象,然后调用register方法
            //这里和NIO是一样了,将Channel注册到Selector中,可以看到Selector也是EventLoop中的
            //ops参数是0,即不见听任何数据
            this.selectionKey = this.javaChannel().register(this.eventLoop().unwrappedSelector(), 0, this);
            //nio的channel.register(selector, SelectionKey.OP_ACCEPT, new Acceptor) 
            //但是这里是0,说明没有监听任何事件
            return;
        }
        ...
    }
}

返回上一级,在doRegister完成后,拿到selectionKey,此时还没有监听任何事件

接下来看下面的fireChannelActive方法,继续向下传递事件

public final ChannelPipeline fireChannelActive() {
    //传递的是流水线上的默认头节点
    AbstractChannelHandlerContext.invokeChannelActive(this.head);
    return this;
}
static void invokeChannelActive(final AbstractChannelHandlerContext next) {
    EventExecutor executor = next.executor();
    if (executor.inEventLoop()) {
        next.invokeChannelActive(); //接着向下
    } else {
        executor.execute(new Runnable() {
            public void run() {
                next.invokeChannelActive();
            }
        });
    }

}
private void invokeChannelActive() {
    if (this.invokeHandler()) {
        try {
            //调用头节点的channelActive进行处理
            ((ChannelInboundHandler)this.handler()).channelActive(this);
        } catch (Throwable var2) {
            this.invokeExceptionCaught(var2);
        }
    } else {
        this.fireChannelActive();
    }

}

具体的头节点的channelActive实现,在HeadContext

public void channelActive(ChannelHandlerContext ctx) {
    ctx.fireChannelActive();
    this.readIfIsAutoRead(); //继续向下
}
private void readIfIsAutoRead() {
    if (DefaultChannelPipeline.this.channel.config().isAutoRead()) {
        DefaultChannelPipeline.this.channel.read(); //继续向下
    }
}

一直往下点,直到走到

public void read(ChannelHandlerContext ctx) {
    //最后这里调用beginRead方法
    this.unsafe.beginRead();
}

再进去

public final void beginRead() {
    this.assertEventLoop();

    try {
        //调用AbstractNioChannel的doBeginRead方法
        AbstractChannel.this.doBeginRead();
    } catch (final Exception var2) {
        this.invokeLater(new Runnable() {
            public void run() {
                AbstractChannel.this.pipeline.fireExceptionCaught(var2);
            }
        });
        this.close(this.voidPromise());
    }

}

进去,终于找到熟悉的NIO了

protected void doBeginRead() throws Exception {
    //先拿到之前注册号的selectionKey
    SelectionKey selectionKey = this.selectionKey;
    if (selectionKey.isValid()) {
        this.readPending = true;
        int interestOps = selectionKey.interestOps(); //将监听的操作取出来
        if ((interestOps & this.readInterestOp) == 0) { //如果没有监听任何操作
            //就把readInterestOp事件进行监听
            //这里的readInterestOp实际上就是OP_ACCEPT
            selectionKey.interestOps(interestOps | this.readInterestOp);
        }

    }
}

这样,Channel在初始化完成之后也完成了底层的注册,已经可以开始等待事件了

回到上面的doBind方法的注册位置,注册完成后,基本上真个主从Reactor结构就已经出来了,还需要做什么?

private ChannelFuture doBind(final SocketAddress localAddress) {
    //此时,注册和初始化已经成功
    final ChannelFuture regFuture = this.initAndRegister();
    //....
    //由于是异步操作,通过ChannelFuture拿到对应的ServerSocketChannel对象
    final Channel channel = regFuture.channel();
    if (regFuture.cause() != null) {
        return regFuture;
    } else if (regFuture.isDone()) { //如果初始化已经完成了
        ChannelPromise promise = channel.newPromise();
        doBind0(regFuture, channel, localAddress, promise); //直接开始进一步的绑定
        return promise;
    } else {
        //如果还没完成初始化,则创建Promise继续等待任务完成
        final PendingRegistrationPromise promise = new PendingRegistrationPromise(channel);
        regFuture.addListener(new ChannelFutureListener() {
            public void operationComplete(ChannelFuture future) throws Exception {
                Throwable cause = future.cause();
                if (cause != null) {
                    promise.setFailure(cause);
                } else {
                    promise.registered();
                    AbstractBootstrap.doBind0(regFuture, channel, localAddress, promise);
                }

            }
        });
        return promise;
    }
}

最终都会走到doBind0绑定方法

private static void doBind0(final ChannelFuture regFuture, final Channel channel, final SocketAddress localAddress, final ChannelPromise promise) {
    //最后会向Channel已经注册到的EventLoop中提交一个新任务
    channel.eventLoop().execute(new Runnable() {
        public void run() {
            if (regFuture.isSuccess()) {
                //这里才是真正调用Channel底层进行绑定操作
                channel.bind(localAddress, promise).addListener(ChannelFutureListener.CLOSE_ON_FAILURE);
            } else {
                promise.setFailure(regFuture.cause());
            }

        }
    });
}

至此,服务端的启动流程结束。

NIO空轮询问题解决方法

至于之前的NIO空轮询问题,可以看看Netty的解决方法

NioEventLoop

while(true) {
    boolean var34;
    try {
        ...

                        try {
                            if (!this.hasTasks()) {
                                //首先会在这里进行Selector.select()操作,和NIO一样
                                strategy = this.select(curDeadlineNanos);
                            }
              ...
                  
            //每次唤醒都会让selectCnt自增
            ++selectCnt;
            this.cancelledKeys = 0;
            ...
                
            if (!ranTasks && strategy <= 0) {
                //判断是否会出现空轮询BUG
                if (this.unexpectedSelectorWakeup(selectCnt)) {
            ...

看看如何进行判断的

private boolean unexpectedSelectorWakeup(int selectCnt) {
    if (Thread.interrupted()) {
        if (logger.isDebugEnabled()) {
            logger.debug("Selector.select() returned prematurely because Thread.currentThread().interrupt() was called. Use NioEventLoop.shutdownGracefully() to shutdown the NioEventLoop.");
        }

        return true;
        //若selectCnt大于等于SELECTOR_AUTO_REBUILD_THRESHOLD(默认为512),则会直接重建Selector
    } else if (SELECTOR_AUTO_REBUILD_THRESHOLD > 0 && selectCnt >= SELECTOR_AUTO_REBUILD_THRESHOLD) {
        logger.warn("Selector.select() returned prematurely {} times in a row; rebuilding Selector {}.", selectCnt, this.selector);
        this.rebuildSelector(); //当前的Selector出现BUG了,会重建一个Selector
        return true;
    } else {
        return false;
    }
}

每当空轮询发生时会有专门的计数器+1,如果空轮询的次数超过了512次,就认为其触发了空轮询BUG,触发BUG后,Netty会直接重建一个Selector,将原来的Channel重新注册到新的Selector上,将旧的Selector关掉,这样就放置了无限循环

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值