分布式多媒体组播系统

image-20230308212500939

一个简单的流媒体项目,基于客户端/服务器模型(C/S)开发,采用UDP组播技术,实现MP3格式音乐广播系统。服务器端采用多线程处理频道信息并使用线程池解决多线程并发带来的性能消耗问题,实现媒体库(MP3)读取并进行流量控制(令牌桶),并通过UDP组播发送;客户端采用多进程,实现父进程接受来自网络的数据,通过进程间通信技术管道发送给子进程,子进程进行数据解码并输出到特定频道。

server端

该系统的server端主要分为三个模块,解析本地目录和读取音乐文件的媒体库模块,一个就是向多播组里面发送每一个频道信息的线程池模块,还有一个就是负责主要调度的main线程模块

媒体库模块

媒体库模块负责向上层提供数据和频道列表,主要就是解析目录和读取本地的媒体文件。其中主要有两个接口,读取频道的列表和读取频道数据,使用IO进行读取。读取相应的数据加入到多播组来进行后续操作。

线程池模块

线程池模块主要解决的是多线程的并发问题,我们需要向多播组里面发送频道数据,而每一个频道使用一个线程。每一个线程我们使用完后就需要销毁,再次使用又需要重新创建新的线程,我们就需要使用线程池来对线程进行复用,降低频繁创建和销毁线程带来的实际开销。

main线程模块

负责调度,向线程池任务队列中添加任务

main线程需要知道频道列表才能获取频道的个数以及频道描述,向线程池中的任务队列添加任务。

  • 周期性地发送频道列表
  • 发送频道数据时每一个频道要进行流量控制(令牌桶),发送对应频道的数据,有多少频道就要向线程池的任务队列中发送多少任务。

client端

在客户端,客户端的父进程接收到server端发来的数据包,要对包进行解析,将根据两端约定的协议解析出频道列表和频道数据。父进程创建子进程并通过管道发送解析出的数据,将频道列表定向到标准输出中供用户选择,用户指定频道后将读取到的音乐文件重定向到管道的读端,并通过mplayer进行播放。

可能会问的问题

项目中遇到哪些问题,如何解决的

在子进程播放音乐时会出现丢帧的问题

多播组地址的范围

224~239是组播地址

怎么来实现组播

使用setsocketopt()函数,通过这个函数将多播的选项打开,将它使能,然后接收数据包加入到多播组来实现组播

如何实现多线程并发,如何实现线程池

image-20230306185948088

我们通过使用线程池,可以有效降低多线程操作中任务申请和释放产生的性能消耗。特别是每个线程需要处理的任务比较快的时候,系统大部分性能都会消耗再pthread_create以及线程的释放过程中。

线程池包含三个模块:任务队列管理者线程工作线程

根据上面图片简单来说,线程池的处理过程要先将任务添加到任务队列中,然后由工作线程来处理队列中的任务,已经处理的任务会被删除。

工作线程中维护了一定数量的线程,用来不停地读任务队列,从里面取出任务并处理,如果任务队列为空,工作线程就会被阻塞(条件变量/信号量),当队列中有了新的任务,就将阻塞解除工作线程开始工作。

管理者线程是不处理任务队列中地任务地,它的任务是周期性地对任务队列中任务数量以及工作线程中处于busy态的线程进行检测。根据任务的数量适量创建或销毁工作线程。

读列表和读数据用的协议

读列表和读数据在传输时用的是两个包,是不同的协议。但是在两种协议里面都的都是变长的结构体,因为我们不知道有多少的频道列表和频道列表中有多少的媒体文件。

而频道列表的结构要复杂一点,因为在发送频道列表时,发送的是每一个频道的频道号和频道描述,而我要作为一个数据包发送过去。为了区分读取的是频道列表包还是频道数据包,将整个频道列表结构体的的频道号设置为0号。每一个频道又有自己的频道号,频道的频道描述是没有限制的,所以使用的是可变长的结构体,频道数据也是一样的。

为什么c端使用的是多进程并发

c端的并发工作主要是用播放器进行播放,播放接收到的音乐数据,在播放时就要创建一个新的进程去运行播放器。所以这里我只能去选择使用多进程,进程是可以被一个新的进程替换掉的,当我的父进程从多播组接收到音乐文件,我选择通过管道传输给播放器进程,也就是子进程,因为播放器进程替换了子进程。

c端如何让播放器进程去读管道

c端要将文件的重定向,使用dup2()函数,把读取到的音乐文件重定向到管道的读端

c端一定要读管道吗

不一定要用管道,因为管道比较好实现,操作方便。而且足以满足目前对于进程间通信的需求。进程间通信还可以使用共享内存、ipc的消息队列。但是用的最多的还是socket套接字