LinuxC应用开发学习笔记(十六)—流媒体广播-终章

LinuxC应用开发学习笔记(十六)—流媒体广播-终章,第1张

LinuxC应用开发学习笔记(十六)—流媒体广播-终章 终章-完结 流媒体广播客户端搭建

项目名称:基于IPV4的流媒体广播系统
项目需求:
目前需要实现基于客户机和服务器模型的网络音频点播系统。
本音频系统可以广泛应用在语音教室和公共广播等多种场所。该软件分为服务器和客户机两个部分,
服务器运行在PC上,客户机可运行在PC机或嵌入式设备伤,服务器以多播的方式向局域网中所有的客户
机发送数据,客户机可以根据自己的选择来决定要接受的数据。如题简示

其中S端是服务器端,C端是客户端,从S端发送数据给C端,发送一个.mp3的文件。S端会发送一些节目单(类似发送广播节目一样)。data和list从lib传过来,用main将几个模块综合起来。广播是S端发过来,然后自己选择。点播是C端为主动。
C端父进程负责从网络上接受数据,传送给子进程,子进程负责来播放,其中用到了进程间通信(管道,共享,消息队列)。

代码实现 客户端 客户端client.c
#include 
#include 
#include 
#include 
#include 
#include    
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include "client.h"



struct client_conf_st client_conf = {
        .rcvport = DEFAULT_RCVPORT,
        .mgroup = DEFAULT_MGROUP,
        .player_cmd = DEFAULT_PLAYERCMD};

//打印帮助信息
static void printhelp(void)
{
    printf("-P  --port      指定接受端口n");
    printf("-M  --mgroup    指定多播组n");
    printf("-p  --player    指定播放器命令行n");
    printf("-H  --help      显示帮助n");
}



static size_t writen(int fd,const char *buf,size_t len)
{
    int ret,pos=0;

    while (len>0)
    {
        ret =  write(fd,buf+pos,len);
        if (ret<0)
        {
            if(errno == EINTR)
                continue;
        perror("write()");
        return -1;
        }
        len -= ret;
        pos += ret;
    }

    return pos;
    
}

//管道进行父子间通信
int main(int argc, char **argv)
{
    int c,sd,val,pd[2],len,ret;
    int index = 0;
    pid_t pid;
    struct sockaddr_in localaddr,serveraddr,raddr;
    socklen_t serveraddr_len,raddr_len;
    struct ip_mreqn mreq;
    int chosenid;
    struct option argarr[] = {{"port",1,NULL,'P'},{"mgroup",1,NULL,'M'},
                              {"player",1,NULL,'p'},{"help",0,NULL,'H'},
                              {NULL,0,NULL,0}};


    //有那些opt是带参数的,如果有的话加一个冒号。
    //分析命令行,man getopt_long
    while (1)
    {
        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 'p':
            client_conf.player_cmd = optarg;
            break;
        case 'H':
            printhelp();//打印帮助信息
            exit(0);
            break;
        
        default:
            break;
        }
        
    }
    
    
    sd = socket(AF_INET,SOCK_DGRAM,0);
    if (sd<0)
    {
        perror("socket");
        exit(1);
    }
    
    //man 7 ip 加入多播组 点分式转大整数
    inet_pton(AF_INET,client_conf.mgroup,&mreq.imr_multiaddr);

    inet_pton(AF_INET,"0.0.0.0",&mreq.imr_address);

    mreq.imr_ifindex = if_nametoindex("eth0");

    if(setsockopt(sd,IPPROTO_IP,IP_ADD_MEMBERSHIP,&mreq,sizeof(mreq))<0)
    {
        perror("setsocket()");
        exit(1);
    }

    val =1;
    if(setsockopt(sd,IPPROTO_IP,IP_MULTICAST_LOOP,&val,sizeof(val))<0)
    {
        perror("setsocket()");
        exit(1);
    }


    localaddr.sin_family = AF_INET;
    localaddr.sin_port   = htons(atoi(client_conf.rcvport));
    inet_pton(AF_INET,"0.0.0.0",&localaddr.sin_addr.s_addr); 
    if (bind(sd,(void *)&localaddr,sizeof(localaddr))<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)
    {
        //子进程:调用解码器
        close(sd);
        close(pd[1]);//关闭管道的写端
        dup2(pd[0],0);//重定向  
        if (pd[0] > 0)
            close(pd[0]);
        execl("/bin/sh","sh","-c",client_conf.player_cmd,NULL);//子进程调用解码器
        perror("execl()");
        exit(1);
    }
    
    
    //parent父进程:从网络上收包,发送给子进程

    //收节目单
    struct msg_list_st *msg_list;

    msg_list = malloc(MSG_LIST_MAX);
    if (msg_list == NULL)
    {
        perror("malloc()");
        exit(1);
    }
    
    serveraddr_len = sizeof(serveraddr);

    while (1)
    {
            //存放地址为msg_list
        len = recvfrom(sd,msg_list,MSG_LIST_MAX,0,(void *)&serveraddr,&serveraddr_len);
        if (len < sizeof(struct msg_list_st))
        {
            fprintf(stderr,"message is too small.n");
            continue;
        }
        if (msg_list->chnid != LISTCHNID)
        {
            fprintf(stderr,"chnid is not match.n");
            continue;
        }
        break;
    }
    
    //打印节目单并选择频道
    struct msg_listentry_st *pos;
    for (pos = msg_list->entry;(char *)pos < (((char*)msg_list) + len) ;pos = (void *)(((char*)pos)+ntohs(pos->len)))
    {
        printf("channel %d:%sn",pos->chnid,pos->desc);
    }
    
    //free(msg_list);
    puts("please enter:");
    while (ret < 1)
    {
       ret = scanf("%d",&chosenid);
       if (ret != 1)
            exit(1);
    }
    

    //收频道包,发送给子进程
    fprintf(stdout,"chosenid = %dn",ret);

    struct msg_channel_st *msg_channel;
    msg_channel = malloc(MSG_CHANNEL_MAX);
    if (msg_channel == NULL)
    {
        perror("malloc()");
        exit(1);
    }
    raddr_len = sizeof(raddr);
    while (1)
    {
            //存放地址为msg_channel
        len = recvfrom(sd,msg_channel,MSG_CHANNEL_MAX,0,(void *)&raddr,&raddr_len);
        if (raddr.sin_addr.s_addr != serveraddr.sin_addr.s_addr||raddr.sin_port!=serveraddr.sin_port)
        {
            fprintf(stderr,"Ignore:adress not match.n");
            continue;
        }
        if (len < sizeof(struct msg_channel_st))
        {
            fprintf(stderr,"Ignore:message is to small.n");
            continue;
        }

        //暂停
        if (msg_channel->chnid == chosenid)
        {
            fprintf(stdout,"accepted msg:%d recieved.n",msg_channel->chnid);
            if (writen(pd[1],msg_channel->data,len-sizeof(chnid_t))<0)//往管道口写入
            {
                exit(1);
            }
            
        }
        break;
    }

    free(msg_channel);

    close(sd);

    exit(0);

}
客户端client.h
#ifndef  CLIENT_H__
#define  CLIENT_H__

#define  DEFAULT_PLAYERCMD      "/usr/bin/mpg123 - > /dev/null" 

struct client_conf_st
{
    char* rcvport;  //跨网络的数据传输绝对不会出现指针
    char* mgroup;
    char* player_cmd;
};


//全局变量在文件进行使用的时候,要在.h文件中进行声明
extern struct client_conf_st client_conf;


#endif


客户端 服务器端 服务器配置头文件.h
#ifndef SERVER_CONF_H__
#define SERVER_CONF_H__

#define  DEFAULT_MEDIADIR       "/var/media"
#define  DEFAULT_IF             "eth0"

enum 
{
    RUN_DAEMON =1 , //守护进程运行
    RUN_FORGROUND   //前台方式运行
};


struct server_conf_st
{
    char *rcvport;  //默认接收端口
    char *mgroup;   //默认多播组号码
    char *media_dir;//媒体库的位置
    char runmode;   //运行模式(前台或者后台)
    char *ifname;   //网络设备
};

extern struct server_conf_st server_conf;
extern int serversd;
extern struct sockaddr_in sndaddr;
#endif // !SERVER_CONF_H__

媒体库.c
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include "medialib.h"
#include "mytbf.h"
#include "server_conf.h"

#define PATHSIZE 1024

#define LINEBUFSIZE 1024 

#define MP3_BITRATE 100
//封装的结构体
struct channel_context_st
{
    chnid_t chnid;
    char *desc;
    glob_t mp3glob;
    int pos;
    int fd;
    __off_t offset;
    mytbf_t *tbf;//流量控制


};

static struct channel_context_st channel[MAXCHNID + 1];


static struct channel_context_st *path2entry(const char *path)
{
    char pathstr[PATHSIZE];
    char linebuf[LINEBUFSIZE];
    FILE *fp;
    struct channel_context_st *me;
    static chnid_t curr_id =MINCHNID;
    
    strncpy(pathstr,path,PATHSIZE);

    strncat(pathstr,"/desc.text",PATHSIZE);

    fp = fopen(pathstr,"r");
    if (fp == NULL)
    {
        syslog(LOG_INFO,"%s is not a channel dir(can't find desc.text)",path);

        return NULL;
    }
    if (fgets(linebuf,LINEBUFSIZE,fp) == NULL)
    {
        syslog(LOG_INFO,"%s is not a channel dir(can't find desc.text)",path);
        return NULL;
    }
    
    fclose(fp);

    me = malloc(sizeof*me);
    if (me == NULL)
    {
        syslog(LOG_ERR,"malloc():%sn",strerror(errno));
        return NULL;   
    }
    
    me->tbf = mytbf_init(MP3_BITRATE/8*3,MP3_BITRATE/8*10);
    if (me->tbf == NULL)
    {
        syslog(LOG_ERR,"mytbf_init(): %sn",strerror(errno));
        free(me);
        return NULL;
    }
    
    me->desc = strdup(linebuf);

    strncpy(pathstr,path,PATHSIZE);
    strncat(pathstr,"
struct server_conf_st server_conf = {.rcvport   = DEFAULT_RCVPORT,
                                     .mgroup    = DEFAULT_MGROUP,
                                     .media_dir = DEFAULT_MEDIADIR,
                                     .runmode   = RUN_DAEMON,
                                     .ifname    = DEFAULT_IF };


int serversd;

struct sockaddr_in sndaddr;

static struct mlib_listentry_st *list;

//打印帮助信息
static void printhelp(void)
{
    printf("-P    指定接受端口n");
    printf("-M    指定多播组n");
    printf("-D    指定媒体库位置n");
    printf("-F    前台运行n");
    printf("-I    制定网络设备n");
    printf("-H    显示帮助n");
}

static void daemonize_exit(int s)
{
    thr_list_destory();
    thr_channel_destoryall();
    mlib_freechnlist(list);

    syslog(LOG_WARNING,"signal-%d caught,exit now.",s);
    closelog();
    
    exit(0);
}

static int daemonize(void)
{
    __pid_t pid;
    int fd;
    pid = fork();
    if (pid<0)
    {
        //perror("fork()");
        syslog(LOG_ERR,"fork():%s",strerror(errno));
        return -1;
    }
    if (pid>0)      //parent
        exit(0);
    fd = open("/dev/null",O_RDWR);
    if (fd < 0)
    {
        //perror("open()");
        syslog(LOG_WARNING,"open():%s",strerror(errno));
        return -2;
    }
    else
    {
        dup2(fd,0);
        dup2(fd,1);
        dup2(fd,2);
        if (fd > 2)
            close(fd);
    }
    
    setsid();

    chdir("/");
    umask(0);
    return 0;
}

static int socket_init(void)
{
    
    struct ip_mreqn mreq;
    serversd = socket(AF_INET,SOCK_DGRAM,0);
    if (serversd < 0)
    {
        syslog(LOG_ERR,"socket():%s",strerror(errno));
        exit(1);
    }
    
    inet_pton(AF_INET,server_conf.mgroup,&mreq.imr_multiaddr);

    inet_pton(AF_INET,"0.0.0.0",&mreq.imr_address);//any adress

    mreq.imr_ifindex = if_nametoindex(server_conf.ifname);

    if (setsockopt(serversd,IPPROTO_IP,IP_MULTICAST_IF,&mreq,sizeof(mreq))<0)
    {
        syslog(LOG_ERR,"setsockopt(IP_MULTICAST_IF):%s",strerror(errno));
        exit(1);
    }
     
    //bind();
    sndaddr.sin_family = AF_INET;
    sndaddr.sin_port   = htons(atoi(server_conf.rcvport));
    inet_pton(AF_INET,server_conf.mgroup,&sndaddr.sin_addr.s_addr);

    return 0;

}

int main(int argc,char **argv)
{


    openlog("netradio",LOG_PID|LOG_PERROR,LOG_DAEMON);
    
    int  c,i,err;
    //信号意外结束,定义三个信号的行为
    struct sigaction sa;
    sa.sa_handler = daemonize_exit;
    sigemptyset (&sa.sa_mask);//信号集 清空
    __sigaddset(&sa.sa_mask,SIGINT);
    __sigaddset(&sa.sa_mask,SIGQUIT);
    __sigaddset(&sa.sa_mask,SIGTERM);
    
    sigaction(SIGTERM,&sa,NULL);//时常用来打断一个行为,对信号之前的状态不关心,接到信号终止守护进程的时候,会执行回调函数
    sigaction(SIGINT,&sa,NULL);
    sigaction(SIGQUIT,&sa,NULL);
    while (1)
    {
        c=getopt(argc,argv,"M:P:FD:I:H");
        if (c<0)
            break;
        switch (c)
        {
        case 'P':
            server_conf.rcvport = optarg;
            break;
        case 'M':
            server_conf.mgroup = optarg;
            break;
        case 'F':
            server_conf.runmode = RUN_FORGROUND;
            break;
        case 'D':
            server_conf.media_dir = optarg;
            break;
        case 'I':
            server_conf.ifname = optarg;
            break;
        case 'H':
            printhelp();//打印帮助信息
            exit(0);
            break;
        default:
            abort();
            break;
        }
    }
    
    

    
    if (server_conf.runmode == RUN_DAEMON)
    {
        if (daemonize()!=0)
            exit(1);
        
    }
        
    else if (server_conf.runmode == RUN_FORGROUND)
        {
            
        }
        else
        {
            //fprintf(stderr,"EINVALn");
            syslog(LOG_ERR,"EINVAL server_conf.runmode.");
            exit(1);
        }
    
    
    
    socket_init();
    
    
    int list_size;

    err = mlib_getchnlist(&list,&list_size);
    if (err)
    {
        syslog(LOG_ERR,"mlib_getchnlist():%s",strerror(err));
        exit(1);
    }
    
    
    //一个线程专门创建节目单
            
    err = thr_list_create(list,list_size);
    if (err)
        exit(1);

    
    //100个线程专门发送频道的数据,一个线程服务一个server
    for (i = 0; i < list_size; i++)
    {
        thr_channel_create(list+i);
        if (err)
        {
            fprintf(stderr,"thr_channel_create():%sn",strerror(err));
            exit(1);
        }
        
        
    }
    
    syslog(LOG_DEBUG,"%d channel threads create. ",i);

    
    
    while (1)
        pause();
    
    
    exit(0);
}

耗时2个月,288节课,完结!!!!!!

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zaji/5703910.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-17
下一篇 2022-12-17

发表评论

登录后才能评论

评论列表(0条)

保存