C语言实现流媒体广播项目

一、简介

一个简单的流媒体项目,基于客户端/服务器模型(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
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值