Netty Channel源码分析

本文详细介绍了Netty Channel的核心概念,包括其UML结构、主要API,以及创建、初始化、注册和绑定的流程。Netty通过高度抽象的API简化了不同传输方式之间的切换。在创建过程中,涉及到了ChannelId、ChannelPipeline和Unsafe的初始化。初始化后,Channel会注册到Selector并进行绑定,这一系列操作为高效网络通信提供了基础。
摘要由CSDN通过智能技术生成

原文链接:https://wangwei.one/posts/netty-channel-source-analyse.html

前面,我们大致了解了Netty中的几个核心组件。今天我们就来先来介绍Netty的网络通信组件,用于执行网络I/O操作 —— Channel

Netty版本:4.1.30

概述

数据在网络中总是以字节的形式进行流通。我们在进行网络编程时选用何种传输方式编码(OIO、NIO等)决定了这些字节的传输方式。

在没有Netty之前,为了提升系统的并发能力,从OIO切换到NIO时,需要对代码进行大量的重构,因为相应的Java NIO 与 IO API大不相同。而Netty在这些Java原生API的基础上做了一层封装,对用户提供了高度抽象而又统一的API,从而让传输方式的切换不在变得困难,只需要直接使用即可,而不需要对整个代码进行重构。

Netty Channel UML

netty channel族如下:

NettyChannel

整个族群中,AbstractChannel 是最为关键的一个抽象类,从它继承出了AbstractNioChannel、AbstractOioChannel、AbstractEpollChannel、LocalChannel、EmbeddedChannel等类,每个类代表了不同的协议以及相应的IO模型。除了 TCP 协议以外,Netty 还支持很多其他的连接协议,并且每种协议还有 NIO(异步 IO) 和 OIO(Old-IO,即传统的阻塞 IO) 版本的区别. 不同协议不同的阻塞类型的连接都有不同的 Channel 类型与之对应。下面是一些常用的 Channel 类型:

  • NioSocketChannel:代表异步的客户端 TCP Socket 连接
  • NioServerSocketChannel:异步的服务器端 TCP Socket 连接
  • NioDatagramChannel:异步的 UDP 连接
  • NioSctpChannel:异步的客户端 Sctp 连接
  • NioSctpServerChannel:异步的 Sctp 服务器端连接
  • OioSocketChannel:同步的客户端 TCP Socket 连接
  • OioServerSocketChannel:同步的服务器端 TCP Socket 连接
  • OioDatagramChannel:同步的 UDP 连接
  • OioSctpChannel:同步的 Sctp 服务器端连接
  • OioSctpServerChannel:同步的客户端 TCP Socket 连接

Channel API

我们先来看下最顶层接口 channel 主要的API,常用的如下:

接口名 描述
eventLoop() Channel需要注册到EventLoop的多路复用器上,用于处理I/O事件,通过eventLoop()方法可以获取到Channel注册的EventLoop。EventLoop本质上就是处理网络读写事件的Reactor线程。在Netty中,它不仅仅用来处理网络事件,也可以用来执行定时任务和用户自定义NioTask等任务。
pipeline() 返回channel分配的ChannelPipeline
isActive() 判断channel是否激活。激活的意义取决于底层的传输类型。例如,一个Socket传输一旦连接到了远程节点便是活动的,而一个Datagram传输一旦被打开便是活动的
localAddress() 返回本地的socket地址
remoteAddress() 返回远程的socket地址
flush() 将之前已写的数据冲刷到底层Channel上去
write(Object msg) 请求将当前的msg通过ChannelPipeline写入到目标Channel中。注意,write操作只是将消息存入到消息发送环形数组中,并没有真正被发送,只有调用flush操作才会被写入到Channel中,发送给对方。
writeAndFlush() 等同于调用write()并接着调用flush()
metadate() 熟悉TCP协议的读者可能知道,当创建Socket的时候需要指定TCP参数,例如接收和发送的TCP缓冲区大小,TCP的超时时间。是否重用地址等。在Netty中,每个Channel对应一个物理链接,每个连接都有自己的TCP参数配置。所以,Channel会聚合一个ChannelMetadata用来对TCP参数提供元数据描述信息,通过metadata()方法就可以获取当前Channel的TCP参数配置。
read() 从当前的Channel中读取数据到第一个inbound缓冲区中,如果数据被成功读取,触发ChannelHandler.channelRead(ChannelHandlerContext,Object)事件。读取操作API调用完成后,紧接着会触发ChannelHander.channelReadComplete(ChannelHandlerContext)事件,这样业务的ChannelHandler可以决定是否需要继续读取数据。如果已经有操作请求被挂起,则后续的读操作会被忽略。
close(ChannelPromise promise) 主动关闭当前连接,通过ChannelPromise设置操作结果并进行结果通知,无论操作是否成功,都可以通过ChannelPromise获取操作结果。该操作会级联触发ChannelPipeline中所有ChannelHandler的ChannelHandler.close(ChannelHandlerContext,ChannelPromise)事件。
parent() 对于服务端Channel而言,它的父Channel为空;对于客户端Channel,它的父Channel就是创建它的ServerSocketChannel。
id() 返回ChannelId对象,ChannelId是Channel的唯一标识。

Channel创建

对Netty Channel API以及相关的类有了一个初步了解之后,接下来我们来详细了解一下在Netty的启动过程中Channel是如何创建的。服务端Channel的创建过程,主要分为四个步骤:1)Channel创建;2)Channel初始化;3)Channel注册;4)Channel绑定。

Netty Channel Process

我们以下面的代码为例进行解析:

// 创建两个线程组,专门用于网络事件的处理,Reactor线程组
// 用来接收客户端的连接,
EventLoopGroup bossGroup = new NioEventLoopGroup();
// 用来进行SocketChannel的网络读写
EventLoopGroup workGroup = new NioEventLoopGroup();

// 创建辅助启动类ServerBootstrap,并设置相关配置:
ServerBootstrap b = new ServerBootstrap();
// 设置处理Accept事件和读写操作的事件循环组
b.group(bossGroup, workGroup)
         // 配置Channel类型
        .channel(NioServerSocketChannel.class)
         // 配置监听地址
        .localAddress(new InetSocketAddress(port))
         // 设置服务器通道的选项,设置TCP属性
        .option(ChannelOption.SO_KEEPALIVE, Boolean.TRUE)
         // 设置建立连接后的客户端通道的选项
        .childOption(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000)
         // channel属性,便于保存用户自定义数据
        .attr(AttributeKey.newInstance("UserId"), "60293")
    	.handler(new LoggingHandler(LogLevel.INFO))
        // 设置子处理器,主要是用户的自定义处理器,用于处理IO网络事件
        .childHandler(new ChannelInitializer<SocketChannel>() {
   
            @Override
            public void initChannel(SocketChannel ch) throws Exception {
   
                ch.pipeline().addLast(serverHandler);
            }
        });

// 调用bind()方法绑定端口,sync()会阻塞等待处理请求。这是因为bind()方法是一个异步过程,会立即返回一个ChannelFuture对象,调用sync()会等待执行完成
ChannelFuture f = b.bind().sync();
// 获得Channel的closeFuture阻塞等待关闭,服务器Channel关闭时closeFuture会完成
f.channel().closeFuture().sync();

调用channel()接口设置 AbstractBootstrap 的成员变量 channelFactory,该变量顾名思义就是用于创建channel的工厂类。源码如下:


...

public B channel(Class<? extends C> channelClass) {
   
    if (channelClass == null) {
   
        throw new NullPointerException("channelClass");
    }
    // 创建 channelFactory
    return channelFactory(new ReflectiveChannelFactory<C>(channelClass));
}

...

public B channelFactory(ChannelFactory<? extends C> channelFactory) {
   
    if (channelFactory == null) {
   
        throw new NullPointerException("channelFactory");
    }
    
    if (this.channelFactory != null) {
   
        throw new IllegalStateException("channelFactory set already");
    }
    this.channelFactory = channelFactory;
    return (B) this;
}

...

channelFactory 设置为 ReflectiveChannelFactory ,在我们这个例子中 clazz 为 NioServerSocketChannel ,我们可以看到其中有个 newChannel() 接口,通过反射的方式来调用,这个接口的调用处我们后面会介绍到。源码如下:

// Channel工厂类
public class ReflectiveChannelFactory<T extends Channel> implements ChannelFactory<T> {
   

    private final Class<? extends T> clazz;

    public ReflectiveChannelFactory(Class<? extends T> clazz) {
   
        if (clazz == null) {
   
            throw new NullPointerException("clazz");
        }
        this.clazz = clazz;
    }
	
    @Override
    public T newChannel() {
   
        try {
   
            // 通过反射来进行常见Channel实例
            return clazz.newInstance();
        } catch (Throwable t) {
   
            thro
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值