这是[手把手一起学live555]的第6篇(按这个序号看,请找正确顺序看)。
live555工程在我的gitee下(doc下有思维导图、drawio图):https://gitee.com/lure_ai/live555/tree/master
章节目录链接
0.前言——章节目录链接与为何要写这个?
https://blog.csdn.net/yhb1206/article/details/127259190?spm=1001.2014.3001.5502
学习demo
live555mediaserver.cpp
学习线索和姿势
1.学习的线索和姿势
网络编程
流媒体的地基是网络编程(socket编程)。
[网络编程学习]-0.学习路线。
绘图规则
本文的对象图和思维导图遵守的规则详见:
2.绘图规则
本节内容和目标
(1)TCP非阻塞服务端网络编程流程accept节点(非阻塞服务网络编程流程:socket创建、bind、listen、select、accept、select、recvfrom/send、close)
(2)思维导图绘制
(3)对象图绘制
正式开始
非阻塞服务端网络编程流程:socket创建、bind、listen、select、accept、select、read/write。
4.live555mediaserver-第一次select讲到了万事俱备只欠东风了,我要是客户端,此时我会说:我好孤独单,我好寂寞,我好冷啊!谁来链接我?!(本节就追踪下客户端链接后执行的accept流程)。
此时东风来了——有客户端调用connect来链接live555mediaserver这个服务端了。——哎呦,不错哦!
但是如何感性认识呢?
把live555mediaserver.cpp编译完成生成可执行程序
live555mediaserver.exe(win系统下),执行
live555mediaserver.exe文件(win下为例是双击它),就会出现如下画面:
这就是感性认识(外在认识)——其实就是前面2节讲的流程走一遍的打印信息——程序运行轨迹——而此时它处于select监听状态。你看,它把URL都打印出来了——URL:rtsp://192.xxx.xxx.xxx/文件名。这个时候我打开VLC,然后把那个url写进去,比如rtsp://192.xxx.xxx.xxx/test.264(前提
live555mediaserver.exe所在目录下有这个文件),然后VLC就会去拉流,那么这个时候我们能看到VLC播放出来这个视频文件。
而VLC就是个客户端,它播放这些文件前需要向客户端live555mediaserver这个服务端发起链接。
现在我就只关注VLC发起链接时,服务端的动作是什么呢?如下思维导图。
可以看到VLC发起客户端链接,服务端处于select监听状态,此时有2种情况:
(1)select失败。
select返回值如果<0则select失败,则进行异常处理,如下图。
我其实把它的源码规范了下格式,因为源码风格不是特别容易区分selectResult结果判断的覆盖范围,上图就很清晰。如果select返回结果<0则进入异常的处理,否则就是成功。
(2)select成功。
selext返回值≥0,select要么超时,要么监听的可读或可写或异常事件集里有事件发生了。——假设就是我们的服务端socket接收到VLC客户端的链接请求了——也就是说可读监听socket集里发生了事件(我们的服务端socket已经添加进去了)。
那么来看下接下来我关注的处理,如下图。
这是轮询链表队列的成员,一一匹配是哪个socket发生了事件——当然此时会匹配到我们的服务端socket,那么就会执行对应的方法。
前面一节讲select流程节点的准备工作之一就是把服务端socket、对应的请求响应方法以及其他参数保存到new出来的链表成员里,那么此时就匹配到了这个链表的成员。就开始执行这个链表队员保存的方法,思维导图如下:
服务端看着上图泪流满面:客官,你可来了!终于等到你了!我可想死你了!
如上思维导图,匹配到了服务端socket,最终调用的方法是
GenericMediaServer::incomingConnectionHandlerOnSocket(int ),来看下它干的事:
(1)accept调用——accept调用原来在这里 ——accept调用就获取到了客户端的信息,比如客户端ip地址等信息。同时系统会创建一个客户端已链接socket给我们,我们用来和客户端进行数据接受与发送。
(2)忽略pipe信号。这样如果客户端断开链接服务端进程不会挂。
(3)设置为非阻塞socket——因为系统给新来的客户端创建一个socket,需要设置下。
(4)调用GenericMediaServer::createNewClientConnection,但是它是纯虚函数,实现它的是在子类,即RTSPServer::createNewClientConnection,如下图
RTSPServer::createNewClientConnection
它做的只有一件事:
new RTSPServer::RTSPClientConnection,
创建这个对象出来(fOurConnectionsUseTLS默认是false)——有意思——而且这个类对象是RTSPServer类中定义的一个类,且它继承自GenericMediaServer类中定义的另一个类。
注意,我要放出新类图了,如下。
如上图的最右边的类,我不知道怎么画类中类,就自创一个双向箭头表示把这个类拿出来放大剖析——类中类,有意思。
看下new RTSPServer::RTSPClientConnection这个对象创建的过程中干了啥。如下思维导图:
如上思维导图是new RTSPServer::RTSPClientConnection的完整流程,最终干了2件事:
(1)将accept获取到的新的已链接客户端socket添加到可读和异常的select监听集——还记得吧,之前已经添加了服务端监听socket,现在它们可以一起被监听了。
(2)new一个链表对象,将accept获取到新的已链接客户端socket、对应响应方法保存起来,并放到BasicTaskScheduler0的成员fHandlers维护的链表中去了。
注意响应的方法是:
GenericMediaServer::ClientConnection::incomingRequestHandler(void* instance, int /mask/)
现在链表中有3个了——一个是链表头结点(哨兵-不保存任何数据),一个是服务端监听socket,一个是客户端已链接socket。新的链表队列如下图:
实际上上图并不准确,除链表头应该有5个链表队员,这里为了简化理解可以这么画,实际图后续总结里放出来。
那么下次BasicTaskScheduler0::doEventLoop循环又要调用BasicTaskScheduler::SingleStep,而它里面又是select,这次就同时监听服务端监听socket、客户端已链接socket了。
accept到此结束。
这也代表着已经完成建立客户端与服务端网络的链接,它们之间接着要传输货物了——rtsp协议、rtp包等——但是这些先不管,下一节是讲解网络编程的剩余线索——selec-recvfrom/send-close。
另外,创建新对象RTSPServer::RTSPClientConnection还有一点,还把它放到hash链表里管理了,这个hash链表只是个名字,其操作代码如下:
其对应的对象图如下:
如上图,单拿出来将这个新对象以及其管理链表画出来,这个其实就是上面代码的图形话,至于hash链表,此hash非彼hash,只是live555里的类名,如上图的hashtable,解释下,如下代码
创建这种hash链表有2类,一个是 string字符串,这个后面会用到,到时再说。另一类是one word类,本次就是如此。它们的区别只是计算索引的公式不同——索引是个啥?就是数组fStaticBuckets的下标,可以看前面对象图里有4个成员,每个成员管理一个链表。——不同类型的索引公式计算如下
而fClientConnections初始化时是ONE_WORD_HASH_KEYS,所以此时的公式如下:
这个公式呢,就是key值——新对象的this指针值也就是新对象的地址——乘以一个固定值再除以fDownShift值(初始值为28),最后再夏至值的范围为0-3——最后映射到0-3。我是没搞懂它这是啥意思,反正它最后的目的是把新对象的地址映射为0-3值,管它呢!
小结
本节追踪了accept,可以看到,每来一个客户端链接都会执行本节思维导图所讲的流程,按你感性认识就是:每来一个新客户端链接就会accept得到已连接客户端socket,然后new 一个RTSPServer::RTSPClientConnection对象——这个对象会把客户端已连接socket加入select监听集,然后new一个socket链表队员并把新的客户端socket和固定响应方法“装起来”。——其实在大脑中应该就是一个客户端链接就对应创建一个新对象RTSPServer::RTSPClientConnection和创建一个新的socket链表队员并入队——socket链表也在动态的增长。这个在后续总结里可以绘图理解。
在此,再贴下本节live555实现accept中创建的新对象:
(1)RTSPServer::RTSPClientConnection对象
(2)fClientConnections管理这个新对象的链表
如下图: