【Socket网络编程进阶与实战】【手记】服务器传输优化 详解

前言

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

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

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

1、关键的类的介绍

IoArgs:对ByteBuffer操作的简单封装,并定义了 read 和 write 开始时和启动时的回调接口。

IoSelctorProvider:在Demo中维护了全局性的一个实例(实现类的实例),这个实例持有全局性的 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放到全局的列表中。


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


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

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

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

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

3、结合代码进行流程分析

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

此处只讨论第7章修改的代码。

毫无疑问,服务端的入口类就是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方法中我们暂时取消了对该通道的监听,这样对于整个轮询会有效率上的提高。

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

那么何时恢复对该SocketChannel的监听呢?

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

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

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

猜你喜欢

转载自blog.csdn.net/qq_43290318/article/details/108395386