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

-由于是一个广播系统,C/S之间的通讯采用无连接的udp组播,组播相对于广播更加灵活并且节省资源,故采用组播技术
- 由于需要考虑到节目单频道,让用户可以提前了解到每一个频道的类型,所以在各音乐频道内容的基础上,加上节目单频道,这样无需遍历每一个频道,就可以知道各频道的内容,从而针对性的选择。因此需要规定节目单数据格式和节目频道数据格式
- 由于广播的内容是音乐(后续可延伸扩展为视频),所以需要进行流量控制。流量控制采用[令牌桶]实现,可设置传输的速率。
- 由于频道数目媒体库信息调整,故采用多线程获取并发送频道数据。不涉及多线程之间的通讯,需要考虑的令牌桶资源的互斥访问。
- 服务器最后以守护进程方式运行,系统运行过程中的数据采用系统日志保存。
二、代码实现
1、客户端
流程:先获取用户命令参数,若无特殊要求则为默参数,再创建套接字,加入多播组,fork一个子进程,子进程调用播放器,父进程从网络上接收数据包通过管道传给子进程,子进程播放。
client.h
#ifndef CLIENT_H_
#define CLIENT_H_
#define DEFAULT_PLAYERCMD " /usr/bin/mpg123 - > /dev/null"
//#define DEFAULT_PLAYERCMD " /usr/bin/mplayer - > /dev/null"
struct client_conf_st
{
char *rcvport; // for local using
char *mgroup;
char *player_cmd;
};
//extern struct clinet_conf_st client_conf;
#endif
client.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <getopt.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <errno.h>
#include <error.h>
#include <string.h>
#include <net/if.h>
#include "client.h"
#include "../../include/proto.h"
//#define DEBUG
/*
-M --mgroup 指定多播组
-P --port 指定端口号
-p --player 指定播放器
-H --help 显示帮助
*/
/* 默认参数 */
struct client_conf_st client_conf = {\
.rcvport = DEFAULT_RCVPORT,\
.mgroup = DEFAULT_MGROUP,\
.player_cmd = DEFAULT_PLAYERCMD,\
.mgroup = DEFAULT_MGROUP};
/* 帮助函数 */
static void print_help()
{
printf("-P --port specify receive port\n");
printf("-M --mgroup specify multicast group\n");
printf("-p --player specify player \n");
printf("-H --help show help\n");
}
/* 往管道中写入数据,确保每个字节写入 */
static int writen(int fd, const void * buf, size_t len)
{
int count = 0;
int pos = 0;
while(len > 0)
{
count = write(fd, buf + pos, len);
if(count < 0)
{
if(errno == EINTR)
continue;
perror("write()");
return -1;
}
len -= count;
pos += count;
}
return 0;
}
int main(int argc, char * argv[])
{
/*
initializing
level:default < configuration file <
environment < arg
*/
int index = 0;
int sd = 0;
struct ip_mreqn mreq;//group setting
struct sockaddr_in laddr; //local address
int val;//set sockopt
int pd[2];
pid_t pid;
struct sockaddr_in server_addr;
socklen_t serveraddr_len;
int len;
int chosenid;
int ret = 0;
struct msg_channel_st *msg_channel;
struct sockaddr_in raddr;
socklen_t raddr_len;
struct option argarr[] = {
{"port", 1, NULL, 'P'}, \
{"mgroup", 1, NULL, 'M'},\
{"help", 0, NULL, 'H'},\
{NULL, 0, NULL, 0}};
int c;
while(1)
{
/*long format argument parse*/
c = getopt_long(argc, argv, "P:M:p:H",argarr, &index);
if(c < 0)
break;
switch(c)
{
case 'P':
client_conf.rcvport = optarg;
break;
case 'M':
client_conf.mgroup = optarg;
break;
case 'h':
client_conf.player_cmd = optarg;
break;
case 'H':
print_help();
/*press q to quit*/
exit(0);
break;
default:
abort();
break;
}
}
/* 创建套接字 */
sd = socket(AF_INET, SOCK_DGRAM, 0);
if(sd < 0)
{
perror("socket()");
exit(0);
}
//multicast group
inet_pton(AF_INET, client_conf.mgroup, &mreq.imr_multiaddr);//255.255.255.255-->0xFF..
//local address(self)
inet_pton(AF_INET, "0.0.0.0", &mreq.imr_address);
//local net card
mreq.imr_ifindex = if_nametoindex("ens33");
/* 加入多播组 */
if(setsockopt(sd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) < 0)
{
perror("setsockopt()");
exit(1);
}
val = 1;
//improve efficiency
if(setsockopt(sd, IPPROTO_IP, IP_MULTICAST_LOOP, &(val), sizeof(val)) < 0)
{
perror("setsockopt()");
exit(1);
}
/* 改变套接字接收的大小 */
uint64_t receive_buff_size = 20 * 1024 * 1024; //20M
if (setsockopt(sd, SOL_SOCKET, SO_RCVBUF, &receive_buff_size, sizeof(receive_buff_size)) < 0)
{
perror("setsockopt()");
exit(1);
}
/* 绑定地址 */
laddr.sin_family = AF_INET;
laddr.sin_port = htons(atoi(client_conf.rcvport));
inet_pton(AF_INET, "0.0.0.0", &laddr.sin_addr);
if(bind(sd, (void*)&laddr, sizeof(laddr)) < 0)
{
perror("bind()");
exit(1);
}
if(pipe(pd) < 0 )
{
perror("pipe()");
exit(1);
}
pid = fork();
if(pid < 0)
{
perror("fork()");
exit(1);
}
if(pid == 0)//child, read, close write
{
/*decode*/
/*mpg123 read from stdin*/
close(sd);//socket
close(pd[1]);//0:read, 1:write
dup2(pd[0], 0);//set pd[0] as stdin
if(pd[0] > 0) //close pd[0]
close(pd[0]);
/*use shell to parse DEFAULT_PLAYERCMD, NULL means to end*/
execl("/bin/sh", "sh", "-c", client_conf.player_cmd, NULL);
perror("execl()");
exit(1);
}
else//parent
{
/*receive data from network, write it to pipe*/
//receive programme
struct msg_list_st *msg_list;
msg_list = malloc(MSG_LIST_MAX);
if(msg_list == NULL)
{
perror("malloc");
exit(1);
}
//必须从节目单开始
while(1)
{
len = recvfrom(sd, msg_list, MSG_LIST_MAX, 0, (void*)&server_addr, &serveraddr_len);
fprintf(stderr, "server_addr:%d\n", server_addr.sin_addr.s_addr);
if(len < sizeof(stru

最低0.47元/天 解锁文章
443

被折叠的 条评论
为什么被折叠?



