时长22:44大小20.83M
在上一篇文章中,我向你介绍了 Medooze 的 SFU 模型、录制回放模型以及推流模型,并且还向你展示了 Medooze 的架构,以及 Medooze 核心组件的基本功能等相关方面的知识。通过这些内容,你现在应该已经对 Medooze 有了一个初步了解了。
不过,那些知识我们是从静态的角度讲解的 Medooze ,而本文我们再从动态的角度来分析一下它。讲解的主要内容包括:WebRTC 终端是如何与 Meooze 建立连接的、数据流是如何流转的,以及 Medooze 是如何进行异步 I/O 事件处理的。
异步 I/O 事件是什么概念呢?你可以把它理解为一个引擎或动力源,这里你可以类比汽车系统来更好地理解这个概念。
汽车的动力源是发动机,发动机供油后会产生动力,然后通过传动系统带动行车系统。同时,发动机会驱动发电机产生电能给蓄电池充电。汽车启动的时候,蓄电池会驱动起动机进行点火。
这里的汽车系统就是一种事件驱动模型,发动机是事件源,驱动整车运行。实际上,Medooze 的异步 I/O 事件模型与汽车系统的模型是很类似的。那接下来,我们就看一下 Medooze 中的异步 I/O 事件驱动机制:
上面这张图就是 Modooze 异步 I/O 事件处理机制的整体模型图,通过这张图你可以发现 poll 就是整个系统的核心。如果说 EventLoop 是 Medooze 的发动机,那么 EventLoop 中的 poll 就是汽缸,是动力的发源地。
下面我就向你介绍一下 poll 的基本工作原理。在 Linux 系统中,无论是对磁盘的操作还是对网络的操作,它们都统一使用文件描述符 fd 来表示,一个 fd 既可以是一个文件句柄,也可以是一个网络 socket 的句柄。换句话说,我们只要拿到这个句柄就可以对文件或 socket 进行操作了,比如往 fd 中写数据或者从 fd 中读数据。
对于 poll 来说,它可以对一组 fd 进行监听,监听这些 fd 是否有事件发生,实际上就是监听 fd 是否可写数据和读数据。当然,具体是监听读事件还是写事件或其他什么事件,是需要你告诉 poll 的,这样 poll 一旦收到对应的事件时,就会通知你该事件已经来了,你可以做你想要做的事儿了。
那具体该怎么做呢?我们将要监听的 fd 设置好监听的事件后,交给 poll,同时还可以给它设置一个超时时间,poll 就开始工作了。当有事件发生的时候,poll 的调用线程就会被唤醒,poll 重新得到执行,并返回执行结果。此时,我们可以根据 poll 的返回值,做出相应的处理了。
需要说明的是,poll API 本身是一个同步系统调用,当没有要监听的事件(I/O 事件 和 timer 事件)发生的时候,poll 的调用线程就会被系统阻塞住,并让它处于休眠状态,而它处理的 fd 是异步调用,这两者一定不要弄混了。
poll 的功能类似 select,其 API 原型如下:
include <poll.h> int poll(struct pollfd *fds, nfds_t nfds, int timeout);复制代码
其各个参数及返回值含义如下表所示:
其中第一个fds ,它表示的是监听的 pollfd 类型的数组,pollfd 类型结构如下:
struct pollfd { int fd; / file descriptor / short events; / requested events / short revents; / returned events / };复制代码
下表是常见事件的说明:
了解了 poll API 各参数和相关事件的含义之后,接下来我们看一下当 poll 睡眠时,在哪些情况下是可以被唤醒的。poll 被唤醒的两个条件:
以上就是异步 I/O 事件处理的原理以及 poll API 的讲解与使用。接下来我们再来看看 EventLoop 模块中其他几个类的功能及其说明:
了解了 Medooze 的异步 I/O 事件处理机制后,接下来我们看一下 ICE 连接的建立。实际上,传输层各个组件的基本功能,我们在上篇文章中都已一一介绍过了,不过那都是静态的,本文我们从动态的角度,进一步了解一下 DTLS 连接的建立过程。
当某个参与人(Participator )加入房间后,需要建立一个 DTLS 连接使客户端与 Medooze 可以进行通信,上图描述了各个对象实例之间的先后调用关系。其具体连接建立过程说明如下(步骤较多,建议对照上图来理解和学习):
经历这么一个过程,ICE 连接就建立好了,剩下就是媒体流的转发了。
当终端和 Medooze 建立好 DTLSICETransport 连接以后,对于需要共享媒体的终端来说,Medooze 会在服务端为它建立一个与之对应的 IncomingStream 实例,这样终端发送来的音视频流就进入到 IncomingStream 中。
如果其他终端想观看共享终端的媒体流时,Medooze 会为观看终端创建一个 OutgoingStream 实例,并且将此实例与共享者的 IncomingStream 实例绑定到一起,这样从 IncomingStream 中收到的音视频流就被源源不断输送给了 OutgoingStream。然后 OutgoingStream 又会将音视频流通过 DTLSICETransport 对象发送给观看终端,这样就实现了媒体流的转发。
RTP 数据包在 Medooze 中的详细流转过程大致如下图所示:
图中带有 in 标签的绿色线条表示共享者数据包流入的方向,带有 out 标签的深红色线条表示经过 Medooze 分发以后转发给观看者的数据流出方向,带有 protect_rtp/unprotect_rtp 标签的红色线条表示 RTP 数据包的加密、解码过程,带有 dtls 标签的紫色线条表示 DTLS 协议的协商过程以及 DTLS 消息的相关处理逻辑。
上面的内容有以下四点需要你注意一下:
接下来,我们就对数据包的流转过程做一个简要的说明(步骤较多,建议对照上图来理解和学习):
本文我们重点介绍了事件驱动机制,它像人的心脏一样,在现代服务器中起着至关重要的作用。 Medooze 服务器中的具体实现模块就是 EventLoop 模块。通过本文的学习,我相信你对事件驱动机制有了一定的了解。接下来,就需要结合代码做进一步的学习,最好能进行相应的项目实践,这样对你继续研读 Medooze 源码会有很大的帮助。
后半部分,我们还分别介绍了 DTLS 连接建立过程和数据包的流转过程。通过本文以及上一篇文章的学习,我相信你对 Medooze 的基础架构以及 SFU 的实现细节已经有了深刻的理解。
今天你的思考题是:异步 I/O 事件中,什么进候会触发写事件?
欢迎在留言区与我分享你的想法,也欢迎你在留言区记录你的思考过程。感谢阅读,如果你觉得这篇文章对你有帮助的话,也欢迎把它分享给更多的朋友。
作者回复: 谢谢!
作者回复: 这是medooze的核心,你可以用它实现SFU,也可以用它来实现 MCU,现在MCU应用的地方很少
作者回复: 在第 30 篇文章中有介绍
作者回复: 这是需要优化的点!