从Netty官方给出的example包着手点分析,echo 回声,也就是客户端传什么,服务端传回什么
先从客户端开始看
属性,ip地址,端口号,数据大小之类的 四个写死了的
判断ssl是否为空,来决定是否需要初始化SslContext (可以理解为一些客户端的初始化配置,我们的例子中默认是null)
下面重点来了,前面暂时都可以忽略
核心点几步:
1. 创建一个线程池组group; //初始化线程池系列
2. 创建一个客户端Bootstrap;
3. 加入线程池组,
4. 加入socketChannel, //设置了channelFactory 用来创建NioSocketChannel
5. 定义了ChannelInitializer,加入了自定义的handel;
6. 异步connect; //这一步做的事比较多,new一个channel,初始化一个pipeline,将hendel全部加入到pipeline。从group中拿一个loop 将channel注入到loop中,启动loop线程,将channel注册到selector上
7. 关闭channel;
8. 关闭线程池组group;
这一篇重点分析第2,3,4,5步
这些不是最重点的,但是也是必要的
本篇简单介绍一下初始化的过程,包括创建BootStrap 初始化handel,定义channelFactory等过程
---
new Bootstrap()
ServerBootstrap 类用于创建服务端实例,Bootstrap 用于创建客户端实例
可以理解为这是Netty留给外界调用的一个入口,大大简化了我们对Netty的使用
源码走起
bootstrap继承自顶层Channel,至于Channel之后会仔细分析,暂时认知他就是个管道吧,每一次连接就会创建一个channel,然后会不断地通过channel传输数据然后传给pipeline处理
bootstrap的定位就是一个客户端,封装了connect,group,channel,handel等方法,将这些组件组装在一起供我们使用
bootstrap.group()
非常非常的简单,就是把之前初始化的group配置进来,self()就是将配置完的自己返回,用来继续配置(builder模式)
bootstrap.channel()
传进来未知类型的class类型,封装成ChannelFactory然后存起来,以后需要这个class的时候通过反射实例化出来。
落地其实就是传入个NioSocketChannel ,把他封装成ReflectiveChannelFactory然后存起来,需要NioSocketChannel的时候 就会反射的方式通过构造方法 实例化一个NioSocketChannel出来
这一步判断传进来的class无参构造方法是否存在
将包装好的ReflectiveChannelFactory存起来,返回bootstrap
new ChannelInitializer<SocketChannel>(){ initChannel() } 这一步比较重点,定义ChannelPipeline,加入各种handler
单看bootstrap.handler()这一步很简单,就是把传入一个ChannelHander,然后配置进来
仔细研究一下传入的这个ChannelInitializer 这是一个很重要的组件
ChannelInitializer 继承自ChannelHandler,这点毋庸置疑
首先解释一下handler与channel的关系:
handler会对传入的数据进行处理,各种各样的handler实现了对数据的各种各样的处理
这些 handler 会组成一个 pipeline,用于处理 IO 事件
每个 Channel 内部都有一个 pipeline,pipeline 由多个 handler 组成,handler 之间的顺序是很重要的,因为 IO 事件将按照顺序顺次经过 pipeline 上的 handler,这样每个 handler 可以专注于做一点点小事,由多个 handler 组合来完成一些复杂的逻辑。
首先,我们看两个重要的概念:Inbound 和 Outbound。在 Netty 中,IO 事件被分为 Inbound 事件和 Outbound 事件。
比如 connect、write、flush 这些 IO 操作是往外部方向进行的,它们就属于 Outbound 事件。 其他的,诸如 accept、read 这种就属于 Inbound 事件。
对于 Inbound 操作,按照添加顺序执行每个 Inbound 类型的 handler;而对于 Outbound 操作,是反着来的,从后往前,顺次执行 Outbound 类型的 handler。
看一下继承关系,总接口ChannelHandler,适配器ChannelHandlerAdapter 最后落实到两个抽象类 ChannelInboundHandlerAdapter channelOutboundHandlerAdapter
刚才说的ChannelInitializer 就是InboundHndler的实现
至于这个适配器Adapter什么用呢?
马老师,发生什么事了 .. 原来 是有handler 同时实现了Inbound 和 Outbound,通过Adapter完成的,比如 LoggingHandler 这种既可以用来处理 Inbound 也可以用来处理 Outbound 事件的 handler。
继续回到代码
看一下ChannelInitiallzer内部的pipeline 如何初始化的
这里是直接获取了AbstractChannel的pipeline,
so 就客户端而言,当NioSocketChannel通过构造方法反射来实例化的时候,调用父类构造器new了一个pipeline 还new了一个unsafe 以后会说
(至于这里为什么用反射,个人理解是因为灵活,比如我们可以不用NioSocketChannel自己写一个继承 AbstractNioByteChannel的类,当bootstra调用.channel()时候可以将我们自己的类传入,通过反射也会实现相应的功能。)
重点来了,pipeline的实现,默认DefaultChannelPipeline, pipeline内部定义了一个channel就是刚才的NioSocketChannel,以后会用到
这里还定义了两个重要参数 head tail
熟悉数据结构链表的同学对这个不会不敏感,好像链表的头节点和尾节点
emmm 还是个双向链表
尾节点实例化 传进来刚才的pipeline 这个TailContext还继承了ChannelIncoundHandler 说白了 他就是个handler
调用父类构造,设置了pipeline
啥意思呢,head tail 其实就是特殊的handler。它的内部还存了pipeline
捋一捋,ChannelInitializer内部构造了一个pipeline,pipeline内部构造了两个handler-head和tail,head和tail又将pipeline设置到自己的内部 (禁止套娃???)
ChannelInitializer,head,tail 都属于handler 也就是hanler内套pipeline内套handler内套pipeline 套娃警告!!
再看一下head的实现
head明显比tail要复杂,内部存了一个unsafe 这个unsafe直接用的NioSocketChannel的unsafe 这个unsafe类似于jdk的unsafe类,会调用操作系统层的一些资源
head同时继承了Inbound和OutBound!!!
此时此刻pipeline是这个样子地,有一个head 和tail
马上,很快啊,ChannelInitializer也会马上加入进来
变成这样,pipeline的初始化就算完成了,再往后添加handler就是自定义的handler了,也会按照流水线执行
至于ChannelInitializer什么时候加进来,下篇在讲,耗子尾汁.