更多技术分享可关注我
前言
原文:Netty如何封装Socket客户端Channel,Netty的Channel都有哪些类型?
前面分析过Netty封装的服务端Channel——NioServerSocketChannel,对应的客户端也会封装一个Channel——NioSocketChannel,可以对比OIO网络编程模型中的ServerSocket和Socket,对应NIO中的ServerSocketChannel和SocketChannel,看看这两个组件是如何被Netty封装使用的。
Netty封装客户端SocketChannel的源码分析
如下是Netty服务端读取新连接的核心代码,在文章:Netty是如何处理新连接接入事件的?中已经总结分析过:
在黄色1处获取了JDK的SocketChannel后,在黄色2处Netty调用了NioSocketChannel的构造器,目的是将JDK的SocketChannel封装为Netty自己的客户端Channel——NioSocketChannel。下面跟进源码,看看Netty都封装了什么东西,以及为何要这么做。
总的来说,NioSocketChannel的构造器主要做了两件事:
1、调用一系列父类构造器,对SocketChannel做初始化工作
-
设置SocketChannel为非阻塞模式
-
保存初始化SocketChannel需要注册的I/O事件——OP_READ=16
-
创建SocketChannel的唯一id
-
创建SocketChannel的unsafe实现类——NioByteUnsafe,后续专门总结Netty的unsafe,这里先知道。
-
创建SocketChannel的默认pipeline组件,这个组件是Netty额外封装JDK的Channel的核心原因,目的就是更好的设计自己的架构,这里和服务端封装ServerSocketChannel是一样的逻辑,复用了代码
2、调用配置类:主要是禁止TCP Nagle算法
下面看下细节,下面是NioSocketChannel的构造器源码:
首先,层层调用父类构造器对其初始化,复习Netty Channel的继承关系:
比如先调用直接父类——AbstractNioByteChannel构造器,该构造器会将SocketChannel的I/O读事件保存:
然后继续调用父类构造器——AbstractNioChannel,这里和Netty封装服务端Channel——NioServerSocketChannel,共用了一段逻辑:
主要做了三件事:
1、保存JDK的SocketChannel,也就是AbstractNioChannel中的ch属性
2、为Netty封装的Channel保存interest的I/O事件
3、设置JDK的SocketChannel为非阻塞模式
当然在做这三件事前,会继续先调用上层父类AbstractChannel的构造器:
该构造器主要作用是初始化Netty客户端Channel的一些共有配置,比如唯一id,大动脉pipeline组件,以及为其创建unsafe,和封装服务端Channel一样的流程
全部搞完,返回到NioSocketChannel构造器,继续执行如下初始化config属性的逻辑:
这里初始化config,最重要的一件事是禁止了TCP的Nagle算法,如下代码:
至于何时才会自动关闭该算法,需要看if判断逻辑里的判断方法——canEnableTcpNoDelayByDefault是什么东西,如下:
发现一个默认的变量CAN_ENABLE_TCP_NODELAY_BY_DEFAULT,它在非安卓环境下设置为关闭,即在非安卓端上部署Netty,它会自动关闭TCP的nagle算法。
TCP协议默认开启了Nagle算法(即默认关闭TCP_NODELAY选项,注意意思是相反的)
Netty客户端NioSocketChannel的创建很简单,至此分析完毕。
小结
Netty封装客户端NioSocketChannel主要就是三件事:
1、配置SocketChannel为非阻塞——configureBlocking(false)
2、保存OP_READ事件,但是是延迟注册的,且会为Channel创建唯一id,unsafe组件(负责底层数据读写)和pipeline组件(业务数据流动的载体)
3、在NioSocketChannelConfig()中判断设置是否打开TCP的Nagle算法,即在非安卓环境下,执行setTcpNoDelay(true),即禁止Nagle算法,希望把小数据包尽量发送出去,降低延迟,而Nagle算法会通过减少需要传输的数据包来优化网络。在Linux内核中,数据包的发送和接受会先做缓存,分别对应于写缓存和读缓存。目的是为了尽可能发送大块数据,避免网络中充斥着许多小数据块。
Netty的Channel类型总结
下面全面总结一下Netty的Channel类型,当然主要是NIO模型下的Channel。Netty的NIO模型的Channel主要分为两类:NioSocketChannel和NioServerSocketChannel,分别封装了JDK的SocketChannel和ServerSocketChannel,对应了客户端Socket和服务端Socket。
一个精简版的类图如下,蓝色部分是服务端Channel,白色是客户端的Channel。
我们只关心对JDK NIO的封装设计,从顶到下,基本脉络是:Channel接口>AbstractChannel(所有Channel的骨架实现)>AbstractNioChannel(NIO模型下Channel的骨架),而从AbstractNioChannel又开始分支,如下:
Channel是Netty的所有Channel的共同接口,定义了一系列的Socket或者Netty自身的I/O操作的接口,而Channel的底层I/O功能的实现都是由Unsafe接口负责,该接口聚合在了Channel接口,作为其内部接口。
AbstractChannel是Channel的骨架实现,负责实现不同类型Channel的共同组件或者基础属性,比如保存Channel的标识id,unsafe骨架实现,pipeline,EventLoop属性等。
AbstractNioChannel是NIO模型下Channel的骨架实现,相对的,阻塞模型下Channel的骨架实现就是AbstractOioChannel,这很少有人用,不讨论它。AbstractNioChannel最大的特性是聚合了JDK的I/O多路复用器——Selector,主要负责NIO相关的Channel的抽象实现,且内置了JDK底层的Channel接口,可以给客户端/服务端Channel设置非阻塞模式,保存interest的I/O事件,SelectionKey等,前面分析过这个构造器。
从AbstractNioChannel后,开始细分服务端和客户端的Channel:
主要就是这两类Channel,它们都直接继承AbstractNioChannel,两者最明显的区别是默认设置的I/O事件不一样,NioSocketChannel是设置OP_READ事件,而NioServerSocketChannel是设置OP_ACCEPT事件,即前者是读取已建立连接上的数据,后者是读取新连接。
还有一个Unsafe,前面说过每个Netty的Channel都有一个Unsafe接口与之绑定,Unsafe接口的相关实现类负责实现Netty的Channel所有I/O操作,一共有两类unsafe的实现类,即服务端Channel的NioMessageUnsafe,它的主要作用就是读新连接,还有客户端Channel的NioByteUnsafe,它的主要作用是读、写已有连接上的数据,复习下两者的继承关系:
最后,还能知道每个Netty的Channel都有一个config——配置工具类,存储了每类Channel的底层网络配置,其继承关系如下:
整个结果一气呵成,充分利用了模板方法等设计模式,不论是命名上,还是具体实现上,都非常美观和流畅,可以学习这种组件分类设计的方式。
小结
NIO模型下,Netty中的Channel分类:
1、NioServerSocketChannel是服务端Channel,继承AbstractNioMessageChannel,启动时注册的I/O事件为OP_ACCEPT,并创建NioServerSocketChannelConfig和NioMessageUnsafe,核心作用就是读新客户端连接
2、NioSocketChannel是客户端Channel,继承AbstractNioByteChannel,初始化时注册的I/O事件为OP_READ,创建NioSocketChannelConfig和NioByteUnsafe,核心作用是读、写已有连接上的数据
欢迎关注
dashuai的博客是终身学习践行者,大厂程序员,且专注于工作经验、学习笔记的分享和日常吐槽,包括但不限于互联网行业,附带分享一些PDF电子书,资料,帮忙内推,欢迎拍砖!