C项目实战:基于IPV4流媒体广播系统

需求

目前需要实现基于客户机和服务器模型的网络音频广播/点播系统。

本音频可以广泛应用在语音教室和公共广播等多种场景。该软件分为客户端和服务器两个部分,服务器运行在PC机上,客户机可以在PC机或者嵌入式设备上,服务以多播的形式向局域网中所有的客户机发送消息,客户机可以根据自己的选择来决定要接受的数据。

参考

UNIX环境高级编程
UNIX网络编程
TCP/IP详解
深入理解计算机系统

实现代码

头文件

proto.h

#ifndef PROTO_H__
#define PROTO_H__

#include "site_type.h"

#define DEFAULT_MGROUP "224.2.2.2"
//默认的多播地址是224.0.0.0到239.255.255.255
#define DEFAULT_RECVPORT  "1989"
#define CHNNR 100   //总频道数

#define LISTCHNID 0 //节目单频道id
#define MINCHNID 1 //最小频道id
#define MAXCHNID (CHNNR+ MINCHNID -1)  // 最大频道号 100   总共有101个频道,1个节目单,100个频道。

#define MSG_CHANNEL_MAX (65536 - 20 - 8-2 )  //推荐包长度-ip包长度-udp包长度
#define MAX_DATA (MSG_CHANNEL_MAX - sizeof(chnid_t)) //最大上线。

#define MSG_LIST_MAX (65536 - 20-8  -2)
#define MAX_ENTRY MSG_LIST_MAX -sizeof(chnid_t)


struct msg_channel_st
{
    chnid_t chnid; // 0-255  MINCHNID --- MAXCHNID
    uint8_t data[1];

}__attribute__((packed));

struct msg_listentry_st
{
    chnid_t chnid;
    uint16_t len;
    uint8_t desc[1];
}__attribute__((packed));

struct msg_list_st
{
    chnid_t chnid; // must be LISTCHNID
    struct msg_listentry_st entry[1]; //变长


}__attribute__((packed));

#endif

site_type.h

#ifndef SITE_TYPE_H__
#define SITE_TYPE_H__

#include <stdint.h>

typedef uint8_t chnid_t; // 0-255
typedef uint32_t socklen_t;// 32位无符号整型数

#endif

服务端

medialib.h

#ifndef MEDIALIB_H
#define MEDIALIB_H
#include "site_type.h"
#include "proto.h"

// #define MP3_BITRATE 65536

struct mlib_listentry_st
{
    chnid_t chnid;
    char* desc;
};

int mlib_getchnlist(struct mlib_listentry_st**,int*list_size);

int  mlib_freechnlist(struct mlib_listentry_st*);

ssize_t mlib_readchn(chnid_t,void*,size_t );

#endif

medialib.c

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <syslog.h>
#include <errno.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <glob.h>
#include <pthread.h>

#include "mytbf.h"
#include "server.h"
#include "proto.h"
#include "medialib.h"

#define MP3_BITRATE 256 *1024

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)
{

    int ret;
    char pathstr[1024]={'\0'};
    char linebuf[1024]={'\0'};
    FILE*fp;
    struct channel_context_st *me;
    static chnid_t curr_id = MINCHNID;
    
    strcat(pathstr,path);
    strcat(pathstr,"/desc.txt");

    fp = fopen(pathstr,"r");
    if(fp ==NULL)
    {
        syslog(LOG_INFO,"[%s:%d] %s is not channel",__FILE__,__LINE__,path);
        syslog(LOG_INFO,"[%s:%d] pathstr = %s",__FILE__,__LINE__,pathstr);
        return NULL;
    }
    if(fgets(linebuf,1024,fp) ==NULL)
    {
        syslog(LOG_INFO,"fgets()");
        fclose(fp);
        return NULL;
    }

    fclose(fp);
    me = malloc(sizeof(*me));
    //strdup拷贝字符串副本,要用free释放。
    me->desc = strdup(linebuf);
    strncpy(pathstr,path,1024);
    strncat(pathstr,"/*.mp3",1024-1);
    pathstr[1024-1] = '\0';
    ret = glob(pathstr,0,NULL,&me->mp3glob);
    if(ret != 0)
    {
        syslog(LOG_ERR,"%s is not a channel",path);
        curr_id++;
        free(me);
        return NULL;
    }
    me->pos = 0;
    me->offset = 0;
    //udp 最多发送65506个字节,不能超过。
    me->tbf = mytbf_init(MP3_BITRATE/8,MP3_BITRATE/8 *10);//记得要初始化。
    me->tbf = mytbf_init(MP3_BITRATE/8*3,MP3_BITRATE/8 *10);//记得要初始化。
    me->fd = open(me->mp3glob.gl_pathv[me->pos],O_RDONLY);
    me->chnid = curr_id;
    curr_id++;
    return me;
}

int mlib_getchnlist(struct mlib_listentry_st**result,int*resnum)
{
    int ret;
    char path[1024];
    glob_t globres;
    int num = 0;
    for(int i=0;i<MAXCHNID+1;i++)
    {
        channel[i].chnid = -1;
    }
    
    snprintf(path,1024,"%s/*",server_conf.media_dir);
    
    ret = glob(path,0,NULL,&globres);
    if(ret != 0)
    {
        return -1;
    }

    struct mlib_listentry_st *ptr;
    struct channel_context_st *res;
    ptr = malloc(sizeof(*ptr)*globres.gl_pathc );
    if(ptr == NULL)
    {
        syslog(LOG_ERR,"malloc error.");
        exit(1);
    }

    for(int i=0;i<globres.gl_pathc;i++)
    {
        res = path2entry(globres.gl_pathv[i]);
        if(res !=NULL)
        {   
            syslog(LOG_DEBUG,"[medialib][mlib_getchnlist] success:[chnid]:%d desc:%s",res->chnid,res->desc);
            memcpy(channel+ res->chnid,res,sizeof(*res));
            ptr[num].chnid = res->chnid;
            ptr[num].desc = strdup(res->desc);
            num++;
        }
    }
    
    *result = realloc(ptr,sizeof(struct mlib_listentry_st)*num);
    
    *resnum = num;
    return 0;
}


int  mlib_freechnlist(struct mlib_listentry_st*ptr)
{
    free(ptr);
    return 0;
}

int open_next(chnid_t chnid)
{
    int i;
    for(i = 0;i<channel[chnid].mp3glob.gl_pathc;i++)
    {
        channel[chnid].pos++;
        if(channel[chnid].pos == channel[chnid].mp3glob.gl_pathc)
        {
            channel[chnid].pos = 0;
            break;
        }
    }
    close(channel[chnid].fd);
    channel[chnid].fd = open(channel[chnid].mp3glob.gl_pathv[channel[chnid].pos],0,O_RDONLY);
    if(channel[chnid].fd<0)
    {
        syslog(LOG_WARNING,"open err");
    }
    else
    {
        channel[chnid].offset = 0;
        return 0;
    }
    return -1;
}
#if 1
ssize_t mlib_readchn(chnid_t chnid,void*buf,size_t size)
{

    int len;
    int tbfsize;
    syslog(LOG_INFO,"mlib_readchn(start) ");

    tbfsize = mytbf_fetchtoken(channel[chnid].tbf,size);
    syslog(LOG_INFO,"fetchtoken tbfsize= %d",tbfsize);

    while(1)
    {
        // pread打开文件指定文件开始读数据
        len = pread(channel[chnid].fd,buf,tbfsize,channel[chnid].offset);
    if(len<0)
    {
        syslog(LOG_WARNING,"media file pread()");
        open_next(chnid);
    }
    else if(len ==0)
    {
        syslog(LOG_DEBUG,"media file over");
        open_next(chnid);
    }
    else
    {
        channel[chnid].offset += len;
        break;
    }
    }
    if(tbfsize - len>0)
    {
        syslog(LOG_INFO,"returntoken()");
        mytbf_returntoken(channel[chnid].tbf,tbfsize-len);
    }
    syslog(LOG_INFO,"mlib_readchn(end) ");
    return len;
}
#endif





mytbf.h

#ifndef MYTBF_H__
#define MYTBF_H__

#define MYTBF_MAX 1024
typedef void mytbf_t;

mytbf_t *mytbf_init(int cps, int burst); // 初始化时需要指定速率和上限
int mytbf_fetchtoken(mytbf_t *, int );
int mytbf_returntoken(mytbf_t *, int );
int mytbf_destroy(mytbf_t *);

#endif

mytbf.c

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/time.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <string.h>
#include <errno.h>
#include <pthread.h>
#include <math.h>
#include "mytbf.h"

struct mytbf_st
{
    int cps;
    int burst;
    int token;
    int pos;
    pthread_mutex_t mut;
    pthread_cond_t cond;
};

static struct mytbf_st *job[MYTBF_MAX];
static pthread_mutex_t mut_job = PTHREAD_MUTEX_INITIALIZER;
static pthread_once_t init_once = PTHREAD_ONCE_INIT;
static pthread_t tid;

static int get_free_pos_unlocked()
{
    for (int i = 0; i < MYTBF_MAX; i++) {
        if (job[i] == NULL) {
            return i;
        }
    }
    return -1;
}

static void *thr_alrm(void *p)
{
    while(1)
    {
        pthread_mutex_lock(&mut_job);
        for (int i = 0; i < MYTBF_MAX; i++)
        {
            if (job[i] != NULL) {
                pthread_mutex_lock(&job[i]->mut);
                job[i]->token +=job[i]->cps;
                if (job[i]->token > job[i]->burst) {
                    job[i]->token = job[i]->burst;
                }
                pthread_cond_broadcast(&job[i]->cond); // 防止有线程在等待token
                pthread_mutex_unlock(&job[i]->mut);
            }
        }
        pthread_mutex_unlock(&mut_job);
        sleep(1);
    }
    return NULL;
}

static void module_unload()
{
    pthread_cancel(tid);
    pthread_join(tid, NULL);

    for (int i = 0; i < MYTBF_MAX; i++) {
        free(job[i]);
    }
    return;
}

static void module_load()
{
    int err = pthread_create(&tid, NULL, thr_alrm, NULL);
    if (err) {
        fprintf(stderr, "[mytbf][module_load] pthread create fail : %s\n", strerror(errno));
        exit(1);
    }
    atexit(module_unload);
}

mytbf_t *mytbf_init(int cps, int burst)
{
    struct mytbf_st *me;

    pthread_once(&init_once, module_load); // 确保module_load在本程序中只执行一次
    me = malloc(sizeof(*me));
    if (me == NULL) {
        return NULL;
    }

    me->cps = cps;
    me->burst = burst;
    me->token = 0;
    pthread_mutex_init(&me->mut, NULL);
    pthread_cond_init(&me->cond, NULL);

    pthread_mutex_lock(&mut_job);
    int pos = get_free_pos_unlocked();
    if (pos < 0) {
        pthread_mutex_unlock(&mut_job);
        free(me);
        return NULL;
    }
    me->pos = pos;
    job[me->pos] = me;
    pthread_mutex_unlock(&mut_job);
    return me;
}
int mytbf_fetchtoken(mytbf_t *ptr, int size)
{
    int n;
    struct mytbf_st *me = ptr;
    pthread_mutex_lock(&me->mut);
    while (me->token <= 0) {
        pthread_cond_wait(&me->cond, &me->mut); // 等待其他线程的通知
    }
    n = me->token < size ? me->token : size;
    me->token -= n;

    pthread_mutex_unlock(&me->mut);
    return n;
}
int mytbf_returntoken(mytbf_t *ptr, int size)
{
    struct mytbf_st *me = ptr;
    pthread_mutex_lock(&me->mut);
    me->token += size;
    if (me->token > me->burst) {
        me->token = me->burst;
    }
    pthread_cond_broadcast(&me->cond);
    pthread_mutex_unlock(&me->mut);
    return 0;
}
int mytbf_destroy(mytbf_t *ptr)
{
    struct mytbf_st *me = ptr;
    pthread_mutex_lock(&mut_job);
    job[me->pos] = NULL;
    pthread_mutex_unlock(&mut_job);

    pthread_mutex_destroy(&me->mut);
    pthread_cond_destroy(&me->cond);
    free(ptr);
    return 0;
}

server.h

#ifndef SERVER_H
#define SERVER_H

#define DEFAULT_MEDIADIR "../../media/" 
//#define DEFAULT_MEDIADIR "/home/qt/media"
#define DEFAULT_IF "ens33"

enum
{
    RUN_DAEMON =1,  //前台运行
    RUN_FOREGROUND //后台运行
};
struct server_conf_st
{
    char* recvport;
    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.c(main入口在这里)

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <net/if.h>
#include <fcntl.h>
#include <syslog.h>
#include <errno.h>
#include <string.h>
#include <signal.h>
#include <bits/sigaction.h>
#include <getopt.h>
#include <arpa/inet.h>

#include "server.h"
#include "proto.h"
#include "medialib.h"
#include "thr_channel.h"
#include "thr_list.h"

/*
-M 指定多播组
-P 指定接受端口
-F 指定前台运行,默认后台运行
-D 指定媒体库位置
-I 指定网络设备网卡
-H 显示帮助信息
*/

struct server_conf_st server_conf = {
    .recvport = DEFAULT_RECVPORT,
    .mgroup = DEFAULT_MGROUP,
    .media_dir = DEFAULT_MEDIADIR,
    .runmode = RUN_DAEMON,
    .ifname = DEFAULT_IF
};
int serversd;
struct sockaddr_in sndaddr;
struct mlib_listentry_st * list;

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

static void daemon_exit(int s)
{
    thr_list_destroy();
    thr_channel_destroyall();
    mlib_freechnlist(list);
    syslog(LOG_WARNING,"signal %d caught.exit now",s);
    closelog();
    exit(0);
}

static int daemonize()
{
    pid_t pid;
    int fd;
    pid = fork();
    if(pid <0)
    {
        syslog(LOG_ERR,"frok failed:%s",strerror(errno));  //不需要写\n
        // perror("fork()");
        return -1;
    }
    if(pid>0)
    {
        exit(0);
    }
    fd = open("/dev/null",O_RDWR);
    if(fd<0)
    {
        syslog(LOG_WARNING,"open():%s ",strerror(errno));
        // perror("open()");
        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()
{

    serversd = socket(AF_INET,SOCK_DGRAM,0);
    if(serversd< 0)
    {
        syslog(LOG_ERR,"socket():%s",strerror(errno));
        exit(1);
    }
    struct ip_mreqn mreq;
    inet_pton(AF_INET,server_conf.mgroup,&mreq.imr_multiaddr);
    inet_pton(AF_INET,"0.0.0.0",&mreq.imr_address); //配置任何的IP地址
    mreq.imr_ifindex = if_nametoindex(server_conf.ifname);

    setsockopt(serversd,IPPROTO_IP,IP_MULTICAST_IF,&mreq,sizeof(mreq));

    //bind可省 先发包
    sndaddr.sin_family = AF_INET;
    sndaddr.sin_port = htons(atoi(server_conf.recvport));
    inet_pton(AF_INET,server_conf.mgroup,&sndaddr.sin_addr.s_addr);
    
    return 0;
}

int main(int argc,char**argv)
{
    int ret;
    struct sigaction sa;
    sa.sa_handler = daemon_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);

    //写系统日志
    openlog("netradio",LOG_PID|LOG_PERROR,LOG_DAEMON);

    // 命令行分析
    int c;
    while(1)
    {
        c = getopt(argc,argv,"M:P:FD:I:H");
        if(c<0)
            break;
        switch(c)
        {
            case 'M':
                server_conf.mgroup = optarg;
                break;
            case 'P':
                server_conf.recvport = optarg;
                break;
            case 'F':
                server_conf.runmode = RUN_FOREGROUND;
                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)
    {
        ret = daemonize();
        if(ret != 0)
            exit(1);
    }
    else if(server_conf.runmode == RUN_FOREGROUND)
    {
        //do nothing...
    }
    else
    {
        syslog(LOG_ERR,"EINVAL server_conf.runmode.");
        //fprintf(stderr,"EINVAL\n");
        exit(1);
    }

    // SOCKET初始化
    
    socket_init();
    syslog(LOG_INFO,"=====socket init success =====");
    // 获取频道信息
    int list_size;
    ret = mlib_getchnlist(&list,&list_size);
    if(ret)
    {
        syslog(LOG_ERR,"mlib_getchanlist():%s",strerror(errno));
        exit(1);
    }
    syslog(LOG_INFO,"=====[server][main] get channel list success====");
    // 创建节目单线程
    ret = thr_list_create(list,list_size);    
    if(ret)
        exit(1);
    syslog(LOG_INFO,"====create list thread success,list_size:%d=====",list_size);
    // 创建频道线程

    int i=0;
#if 1
//    list_size = 1;
    for(i=0;i< list_size;i++)
    {
        ret = thr_channel_create(list +i );
        if(ret)
        {
            exit(1);
        }
    }
#endif
    syslog(LOG_DEBUG,"=====create channel threads success:%d=====",i);
    syslog(LOG_INFO,"创建了%d个频道线程",i);
    
    while(1)
        pause();

    return 0;
}




thr_channel.h

#ifndef THR_CHANNEL_H
#define THR_CHANNEL_H

#include "medialib.h"

int thr_channel_create(struct mlib_listentry_st* );

int thr_channel_destroy(struct mlib_listentry_st*);

int thr_channel_destroyall();

#endif 

 thr_channel.c

#include <stdlib.h>
#include <stdio.h>
#include <pthread.h>
#include <syslog.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <string.h>

#include "thr_channel.h"
#include "server.h"
#include "proto.h"

struct thr_channel_ent_st
{
    chnid_t chnid;
    pthread_t tid;

};
struct thr_channel_ent_st thr_channel[CHNNR];
static int tid_nextpos = 0;

void* thr_channel_snder(void* ptr)
{
    struct msg_channel_st* sbufp;
    struct mlib_listentry_st*ent = ptr;
    int len;
    int ret;
    sbufp  = malloc(MSG_CHANNEL_MAX);
    if(sbufp == NULL)
    {
        exit(1);
    }
    sbufp->chnid = ent->chnid;


    while(1)
    {
        len = mlib_readchn(ent->chnid,sbufp->data,MAX_DATA);
        syslog(LOG_DEBUG,"mlib_readchn()len =%d",len);
        // 65507太大
        ret = sendto(serversd,sbufp,len+sizeof(chnid_t),0,(void* )&sndaddr,sizeof(sndaddr));
        if(ret <0)
        {
            syslog(LOG_ERR,"thr_cahnnel:%d sendto:%s",ent->chnid,strerror(errno));
            exit(1);
        }
        else
        {
            syslog(LOG_DEBUG,"thr_channel(%d):sendto success",ent->chnid);
        }
        sched_yield();//出让调度器
    }
    pthread_exit(NULL);
    return NULL;
}

#if 1
int thr_channel_create(struct mlib_listentry_st*ptr)
{
    int ret;

    ret = pthread_create(&thr_channel[tid_nextpos].tid,NULL,thr_channel_snder,ptr);
    if(ret)
    {
        syslog(LOG_WARNING,"pthread_create:%s",strerror(ret));
        return -ret;
    }
    thr_channel[tid_nextpos].chnid = ptr->chnid;
    tid_nextpos++;
    return 0;

}
#endif 

int thr_channel_destroy(struct mlib_listentry_st*ptr)
{
    int i;
    int ret;
    for(i =0;i<CHNNR;i++)
    {
        if(thr_channel[i].chnid == ptr->chnid)
        {
            ret = pthread_cancel(thr_channel[i].tid);
            if(ret <0)
            {
                syslog(LOG_ERR,"prehad_cancel:%d",ptr->chnid);
                return -ESRCH;
            }
        }
        pthread_join(thr_channel[i].tid,NULL);
        thr_channel[i].chnid = -1;
        break;
    }
    return 0;
}

int thr_channel_destroyall()
{
    int ret;
    for(int i=0;i<CHNNR;i++)
    {
        if(thr_channel[i].chnid>0)
        {
            ret = pthread_cancel(thr_channel[i].tid);
            if(ret<0)
            {
                syslog(LOG_ERR,"thr_channel destroyall failed");
                return -ESRCH;
            }
        }
        pthread_join(thr_channel[i].tid,NULL);
        thr_channel[i].chnid = -1;
    }
    return 0;
}


thr_list.h

#ifndef THR_LIST_H
#define THR_LIST_H

#include "medialib.h"


int thr_list_create(struct mlib_listentry_st*,int);

void  thr_list_destroy();

#endif

thr_list.c

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <pthread.h>
#include <syslog.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <netinet/in.h>

#include "proto.h"
#include "thr_list.h"
#include "server.h"

static pthread_t tid_list;
static int nr_list_ent;
static struct mlib_listentry_st *list_ent;

static void* thr_list(void*p)
{
    int i;
    int size;
    int totalsize;
    struct msg_list_st*entlistp;
    struct msg_listentry_st*entryp;
    totalsize = sizeof(chnid_t);
    
    for(i = 0;i<nr_list_ent;i++)
    {
        syslog(LOG_INFO,"[thr_list]chnid =%d, desc = %s",list_ent[i].chnid,list_ent[i].desc);

        totalsize += sizeof(struct msg_listentry_st) +strlen(list_ent[i].desc);

    }
    entlistp =  malloc(totalsize);
    if(entlistp == NULL)
    {
        syslog(LOG_ERR,"malloc():%s",strerror(errno));
        exit(1);
    }

    entlistp->chnid = LISTCHNID;
    entryp = entlistp->entry;
    
    for( i=0;i<nr_list_ent;i++)
    {
        size = sizeof(struct msg_listentry_st)+ strlen(list_ent[i].desc);
        entryp->chnid = list_ent[i].chnid;
        entryp->len  = htons(size);
        strcpy(entryp->desc,list_ent[i].desc);
        entryp = (void*) (((char*)entryp + size));
    }
    int ret;
    while(1)
    {
        ret = sendto(serversd,entlistp,totalsize,0,(void*)&sndaddr,sizeof(sndaddr)); 
        if(ret <0)
        {
            syslog(LOG_WARNING,"[thr_list]send data fail:%s)",strerror(errno));
        }
        else
        {
            syslog(LOG_DEBUG,"[thr_list]send data Success)");
        }
    sleep(1);
    }
}


int thr_list_create(struct mlib_listentry_st*listp,int nr_ent)
{
    int err;
    list_ent = listp;
    nr_list_ent = nr_ent;
    err = pthread_create(&tid_list,NULL,thr_list,NULL);
    if(err)
    {
        syslog(LOG_ERR,"pthread create");
        return -1;
    }
    return 0;


}

void  thr_list_destroy()
{
    pthread_cancel(tid_list);
    pthread_join(tid_list,NULL);
}



Makefile

CFLAGS += -Wall -I../include/ -pthread -g 
LDFLAGS += -pthread

all:server
server:server.o thr_channel.o thr_list.o mytbf.o medialib.o
	$(CC) $^ -o $@ $(CFLAGS) $(LDFLAGS)

clean:
	rm *.o server -rf

客户端

client.h

#ifndef CLIENT_H__
#define CLIENT_H__

//#define DEFAULT_PLAYERCMD "/usr/bin/mplayer -> /dev/null"   //输出重定向到空设备
 #define DEFAULT_PLAYERCMD "/usr/bin/mpg123 -  > /dev/null"   //输出重定向到空设备

struct client_conf_st
{
    char* recvport;
    char* mgroup;
    char* player_cmd;
};

extern struct client_conf_st client_conf; //外部扩展




#endif

client.c

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <arpa/inet.h>
#include <getopt.h> //getopt_long需要包含
#include <net/if.h>
#include <string.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>

#include "proto.h"

#include "client.h"
/*
-M --mgroup 指定多播组
-P --port   指定接受端口
-p --player 指定播放器
-H --help   显示帮助
*/
void printhelp()
{
    printf("-M --mgroup 指定多播组\n");
    printf("-P --port   指定接受端口\n");
    printf("-p --player 指定播放器\n");
    printf("-H --help   显示帮助\n");
}

struct client_conf_st client_conf = {\
    .recvport = DEFAULT_RECVPORT,\
    .mgroup = DEFAULT_MGROUP,\
    .player_cmd = DEFAULT_PLAYERCMD \
    };

ssize_t writen(int fd,const unsigned char*buf,size_t len)
{
    int ret;
    int 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 ret;
    int index= 0;
    int c;
    struct option argarr[] ={ {"mgroup",1,NULL,'M'},{"port",1,NULL,'P'}, \
                              {"player",1,NULL,'p'},{"help",1,NULL,'H'}, \
                              {NULL,0,NULL,0} };

#if 1
    while(1)
    {
        c = getopt_long(argc,argv,"M:P:p:H",argarr,&index);
        if(c<0)
            break;
        switch(c)
        {
            case 'M':
                client_conf.mgroup = optarg;
                break;
            case 'P':
                client_conf.recvport = optarg;
                break;
            case 'p':
                client_conf.player_cmd = optarg;
                break;
            case 'H':
                printhelp();
                exit(0);
                break;
            default:
                abort(); //参数错误发送abort信号。
                break;
        }
    }
    printf("\n==========[client] getopt success========\n");
    int sd,pid;
    sd = socket(AF_INET,SOCK_DGRAM,0);
    if(sd<0)
    {
        perror("socket()");
        exit(1);
    }
    printf("\n==========[client] get socket success========\n");

    struct ip_mreqn mreq;
    struct sockaddr_in laddr;
    int val = 1;

    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("ens33"); 
    setsockopt(sd,IPPROTO_IP,IP_ADD_MEMBERSHIP,&mreq,sizeof(mreq));
    ret = setsockopt(sd,IPPROTO_IP,IP_MULTICAST_LOOP,&val,sizeof(val));
    if(ret<0)
    {
        perror("setsockopt()");
        exit(1);
    }
    printf("\n==========[client] set socketopt success========\n");

#if 1
    laddr.sin_family =AF_INET;
    laddr.sin_port = htons(atoi(client_conf.recvport)); //主机字节序转为网络字节序
    ret = inet_pton(AF_INET,"0.0.0.0",&laddr.sin_addr.s_addr);
    if(ret <0)
    {
        perror("[client] inet_pton failed");
        exit(1);
    }

    ret = bind(sd,(void*)&laddr,sizeof(laddr)); //被动段,先接受包,需要bind
   if(ret <0)
   {
        printf("bind() failed:%s\n",strerror(errno));

        exit(1);
   }
    printf("\n==========[client] bind success========\n");
#endif
    int pd[2]; 
    ret = pipe(pd);
    if(ret<0)
    {
        perror("pipe() fail");
        exit(1);
    }

    pid = fork();
    if(pid<0)
    {
        perror("fork");
        exit(1);
    }
    if(pid ==0)
    {
    printf("\n==========[client] child process========\n");
        close(sd);
        close(pd[1]);
        dup2(pd[0],0); //将管道的读端,作为标准输入
        if(pd[0]>0)
            close(pd[0]);

    printf("\n==========[client] child:start play ========\n");
        //调用解码器。
        execl("/bin/sh","sh","-c",client_conf.player_cmd,NULL);
        perror("execl()");
        exit(1);
    }
    else if(pid >0)
    {
        close(pd[0]);
        //父进程:从网络收包,发给子进程
        struct msg_list_st *msg_list;
        struct sockaddr_in serveraddr;
        socklen_t serveraddr_len;
        serveraddr_len = sizeof(serveraddr);

        msg_list = malloc(MSG_LIST_MAX);
        if(msg_list ==NULL)
        {
            perror("malloc()");
            exit(1);
        }
    printf("\n==========[client] parent:recvdata========\n");
        
        // 收节目单
        int len;
        while(1)
        {
        len = recvfrom(sd,msg_list,MSG_LIST_MAX,0,(void*)&serveraddr,&serveraddr_len);
        if(len < sizeof(struct msg_list_st))
        {
            fprintf(stderr,"mesage is tool small\n");
            continue;
        }
        if(msg_list->chnid != LISTCHNID)
        {
            fprintf(stderr,"chnid is not match\n");
            continue;
        }
        break;
        }
        // 打印节目单,选择频道
        struct msg_listentry_st* pos;
        int chosenid;
        pos = msg_list->entry;
        for(;(char*)pos< (char*)msg_list+len;pos= (void*)( (char*)pos+ ntohs(pos->len) ) )
        {
            printf("[client]channel %d:%s \b",pos->chnid,pos->desc);
        }
        free(msg_list);
        while(1)
        {
            puts("Please entry:");
            ret = scanf("%d",&chosenid);
            if(ret != 1)
                exit(1);
            if(ret == 1)
                break;
        }
        fprintf(stdout,"choosenid=%d\n",chosenid); 
        // 收频道包,发给子进程
        struct sockaddr_in raddr;
        socklen_t raddr_len;
        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)
        {
            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:address no  match\n.");
                continue;
            }
            if(len <sizeof(struct msg_channel_st))
            {
                fprintf(stderr,"Ignore:message too small.");
                continue;
            }
            if(msg_channel->chnid == chosenid)
            {
                fprintf(stdout,"accpect msg:%d recieved.\n",msg_channel->chnid);
                len =writen(pd[1],msg_channel->data,len-sizeof(chnid_t));
                if(len <0 )
                {
                    exit(1);
                }
            }
        }
        free(msg_channel);
        close(sd);
    }
#endif
    return 0;
}







Makefile

CFLAGS += -I../include/ -Wall

all:client

client:client.o
	gcc $^ -o $@ $(CFLAGS)

clean:
	rm *.o client

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

only-lucky

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值