深入研究Netty框架之Channel和Unsafe详解

Channel简介

Channel是由netty抽象出来的网络I/O操作的接口,作为Netty传输的核心,负责处理所有的I/O操作。Channel提供了一组用于传输的API,主要包括网络的读/写,客户端主动发起连接、关闭连接,服务端绑定端口,获取通讯双方的网络地址等;同时,还提供了与netty框架相关的操作,如获取channel相关联的EventLoop、pipeline等。

一个Channel可以拥有父Channel,服务端Channel的parent为空,对客户端Channel来说,它的parent就是创建它的ServerSocketChannel。当一个Channel相关的网络操作完成后,请务必调用ChannelOutboundInvoker.close()或ChannelOutboundInvoker.close(ChannelPromise)来释放所有资源,如文件句柄。

每个Channel都会被分配一个ChannelPipeline和ChannelConfig。ChannelConfig主要负责Channel的所有配置,并且支持热更新。此外,每个Channel都会绑定一个EventLoop,该通道整个生命周期内的事件都将由这个特定EventLoop负责处理。

Channel是独一无二的,所以为了保证顺序将Channel声明为Comparable的一个子接口。如果两个Channel实例返回了相同的hashcode,那么AbstractChannel中compareTo()方法的实现将会抛出一个Error。

在netty中,所有的I/O操作都是异步的,这些操作被调用将立即返回一个ChannelFuture实例,用户通过ChannelFuture实例获取操作的结果。下面主要介绍下Channel中的传输API及其作用。

Channel的API

 Channel read();

从当前Channel中读取数据到第一个inbound缓冲区中,如果读取数据,触发ChannelInboundHandler.channelRead(ChannelHandlerContext,Object)事件;当读取完成后,触发channelReadComplete事件,这样业务的ChannelHandler可以决定是否需要继续读取数据。

同时,该操作会触发outbound事件,该事件会级联触发ChannelPipeline中ChannelHandler.read(ChannelHandlerContext)方法被调用。

ChannelFuture write(Object msg);
ChannelFuture write(Object msg, ChannelPromise promise);

请求将当前的msg通过ChannelPipeline写入到目标Channel中。注意,write操作只是将消息存入到消息发送环形数组中,并没有被真正发送,只有调用flush操作才会将消息写入到Channel,发送给对方。区别在于方法2提供了ChannelPromise实例,用于设置写入操作的结果。该操作会触发outbound事件,该事件会级联触发ChannelPipeline中ChannelHandler.write(ChannelHandlerContext,msg,ChannelPromise)方法被调用。

Channel flush();

将write操作写入环形数组中的消息全部写入到Channel中,发送给通信对方。该操作会触发outbound事件,该事件会级联触发ChannelPipeline中ChannelHandler.flush(ChannelHandlerContext)方法被调用。

ChannelFuture writeAndFlush(Object msg);
ChannelFuture writeAndFlush(Object msg, ChannelPromise promise);

write操作和flush操作的组合。

ChannelFuture close();
ChannelFuture close(ChannelPromise promise);

主动关闭当前连接,close操作会触发链路关闭事件,该事件会级联触发ChannelPipeline中ChannelOutboundHandler.close(ChannelHandlerContext,ChannelPromise)方法被调用;区别在于方法2提供了ChannelPromise实例,用于设置close操作的结果,无论成功与否。

ChannelFuture bind(SocketAddress localAddress);
ChannelFuture bind(SocketAddress localAddress, ChannelPromise promise);

用于绑定指定的本地Socket地址localAddress,触发outbound事件,该事件会级联触发ChannelPipeline中ChannelHandler.bind(ChannelHandlerContext,SocketAddress,ChannelPromise)方法被调用。

ChannelFuture connect(SocketAddress remoteAddress)
ChannelFuture connect(SocketAddress remoteAddress,ChannelPromise promise);

客户端使用指定的服务器地址remoteAddress发起连接请求,如果连接由于连接超时而失败,则ChannelFuture将会失败,并出现ConnectTimeoutException。 如果由于连接被拒绝而失败,将使用ConnectException。

ChannelFuture connect(SocketAddress remoteAddress,SocketAddress localAddress);
ChannelFuture connect(SocketAddress remoteAddress,
                      SocketAddress localAddress,
                      ChannelPromise promise);

与上connect操作类似,区别在于会先绑定指定的本地地址localAddress再发起连接请求。

connect操纵会触发outbound事件,该事件会级联触发ChannelPipeline中ChannelOutboundHandler的connect(ChannelHandlerContext,SocketAddress,SocketAddress,ChannelPromise)方法被调用。

ChannelFuture disconnect();
ChannelFuture disconnect(ChannelPromise promise);

请求断开与远程通信端的连接,disconnect触发outbound事件,该事件会级联触发ChannelPipeline中ChannelOutboundHandler.disconnect(ChannelHandlerContext,ChannelPromise)方法被调用。

ChannelFuture deregister();
ChannelFuture deregister(ChannelPromise promise);

请求注销先前分配的EventExecutor,deregister触发outbound事件,该事件会级联触发ChannelPipeline中ChannelOutboundHandler.deregister(ChannelHandlerContext,ChannelPromise)方法被调用。

 boolean isRegistered();

判断当前Channel是否已经注册到EventLoop。

 boolean isActive();

判断当前Channel是否处于激活状态。

ChannelMetadata metadata();

返回当前Channel的元数据描述信息,包括TCP参数配置等。

boolean isOpen();

判断当前Channel是否已经打开。

ChannelConfig config();

返回当前Channel配置信息。

EventLoop eventLoop();

获取当前Channel注册的EventLoop。

Channel类图

Channel的子类非常多,按协议分类来看,则有基于TCP、UDP、SCTP的Channel,如基于UDP协议的DatagramChannel;按底层I/O模型来看,分别有基于传统阻塞型I/O模型的Channel,基于java NIO selector模型的Channel,基于FreeBSD I/O复用模型KQueue的Channel,基于Linux Epoll边缘触发模型实现的Channel;按功能来看,主要分为客户端Channel和服务端Channel,常用的有客户端NioSocketChannel和服务端NioServerSocketChannel,两者分别封装了java.nio中包含的 ServerSocketChannel和SocketChannel的功能。下面简单看下NioSocketChannel和NioServerSocketChannel的类图:

由类图可知,NioSocketChannel和NioServerSocketChannel都共同继承了AbstractChannel和AbstractNioChannel,继承自AbstractNioChannel表明使用Java NIO来实现的。NioSocketChannel还继承了AbstractNioByteChannel、DuplexChannel和SocketChannel,AbstractNioByteChannel是一种可以具备对字节进行操作的Channel,DuplexChannel是一种可独立关闭的全双工Channel,SocketChannel则是基于TCP/IP协议的客户端Channel;NioServerSocketChannel还继承了ServerChannel、ServerSocketChannel和AbstractNioMessageChannel,其中ServerChannel是一种接受传入Connection创建子Channel的Channel,内部定义了空实现,ServerSocketChannel基于TCP/IP协议的服务端Channel,AbstractNioMessageChannel是一种具备对消息进行操作的Channel。下面会对部分Channel做一些说明。

AbstractChannel

AbstractChannel作为NioSocketChannel和NioServerSocketChannel共同的父类,提供了Channel的很多默认实现,AbstractChannel采用聚合的方式封装各种功能,由其成员变量的定义即可知:

    private final Channel parent;
    private final ChannelId id;
    private final Unsafe unsafe;
    private final DefaultChannelPipeline pipeline;
    private final VoidChannelPromise unsafeVoidPromise = new VoidChannelPromise(this, false);
    private final CloseFuture closeFuture = new CloseFuture(this);
    private volatile SocketAddress localAddress;
    private volatile SocketAddress remoteAddress;
    private volatile EventLoop eventLoop;
  • parent:代表父类Channel;
  • id:采用默认方式生成全局唯一的id;
  • unsafe:Unsafe实例,用于执行实际的I/O操作;
  • pipeline:当前Channel对应的DefaultChannelPipeline;
  • eventLoop:当前Channel注册的EventLoop,主要负责执行Channel生命周期内的各种操作;
  • localAddress:每个Channel绑定的本地地址,可为空;
  • remoteAddress:Channel连接的远程服务地址。

AbstractChannel中聚合了所有Channel使用的能力对象,由AbstractChannel提供初始化和统一封装,如果功能和子类强相关,则由子类进行实现。下面简单看看AbstractChannel提供的核心Api:

    ......
    public ChannelFuture bind(SocketAddress localAddress) {
        return pipeline.bind(localAddress);
    }
    public ChannelFuture connect(SocketAddress remoteAddress) {
        return pipeline.connect(remoteAddress);
    }
    public ChannelFuture connect(SocketAddress remoteAddress, SocketAddress localAddress) {
        return pipeline.connect(remoteAddress, localAddress);
    }
    ......

AbstractChannel为Channel中提供的网络I/O操作均提供了默认实现,这些网络I/O操作直接调用了DefaultChannelPipeline中的相关方法。Netty基于事件驱动,也就是说当Channel进行I/O操作时会产生对应的I/O事件,然后驱动事件在ChannelPipeline中传播,由对应的ChannelHandler对感兴趣的事件进行拦截和处理,然后再由Unsafe中定义的相关方法完成实际的I/O功能。

AbstractNioChannel

AbstractNioChannel是一个想要使用Java NIO Selector方法实现的Channel需要继承的抽象类,其内部通过聚合Java Nio的几个关键对象来完成,其成员变量的定义即可知:

 private final SelectableChannel ch;
 protected final int readInterestOp;
 volatile SelectionKey selectionKey;
  • ch:当前Channel实例,由于NioSocketChannel和NioServerSocketChannel都继承了SelectableChannel,因此可安全转化为SelectableChannel的实例;
  • readInterestOp:代码JDK SelectKey的OP_READ;
  • selectionKey:Channel注册到EventLoop返回的选择键,由于Channel可能存在多个业务线程并发写的操作,当selectionKey发生变化后,需要对其他线程可见,因此声明为volatile保证可见性。

AbstractNioChannel中AbstractNioUnsafe主要为了doRegister、doDeregister、doBeginRead、newDirectBuffer、doClose等方法提供了默认实现,这里不详细说明,后文将会分析这些实现。

DuplexChannel

DuplexChannel是一种可独立关闭的全双工Channel,其主要定义了如下API:

 boolean isInputShutdown();

当且仅当远程对等体关闭其输出,而不能从当前Channel获取到更多数据时才会返回True。请注意,该方法的语义与Socket.shutdownInput()和Socket.isInputShutdown()的语义不同

ChannelFuture shutdownInput();
ChannelFuture shutdownInput(ChannelPromise promise);

对应Socket.shutdownInput()方法;不同的是第二个方法支持将操作结果写入promise。

boolean isOutputShutdown();

对应Socket.isOutputShutdown()。

ChannelFuture shutdownOutput();
ChannelFuture shutdownOutput(ChannelPromise promise);

对应Socket.shutdownOutput();不同的是第二个方法支持将操作结果写入promise。

boolean isShutdown();

判断当前Channel的输入输出是否关闭。

ChannelFuture shutdown();
ChannelFuture shutdown(ChannelPromise promise);

关闭当前Channel的输入输出端;不同的是第二个方法支持将操作结果写入promise。

Unsafe简介

Unsafe接口是Channel的辅助接口,用于执行实际的I/O操作,且必须在I/O线程中执行。该接口不应该被用户代码直接使用,仅在netty内部使用。Unsafe定义在Channel内部。

主要API

方法返回值说明
localAddress()SocketAddress返回绑定到本地的SocketAddress,没有则返回null
remoteAddress()SocketAddress返回绑定到远程的SocketAddress,如果还没有绑定,则返回null。
register(EventLoop eventLoop, ChannelPromise promise)void注册Channel到多路复用器,并在注册完成后通知ChannelFuture。一旦ChannelPromise成功,就可以在ChannelHandler内向EventLoop提交新任务。 否则,任务可能被拒绝也可能不会被拒绝。
bind(SocketAddress localAddress, ChannelPromise promise)void绑定本地址localAddress到当前channel,完成后通知ChannelFuture
connect(SocketAddress remoteAddress, SocketAddress localAddress, ChannelPromise promise)void如果localAddress非空,绑定到本地的localAddress,连接channel到指定remoteAddress,完成后通知ChannelFuture
disconnect(ChannelPromise promise)void 断开channel的连接,完成后通知ChannelFuture
close(ChannelPromise promise)void关闭channel的连接,完成后通知ChannelFuture
closeForcibly()void强制立即关闭channel,不触发任何事件。可能仅在注册尝试失败时有用。
deregister(ChannelPromise promise)void注销channel先前分配的EventExecutor,完成后通知ChannelFuture
beginRead()void调度填充ChannelPipeline中第一个ChannelInboundHandler的入站缓冲区的读取操作。 如果已经有待处理的读取操作,此方法什么都不做。
write(Object msg, ChannelPromise promise)void将消息发送缓冲数组中,完成后通知ChannelFuture
flush()void将发送缓冲数组中的消息写入channel
voidPromise()ChannelPromise返回一个特殊的可重用和传递的ChannelPromise,它不用于操作成功或失败的通知器,仅作容器使用。
outboundBuffer()ChannelOutboundBuffer返回消息发送缓冲区

由于Unsafe是Channel的内部接口,因此其大多数子类实现也都定义在Channel子类的内部,提供与Channel相关的方法实现。

Unsafe类图

AbstractUnsafe:为Unsafe定义的大部分方法提供了默认实现,如register、bind、connect等等。

NioUnsafe:定义允许访问底层SelectableChannel的操作;

AbstractNioUnsafe:AbstractUnsafe的NIO实现,主要为了doRegister、doDeregister、doBeginRead、newDirectBuffer、doClose等方法提供了默认实现,同时实现了NioUnsafe中定义的操作。

本文主要介绍Channel和Unsafe,同时简单介绍了部分子类实现,关于Channel和Unsafe中核心方法源码的分析将会在后续文章中介绍。

欢迎指出本文有误的地方,转载请注明原文出处https://my.oschina.net/7001/blog/1120349

转载于:https://my.oschina.net/7001/blog/1120349

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值