IPV4流媒体网络多播-代码解析
IPV4流媒体项目(网络多播)-代码解析
一、IPV4项目项目代码以及解析
项目整体框架:
可以做流量控制的地方:
- 服务端通过socket向多播组ip发送数据,需要考虑丢包,拥塞等问题
- 客户端调用解码器播放音频数据
- 客户端,使用管道实现父子进程通信,管道有阻塞的属性
1.协议
site_type.h
该头文件进行数据创建一个别名,将数据类型进行隐藏
1 | /* |
proto.h
通信协议中,定义各类宏:
DEFAULT_MGROUP
:多播组的地址DEFAULT_RCVPORT
:接收端口,客户端服务端的端口需要一致MSG_CHANNEL_MAX
:UDP通信(频道)数据包推荐大小MSG_LIST_MAX
: UDP通信(节目单)数据包推荐大小
定义各类通信数据包的结构体(不适用结构体数据对齐):
msg_channel_st
: 定义有关具体频道的数据包 (包含 频道id + 描述信息)msg_listentry_st
: 定义节目单中每一条记录的数据包 (包含 频道id + 描述信息 + 前面两个部分占用的字节数量(这一条记录占用的大小))msg_list_st
: 定义用于发送节目单的数据包 (包含 节目单的频道id + 节目单中多条记录的数据包)
1 | /* |
2.客户端
客户端主要的工作任务如下:
- 加入多播组,从多播ip接收数据包,数据包含节目单数据以及各个频道的内容数据(MP3文件内容)
- 接收节目单数据,选择频道
- 接收选择频道的数据包,通过管道机制,将数据发送给子进程
- 子进程在管道读端,读取接收的选择频道数据包,并且调用解码器,解析播放接收的mp3文件
客户端程序的工作流程:
- 命令行分析:通过getopt_long函数获得,运行客户端程序时,命令行中所携带的参数,对参数进行分析,运行不同的命令(指定接收端口、指定多播组、指定播放器、显示帮助,这些不同的命令均有默认的参数)
- 创建
UDP
套接字,设置套接字属性,加入多播组 - 绑定客户端本机的ip信息:在这个项目中,客户端用于接收来自多播组的数据包,属于(先接收的一方)被动端,需要绑定本机的ip与端口(端口需要与服务端的端口一致)
- 创建管道用于父子进程通信
- 创建父子进程,区分父子进程工作:
- 父进程:接收节目单数据,选择频道,接收选择的频道数据,将接收的频道数据通过管道发送给子进程
- 子进程:子进程调用解码器,解析播放从管道读取的频道数据(mp3文件数据)
(1)客户端头文件
client.h
1 |
|
解析1:
1 |
/usr/bin/mpg123
表示调用一个解码器,其中 -
表示解码器的输入为标准输入,>
代表输出重定向,操作指令/usr/bin/mpg123
的输出全部重定向到空设备/dev/null
中。/dev/null
又称之为黑洞,任何发送到 /dev/null
的数据都会被丢弃,读取 /dev/null
时它会立即返回文件结束(EOF
)。这使得 /dev/null
成为处理不需要的输出的理想工具。
(2)客户端源码
client.c
1 |
|
CmakeLists.txt
1 | cmake_minimum_required(VERSION 3.16) |
(3)多播组网络接口ip与套接字绑定的本机ip均设置为INADDR_ANY
1 | // 套接字属性-加入多播组--成为多播组的成员 |
- 设置加入多播组时使用
INADDR_ANY
,这指示系统选择最合适的网络接口用于多播数据的接收。这通常是基于路由配置自动决定的,系统会选择能够最有效接收到多播流的接口
1 | /*3. 绑定本机IP与端口*/ |
- 绑定本机IP时,设置本机ip为
0.0.0.0
(INADDR_ANY
)这个特殊的地址用于指示应用程序应监听所有可用的网络接口
(4)子进程调用解码器
1 | // 子进程-调解码器 |
- 子进程会复制父进程的资源,子进程中不需要使用套接字文件描述符,因此可以将复制父进程的套接字文件描述符关闭;
- 子进程读取管道数据,可以关闭写端,避免文件描述符资源浪费,以及确保数据完整性
dup2(pd[0], 0)
是将pd[0]
(通常是一个管道的读端)重定向到标准输入。这意味着所有本来应从标准输入读取的操作现在都会从pd[0]
读取数据。在头文件中已经定义了DEFAULT_PLAYERCMD
调用解码器的命令的输入内容从标准输入中获取,这样重定向之后,解码器的命令的输入内容将会从管道读端获得,而管道读端将会获得父进程从多播组接收的频道(MP3文件)内容- 使用
execl
函数替换当前进程的映像为一个新的程序,即在子进程中执行一个新的调用解码器的程序
(5)父进程接收数据包
1 | // 收节目单 |
- 接收节目单数据包,并且通过数据包中的频道
id
做校验 - 定义节目单数据包结构体类型,并为其开辟动态内存空间,其大小为节目单数据包的推荐大小,在
proto.h
协议文件中存在定义MSG_LIST_MAX
- 打印节目单信息
1 | // 选择频道 |
- 定义节目单数据包结构体类型,并为其开辟动态内存空间,其大小为具体频道单数据包的推荐大小,在
proto.h
协议文件中存在定义MSG_CHANNEL_MAX
- 选择相应频道的数据包,并且通过,节目单数据包发送方与频道数据包发送方的IP与端口进行校验,判断节目单数据包与频道数据包是否来自于同一个发送端,防止数据被中间方伪造
- 通过选择的频道id与接收的频道数据包中的频道
id
做校验,判断是否为选择的频道id数据包 - 将频道数据包中的data数据,写入管道
3.线程令牌桶算法库–流量控制
(1)令牌桶算法头文件
mytbf.h
1 | /*多线程并发的令牌桶,流量控制*/ |
- 令牌桶算法的头文件中通过
typedef void mytbf_t
,隐藏令牌桶算法的真实数据类型
(2)令牌桶源文件
mytbf.c
1 |
|
- 整个令牌桶系统只会创建一个子线程用于,为所有创建的令牌桶固定事件取得CPS数量的令牌
- 在
mytbf_t *mytbf_init(int cps,int burst)
函数中,使用pthread_once(&init_once,module_load)
代码调用一次module_load
函数为令牌桶系统创建一个子线程用于,为所有创建的令牌桶固定事件取得CPS数量的令牌 - 令牌桶系统中的
static struct mytbf_st* job[MYTBF_MAX]
令牌桶数组为共享资源,每次使用之前需要枷锁进行线程同步 - 每次对各个令牌桶中的令牌
token
进行增加或者归还或者使用时,均需要枷锁,进行线程同步 - 在媒体库中会为每一个频道,创建一个令牌桶,实现流量控制
4.媒体库
媒体库的下的目录结构如下:
1 | (base) zxz@ubuntu:~/Proj/C_C++/linux_c/IPV4_StreamingMedia/Music$ tree |
上述目录结构,表示本地文件系统中的媒体库中,有三个频道目录,ch1/ch2/ch3
,每个频道目录有一个描述文件desc.txt
与一个mp3文件,描述文件用于描述,mp3
音乐文件的音乐类型
(1)媒体库头文件
medialib.h
1 |
|
(2)媒体库源文件
1 |
|
(3)解析媒体库下的具体频道目录下的信息
函数static struct channel_context_st *path2entry(const char *path)
1 | /* |
- 判断描述文件是否合法,每一个描述文件均为
desc.txt
,其核心内容为第一行,描述的内容是mp3文件的音乐类型。首先判断描述文件是否存在,其次判断描述文件中是否有内容 - 使用
mytbf_init
为该频道创建一个令牌桶,进行流量控制 - 判断频道核心内容mp3文件是否存在,若存在则打开
(4)获取媒体库的节目单信息
函数int mlib_getchnlist(struct mlib_listentry_st **result,int *resume)
用于获取媒体库中所有的合法频道信息 (频道id + 描述信息)
1 | /* |
(5)读取每一个频道目录下的mp3文件内容
1 | /* |
5.节目单线程
(1)节目单线程头文件
thr_list.h
1 |
|
(2)节目单线程源文件
thr_list.c
1 |
|
(3)节目单线程任务函数
1 | /* |
6.频道线程
(1)频道线程头文件
thr_channel.h
1 |
|
(2)频道线程源文件
1 |
|
(3)节目单线程任务函数
通过媒体库中函数mlib_readchn
读取具体频道的mp3文件内容,并且发送多播组ip
1 | /* |
7.服务端
服务端主要的工作任务如下:
- 判断命令行输入,判断是否将服务端作为一个守护进程运行
- 创建多播组,定义服务端发送数据的远端ip信息
- 从媒体库获取合法的节目单信息
- 通过获取的节目单信息,创建频道线程,有多少合法频道就创建多少频道子线程
- 创建节目单线程,只需要创建一个节目单线程发送节目单数据包
- 将服务端进程挂起,等待信号打断
服务端程序的工作流程:
- 定义进程中信号行为,用于捕捉信号,退出守护进程
- 命令行分析:通过getopt函数获得,运行客户端程序时,命令行中所携带的参数,对参数进行分析,运行不同的命令(指定接收端口、指定多播组、指定接收端口、前台运行,指定媒体库路径,指定网络设备,这些不同的命令均有默认的参数)
- 判断运行模式是否为前台运行,或者为守护进程后台运行
- UDP套接字初始化,设置套接字属性,创建多播组,定义服务端发送数据的远端ip信息
- 获取节目单数据
- 创建频道线程,向多播组ip发送频道数据包
- 创建节目单线程,向多播组ip发送节目单数据包
(1)服务端头文件
server_conf.h
1 |
|
(2)服务端源文件
server.c
1 |
|
CMakeLists.txt
1 | cmake_minimum_required(VERSION 3.16) |
8.整体目录结构
1 | (base) zxz@ubuntu:~/Proj/C_C++/linux_c/IPV4StreamingMedia$ tree |
- INSTALL文件:部署指导,使用这个应用程序,应该如何安装,会产生什么样的库
- LISENCE文件:使用许可,当前遵循什么协议等
- README:告知用户的一些须知
README
1 | 项目名称:IPV4流媒体项目 |
9.编译运行
在src目录下的client目录与server目录下,分别运行指令,编译运行客户端与服务端程序
1 | cmake.. |
运行效果如下:
1 | ./server -F |
- 显示节目单数据与各个频道数据均发送成功
另开终端运行:
1 | ./client |
- 成功接收来自多播组ip的节目单数据,以及频道数据