【手记】我对 第七章结束时 的代码理解

来源:7-13 NIO知识归纳梳理

passerbyYSQ

2020-09-04

更好的浏览体验,移步
博客:https://blog.csdn.net/qq_43290318/article/details/108395386
前言

已经回校了,但还没上课,就抓紧学习暑假被我搁置的课程【Socket网络编程进阶与实战】。今天下午,看完了第7章,有点难受,硬着头皮跟着老师敲代码,敲代码过程中是比较蒙的。敲完第7章后,晚上花了不少时间来理解第7章的代码。

感觉理解了七八成吧,具体的架构和封装大概理解了,剩下还有一些代码的细节还没理解到位。

在看下面之前,需要对Selector和Channel的关系有一些理解。可参看:https://www.jianshu.com/p/10717976c67c

1、关键的类的介绍
【IoArgs】:对ByteBuffer操作的简单封装,并定义了 read 和 write 的回调接口。

【IoSelctorProvider】:在Demo中维护了全局性的一个IoSelctorProvider实例(实现类的实例),这个实例持有全局性的 Selector(见下图)。

主要有两个职责:

(1)将 SocketChannel 注册 到全局的Selector 中,以及 维护 Selector 对其持有的通道(已向Selector注册的通道)的监听。

(2)启动另外的线程,对 Selctor 持有的所有通道(已向Selector注册的通道)进行轮询。当通道可读或可写时,通过SelectionKey从全局的CallbackMap中获取对应的回调函数(Runnable),并交由线程池执行。

【SocketChannelAdapter】:具有异步 发送 和 接收 消息的功能,并且定义有 通道可读或可写 时的回调,在该回调中 与 IoArgs 对象进行交互,并且处理后的IoArgs回调给外部的Connector。

额。。。单看文字,感觉好像还是很抽象和苍白。。。但我已经很努力的概括了。。。

2、Demo的架构图和主要流程分析
图片描述
​图画的比较随意,有些太细致的东西图不好表达。

ServerSocket只有一个,我暂且将它类比为 “服务端”。在TCPServer中,通过一个Selector监听ServerSockerChannel的ACCEPT事件。当有客户端来连接时,拿到客户端对应的SocketChannel(可类比为“客户端”)。并构建ClientHandler,并将对应的ClientHandler放到全局的列表clientHandlerList中。

ClientHandler字面意思比较好理解:客户端处理者。有多少个客户端连接,就对应有多少个ClientHandler。它全权负责j监听客户端对应得SocketChannel的可读和可写事件。它的构造函数 里面 包含了Connector的setup操作,因此在构建对象时就启动了对客户端的注册和监听。

再来看IoSelectorProvider。由于老师第7章结束时,该类里面Write相关尚未完善,下面仅讨论Read相关,对这个类进行理解。首先值得强调的是IoSelectorProvider在全局,只有1个实例!!!!!!!

这个实例里面持有全局的ReadSelector,ReadSelector负责对其持有的所有通道(已向ReadSelector注册的所有通道)进行 读事件 的监听。

这个实例里面里面还有一个全局的 inputCallbackMap。 这也是个关键角色。我们知道 全局的ReadSelector 和 各个SocketChannel 之间的关系(注册后产生的关系)可以由 对应的 SelectionKey 标识。当一个SocketChannel 可读时,需要执行对应的回调(HandleInputCallback)。因此在SocketChannel 注册时,将这个回调HandleInputCallback统一存到Map中。

当一个SocketChannel 可读时,需要执行对应的回调(HandleInputCallback)。这个回调交给全局的线程池 inputHandlePool 统一执行。这个HandleInputCallback主要负责从SocketChannel中读取数据到 IoArgs 中,并将 装好的数据的 IoArgs 回调给 外部的 Connector。

3、结合代码进行流程分析
上面,结合简陋的架构图,将Demo的关键类进行了大概的叙述,对于各个类的作用和主要执行流程有了大概了解。下面结合代码对整个流程进行分析,只有充分理解代码的基础上,才能有能力debug以及进行二次改动。

图片描述
毫无疑问,服务端的入口类就是Server了。上面截图的代码的作用,就是:新建一个全局的 IoSelectorProvider的实例,并将它放到 全局上下文 IoContext 中。

图片描述
TCPServer中上面横线处删除了ReadAndPrint操作,因为相关职责交由了ClientHandler,它的构造函数 里面 包含了Connector的setup操作,因此在构建对象时就启动了对客户端(SocketChannel)的注册和监听。

图片描述
ClientHandler的构造函数上面也提到过了,主要是构造Connector,并setup。

图片描述
看到这里,我们知道:有多少个客户端,就有多少个ClientHandler,就有多少个Connector和SocketChannelAdapter!!!这里关注readNextMessage方法。在回调接口 echoReceiveListener 中又执行了 readNextMessage 方法(如下图)。为什么????这得追溯到IoSelectorProvider中。!!!
图片描述

图片描述
想想横线的问题!!!!!!!!!!。答案见下图:
图片描述
在创建全局的IoSelectorProvider时(构造函数中),执行了startRead方法,该方法中,启用了另外的单独线程(高优先级),对ReadSelector持有的所有通道进行轮询,监听可读事件。上面的叙述也提到了。当有通道可读时,通过SelectionKey获取对应得HandleInputCallback,并交由全局的线程池inputHandlePool执行。那么在执行回调HandleInputCallback期间,我们是不需要也没必要再监听该SocketChannel的。因为HandleInputCallback干的事情是从通道中读取数据到 IoArgs,SocketChannel正在被读,还有必要监听它是否可读吗????因此在handleSelection方法中我们暂时取消了对该通道的监听,这样对于整个轮询会有效率上的提高。那么何时恢复对该SocketChannel的监听呢?

那就要追溯一连串的回调了。

图片描述
图片描述
HandleInputCallback从SocketChannel中读取数据到 IoArgs 中,并将 装好的数据的 IoArgs 回调给 外部的 Connector。Connector将数据打印出来,并再次调用了readNextMessage!!!追溯该方法,知道最终在 IoSelectorProvider中的regietrSelection方法中,重新恢复了监听。(见下图)。
图片描述

好了,我对整个代码的理解,已经竭尽我的表达能力表达出来了。还有一些代码细节(比如说:各种锁等),我还没有想清楚。。。。如有错误,欢迎指正。

写回答

3回答

passerbyYSQ

提问者

2020-09-04

//img.mukewang.com/szimg/5f51b28d099892d013150372.jpg

额。。。看完第7章对这里理解不到位,看第8章才知道这里,是必需的,而不仅仅是为了提高效率。

作用:

1、当有通道可读时,通过SelectionKey获取对应得HandleInputCallback,并交由全局的线程池inputHandlePool执行。那么在执行回调HandleInputCallback期间,我们是不需要也没必要再监听该SocketChannel的。因为HandleInputCallback干的事情是从通道中读取数据到 IoArgs,SocketChannel正在被读,还有必要监听它是否可读吗????因此在handleSelection方法中我们暂时取消了对该通道的监听,这样对于整个轮询会有效率上的提高。

2、如果这里不取消监听,会引起bug。线程池繁忙,导致通道可读事件(消息到达时)处理不及时,从而导致
对通道轮询时(select)会将正在处理而未处理完成的通道的可读事件,进行重复处理,而且造成恶性循环,导致线程池更加繁忙。

0
0

Qiujuer

2020-09-04

很棒,很棒的~~ 梳理的很好

0
0

passerbyYSQ

提问者

2020-09-04

吐槽一下慕课网的富文本编辑器,傻傻的。。。。

0
0

Socket网络编程进阶与实战 系统掌握Socket核心技术

理论+实践,系统且深入掌握Socket核心技术,从容应对各种Socket应用场景的不二之选

2314 学习 · 476 问题

查看课程