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(struct msg_list_st))
            {
                fprintf(stderr, "massage is too short.\n");
                continue;
            }
            if(msg_list->chnid != LISTCHNID)
            {
                fprintf(stderr, "current chnid:%d.\n", msg_list->chnid);
                fprintf(stderr, "chnid is not match.\n");
                continue;
            }
            break;
        }

        //printf programme, select channel
        /*
        1.music xxx
        2.radio xxx
        3.....
        */
        //receive channel package, send it to child process
        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%s", pos->chnid, pos->desc);
        }
        /*free list*/
        free(msg_list);
        puts("Please input your choose:");
        while (ret < 1)
        {
            ret = scanf("%d", &chosenid);
            if(ret != 1)
                exit(1);
        } 

        msg_channel = malloc(MSG_CHANNEL_MAX);
        if(msg_channel == NULL)
        {
            perror("malloc");
            exit(1);
        }
        raddr_len = sizeof(raddr);
        char ipstr_raddr[30];
        char ipstr_server_addr[30];

        while(1)
        {
            len = recvfrom(sd, msg_channel, MSG_CHANNEL_MAX, 0, (void*)&raddr, &raddr_len);

            #ifdef DEBUG
            fprintf(stderr, "raddr:%d\n", raddr.sin_addr.s_addr);
            #endif
            //防止有人恶意发送不相关的包
            if(raddr.sin_addr.s_addr != server_addr.sin_addr.s_addr)
            {
                #ifdef DEBUG
                    inet_ntop(AF_INET, &raddr.sin_addr.s_addr, ipstr_raddr, 30);
                    inet_ntop(AF_INET, &server_addr.sin_addr.s_addr, ipstr_server_addr, 30);
                    fprintf(stderr, "Ignore:addr not match. raddr:%s server_addr:%s.\n", ipstr_raddr, ipstr_server_addr);
                #endif
                continue;
            }
            if(raddr.sin_port != server_addr.sin_port)
            {
                fprintf(stderr, "Ignore:port not match.\n");
                continue;
            }
            if(len < sizeof(struct msg_channel_st))
            {
                fprintf(stderr, "Ignore:massage too short.\n");
                continue;
            }

            //可以做一个缓冲机制,停顿1  2 秒,不采用接收一点播放一点
            if(msg_channel->chnid == chosenid)
            {
                fprintf(stdout, "Accept massage:%d recived.\n", msg_channel->chnid);
                if( writen(pd[1], msg_channel->data, len - sizeof(chnid_t)) < 0)/*write pipe*/
                    exit(1);
                    
            }
        }

        free(msg_channel);
        close(sd);
        exit(0);
    }

}

二、服务端

流程:获取命令行参数,没有特殊要求则为默认,根据用户选择,若为后台模式则运行为守护进程,打开系统日志,建立媒体库(可以引入数据库或者就用linux的文件系统),从媒体库中获取节目单信息创建节目单线程,读取每个频道的数据创建多个频道线程,在从媒体库读取数据的期间引用流量控制(令牌桶)。

server.h

#ifndef SERVER_CONF_H_
#define SERVER_CONF_H_

#define DEFAULT_MEDIADIR "medialib"
#define DEFAULT_IF "ens33"

enum
{
    RUN_DAEMON = 1,
    RUN_FOREGROUND
};
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.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <syslog.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <error.h>
#include <errno.h>
#include <string.h>
#include <signal.h>
#include <sys/socket.h>
#include <netinet/ip.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <net/if.h>
#include "server_conf.h"
#include "medialib.h"
#include "thr_channel.h"
#include "thr_list.h"
#include "../../include/proto.h"

#define DEBUG

/*
-M specify multicast group
-P specify receive port
-F foreground
-D specify medialib location
-I specify net card
-H show help
*/

int serversd;
struct sockaddr_in sndaddr;
struct server_conf_st server_conf = {\
    .rcvport = DEFAULT_RCVPORT,\
    .media_dir = DEFAULT_MEDIADIR,\
    .runmode = RUN_FOREGROUND,\
    .ifname = DEFAULT_IF,
    .mgroup = DEFAULT_MGROUP};
static struct mlib_listentry_st *list;
 
static void print_help()
{
    printf("-P    specify receive port\n");
    printf("-M    specify multicast group\n");
    printf("-F    specify foreground runmode \n");
    printf("-D    specify medialib location \n");
    printf("-I    specify net card\n");
    printf("-H    show help\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)
    {
        //perror("fork()");
        syslog(LOG_ERR, "fork() failed:%s", strerror(errno));
        return -1;
    }

    if(pid > 0)//parent
        exit(0);

    umask(0);
    setsid();

    fd = open("/dev/null", O_RDWR);
    if(fd < 0)
    {
        //perror("open()");
        syslog(LOG_ERR, "open() failed:%s", strerror(errno));
        return -2;
    }
    else
    {
        /*close stdin, stdout, stderr*/
        dup2(fd, 0);
        dup2(fd, 1);
        dup2(fd, 2);
        if(fd > 2)
            close(fd);
    }

    chdir("/");

    return 0;

}

static int socket_init()
{
    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);//local address
    mreq.imr_ifindex = if_nametoindex(server_conf.ifname);//net card

    serversd = socket(AF_INET, SOCK_DGRAM, 0);
    if(serversd < 0)
    {
        syslog(LOG_ERR, "socket():%s", strerror(errno));
        exit(1);
    }
    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);
    //inet_pton(AF_INET, "0.0.0.0", &sndaddr.sin_addr.s_addr);
    
    return 0;
}

int main(int argc, char * const argv[])
{
    /*variable*/
    int c;
    struct sigaction sa;

    /*signal content*/
    /*daemon process receive these signal, go to daemon_exit function*/
    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);
    #ifdef DEBUG
        fprintf(stdout, "here1!\n");
    #endif
    /*analyse argument*/
    while(1)
    {
        c = getopt(argc, argv, "M:P:FD:I:H");//:-->has parameter
        #ifdef DEBUG
            fprintf(stdout, "here2!\n");
        #endif
        printf("get command c:%c\n", c);
        if(c < 0)
            break;
        switch(c)
        {
            case 'M':
                server_conf.mgroup = optarg;
                break;
            case 'P':
                server_conf.rcvport = optarg;
                break;
            case 'F':
                #ifdef DEBUG
                    fprintf(stdout, "here3!\n");
                #endif
                //server_conf.runmode = RUN_FOREGROUND;
                break;
            case 'D':
                server_conf.media_dir = optarg;
                break;
            case 'I':
                server_conf.ifname = optarg;
                break;
            case 'H':
                print_help();
                exit(0);
                break;
            default:
                abort();
                break;
        }
        break;
    }
    #ifdef DEBUG
        printf("server_conf.runmode:%d", server_conf.runmode);
    #endif
    /*daemon process*/
    if(server_conf.runmode == RUN_DAEMON)
        if(daemonize() != 0)
        {
            perror("daemonize()");
        }
    else if(server_conf.runmode == RUN_FOREGROUND)
    {

    }
    else
    {
        syslog(LOG_ERR, "EINVAL server_conf.runmode.");
        exit(1);
    }
    
    /*SOCKET initalize*/
    socket_init();
    /*get channel information*/
    int list_size;
    int err;
    //list 频道的描述信息
    //list_size有几个频道
    err = mlib_getchnlist(&list, &list_size);
    if(err)
    {   
        syslog(LOG_ERR, "mlib_getchnlist():%s", strerror(errno));
        exit(1);
    }

    /*create programme thread*/
    thr_list_create(list, list_size);
    /*if error*/

    /*create channel thread*/
    //创建频道线程
    int i = 0;
    for( i = 0; i < list_size; i++)
    {
        err = thr_channel_create(list + i);
        if(err)
        {
            fprintf(stderr, "thr_channel_create():%s\n", strerror(err));
            exit(1);
        }
    }
    syslog(LOG_DEBUG, "%d channel threads created.", i);
    while(1)
        pause();
}

媒体库.c

#include <stdio.h>
#include <stdlib.h>
#include <glob.h>
#include <syslog.h>
#include <string.h>
#include <errno.h>
#include <error.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "server_conf.h"
#include "medialib.h"
#include "../../include/proto.h"
#include "mytbf.h"

#define DEBUG

#define PATHSIZE 1024
#define PATHSTRSIZE 4096
#define LINEBUFSIZE 1024

#define MP3_BITRATE 64*1024 //correct bps:128*1024
//#define MP3_BITRATE 1024

struct channel_context_st
{
    chnid_t chnid;
    char *desc;
    glob_t mp3glob;
    int pos;  //cur song
    int fd;  //file descriptor
    off_t offset; //偏移量
    mytbf_t *tbf;
};

static struct channel_context_st channel[MAXCHIND + 1];

static struct channel_context_st *path2entry(const char* path)
{
    // path/desc.text  path/*.mp3
    syslog(LOG_INFO, "current path :%s\n", path);
    char pathstr[PATHSTRSIZE] = {'\0'};
    char linebuf[LINEBUFSIZE];
    FILE *fp;
    struct channel_context_st *me;
    static chnid_t curr_id = MINCHNID;

    /* 目录下存在desc.txt文件才为可用目录 */
    strcat(pathstr, path);
    strcat(pathstr, "/desc.txt");
    fp = fopen(pathstr, "r");
    syslog(LOG_INFO, "channel dir:%s\n", pathstr);
    if(fp == NULL)
    {
        syslog(LOG_INFO, "%s is not a channel dir(can't find desc.txt)", path);
        return NULL;
    }
    if(fgets(linebuf, LINEBUFSIZE, fp) == NULL)
    {
        syslog(LOG_INFO, "%s is not a channel dir(can't get the desc.text)", path);
        fclose(fp);
        return NULL;
    }
    fclose(fp);
    me = malloc(sizeof(*me));
    if(me == NULL)
    {
        syslog(LOG_ERR, "malloc():%s", strerror(errno));
        return NULL;
    }

    /* 初始化流控 */
    me->tbf = mytbf_init(MP3_BITRATE/8, MP3_BITRATE/8*5);
    if(me->tbf == NULL)
    {
        syslog(LOG_ERR, "mytbf_init():%s", strerror(errno));
        free(me);
        return NULL;
    }

    /* 解析目录下的mp3文件 */
    me->desc = strdup(linebuf);
    strncpy(pathstr, path, PATHSIZE);
    strncat(pathstr, "/*.mp3", PATHSIZE);
    if(glob(pathstr, 0, NULL, &me->mp3glob) != 0)
    {
        curr_id++;
        syslog(LOG_ERR, "%s is not a channel dir(can not find mp3 files", path);
        free(me);
        return NULL;
    }
    me->pos = 0;
    me->offset = 0;
    me->fd = open(me->mp3glob.gl_pathv[me->pos], O_RDONLY);
    if(me->fd <0 )
    {
        syslog(LOG_WARNING, "%s open failed.",me->mp3glob.gl_pathv[me->pos]);
        free(me);
        return NULL;
    }
    me->chnid = curr_id;
    curr_id++;
    return me;
}

int mlib_getchnlist(struct mlib_listentry_st **result, int *resnum)
{
    /*variable*/
    int num = 0;
    int i = 0;
    char path[PATHSIZE];
    glob_t globres;
    struct mlib_listentry_st *ptr;
    struct channel_context_st *res;

    for(int i = 0 ; i < MAXCHIND; i++)
    {
        channel[i].chnid = -1;
    }

    snprintf(path, PATHSIZE, "%s/*", server_conf.media_dir);
    #ifdef DEBUG
        printf("current path:%s\n", path);
    #endif

    if(glob(path, 0, NULL, &globres))
    {
        return -1;
    }

    #ifdef DEBUG
        printf("globres.gl_pathv[0]:%s\n", globres.gl_pathv[0]);
        printf("globres.gl_pathv[1]:%s\n", globres.gl_pathv[1]);
        printf("globres.gl_pathv[2]:%s\n", globres.gl_pathv[2]);
    #endif
   /* 先分配最大可用目录的空间 */
    ptr = malloc(sizeof(struct mlib_listentry_st) * globres.gl_pathc);
    if(ptr == NULL)
    {
        syslog(LOG_ERR, "malloc() error.");
        exit(1);
    }

    for(i = 0; i < globres.gl_pathc; i++)
    {
        //globres.gl_pathv[i] -->"/var/media/ch1"
        res = path2entry(globres.gl_pathv[i]);//path-->record
        if(res != NULL)
        {
            syslog(LOG_ERR, "path2entry() return : %d %s.", res->chnid, res->desc);
            memcpy(channel+res->chnid, res, sizeof(*res));
            ptr[num].chnid = res->chnid;
            ptr[num].desc = strdup(res->desc);
            num++;
        }
    }
    /* 重新分配num个有效空间 */
    *result = realloc(ptr, sizeof(struct mlib_listentry_st) * num);
    if(*result == NULL)
    {
        syslog(LOG_ERR, "realloc() failed.");
    }
    *resnum = num;
    return 0;
    

}

int mlib_freechnlist(struct mlib_listentry_st *ptr)
{
    free(ptr);
    return 0;
}
//当前是失败了或者已经读取完毕才会调用open_next
static int open_next(chnid_t chnid)
{
    //加入循环是为了防止每一首歌都打开失败,尽量都试着打开一次
    for(int i = 0 ; i < channel[chnid].mp3glob.gl_pathc;i++)
    {
        channel[chnid].pos++;
        //can open any file in channel[chnid].mp3glob.gl_pathv
        //所有的歌曲都没有打开
        if(channel[chnid].pos == channel[chnid].mp3glob.gl_pathc)
        {
            //channel[chnid].pos = 0;//再来一次
            return -1;
            break;//最后一首歌已经打开完毕,结束
        }
        close(channel[chnid].fd);
        channel[chnid].fd = open(channel[chnid].mp3glob.gl_pathv[channel[chnid].pos], O_RDONLY);//对应频道的歌名
        //如果打开还是失败
        if(channel[chnid].fd < 0)
        {
            syslog(LOG_WARNING, "open(%s):%s", channel[chnid].mp3glob.gl_pathv[chnid], strerror(errno));
        }
        else//success
        {
            channel[chnid].offset = 0;
            return 0;
        }
    }
    syslog(LOG_ERR, "None of mp3 in channel %d id available.", chnid);

}
//从每个频道中读取内容,将当前播放的歌曲待发送的数据写到buf,实际大小为size
ssize_t mlib_readchn(chnid_t chnid, void *buf, size_t size)
{
    int tbfsize;
    int len;
    int next_ret = 0;

    //get token number
    tbfsize = mytbf_fetchtoken(channel[chnid].tbf, size);
    syslog(LOG_INFO, "current tbf():%d", mytbf_checktoken(channel[chnid].tbf));
    

    while(1)
    {
        len = pread(channel[chnid].fd, buf, tbfsize,channel[chnid].offset);
        /*current song open failed*/
        if(len < 0)
        {
            //当前这首歌可能有问题,错误不至于退出,读取下一首歌
            syslog(LOG_WARNING, "media file %s pread():%s", channel[chnid].mp3glob.gl_pathv[channel[chnid].pos], strerror(errno));
            open_next(chnid);
        }
        else if(len == 0)
        {
            syslog(LOG_DEBUG, "media %s file is over", channel[chnid].mp3glob.gl_pathv[channel[chnid].pos]);
            #ifdef DEBUG
                printf("current chnid :%d\n", chnid);
            #endif
            next_ret = open_next(chnid);
            break;
        }
        else/*len > 0*///真正读取到了数据
        {
            channel[chnid].offset += len;
            syslog(LOG_DEBUG, "epoch : %f", (channel[chnid].offset) / (16*1000*1.024));
            break;
        }
    }

    //remain some token
    if(tbfsize - len > 0)
        mytbf_returntoken(channel[chnid].tbf, tbfsize - len);
    return len;//返回读取到的长度

    
}

令牌桶.c

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>
#include <error.h>
#include <pthread.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/select.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 once_init = PTHREAD_ONCE_INIT;
static pthread_t tid;

/* select定时器 */
void selectAlrm(int sec)
{
   struct timeval tm;

   tm.tv_sec = sec;
   tm.tv_usec = 0;

   select(0, NULL, NULL, NULL, &tm);
   return ;
}

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);
                pthread_mutex_unlock(&job[i]->mut);
            }
        }
        pthread_mutex_unlock(&mut_job);
        selectAlrm(1);
    }
}

static  void module_unload()
{
    int i;
    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;
    err = pthread_create(&tid, NULL, thr_alrm, NULL);
    if(err)
    {
        fprintf(stderr, "pthread_create():%s", strerror(errno));
        exit(1);
    }
    atexit(module_unload);
}

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

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

    module_load();
    pthread_once(&once_init, module_load);
    int pos;
    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);
    pos = get_free_pos_unlocked();
    if(pos < 0)
    {
        pthread_mutex_unlock(&mut_job);
        fprintf(stderr, "no free position.\n");
        free(me);
        exit(1);
    }
    me->pos = pos;
    job[me->pos] = me;
    pthread_mutex_unlock(&mut_job);
    return me;
}

static int min(int a, int b)
{
    return a < b ? a : b;
}

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 = min(me->token, size);
    me->token -= n;
    pthread_cond_broadcast(&me->cond);
    pthread_mutex_unlock(&me->mut);
    return n;
}

int mytbf_checktoken(mytbf_t *ptr)
{
    int token_left = 0;
    struct mytbf_st *me = ptr;
    pthread_mutex_lock(&me->mut);
    token_left = me->token;
    pthread_mutex_unlock(&me->mut);
    return token_left;
}

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_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;
}

节目单线程

#include <errno.h>
#include <stdlib.h>
#include <stdio.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 <netinet/ip.h>
#include "../../include/proto.h"
#include "thr_list.h"
#include "server_conf.h"
#include "medialib.h"
#include "mytbf.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 totalsize;
    struct msg_list_st *entlistp;
    struct msg_listentry_st *entryp;
    int ret;
    int size;

    totalsize = sizeof(chnid_t);
    for(int i = 0; i < nr_list_ent;i++)
    {
        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;
    syslog(LOG_DEBUG, "nr_list_entn:%d\n", nr_list_ent);
    for(int 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);
        syslog(LOG_DEBUG, "entryp len:%d\n", entryp->len);
    }

    while(1)
    {
        syslog(LOG_INFO, "thr_list sndaddr :%d\n", sndaddr.sin_addr.s_addr);
        ret = sendto(serversd, entlistp, totalsize, 0, (void*)&sndaddr, sizeof(sndaddr));
        syslog(LOG_DEBUG, "sent content len:%d\n", entlistp->entry->len);
        if(ret < 0)
        {
            syslog(LOG_WARNING, "sendto(serversd, enlistp...:%s", strerror(errno));
        }
        else
        {
            syslog(LOG_DEBUG, "sendto(serversd, enlistp....):success");

        }
        selectAlrm(1);

    }
}
//创建节目单线程
int thr_list_create(struct mlib_listentry_st *listp, int nr_ent)
{
    int err;
    list_ent = listp;
    nr_list_ent = nr_ent;
    syslog(LOG_DEBUG, "list content: chnid:%d, desc:%s\n", listp->chnid, listp->desc);
    err = pthread_create(&tid_list, NULL, thr_list, NULL);
    if(err)
    {
        syslog(LOG_ERR, "pthread_create():%s", strerror(errno));
        return -1;
    }
    return 0;
}

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

频道线程

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

static int tid_nextpos = 0;
//each record,每一个线程负责一个频道
struct thr_channel_ent_st
{
    chnid_t chnid;
    pthread_t tid;

};
//thr_channel是一个结构体数组,每一个元素表示一个结构体,结构题内容:频道号,处理该频道线程号
struct thr_channel_ent_st thr_channel[CHANNR];

//对应频道的线程的处理函数
static void *thr_channel_snder(void* ptr)
{
    struct msg_channel_st *sbufp;
    int len;
    struct mlib_listentry_st *ent = ptr;//chnid, desc
    sbufp = malloc(MSG_CHANNEL_MAX);
    if(sbufp == NULL)
    {
        syslog(LOG_ERR, "malloc():%s", strerror(errno));
        exit(1);
    }
    sbufp->chnid = ent->chnid;//频道号处理
    //频道内容读取
    while(1)
    {
        //len = mlib_readchn(ent->chnid, sbufp->data, MAX_DATA);
        len = mlib_readchn(ent->chnid, sbufp->data, 128*1024/8);
        //if(ent->chnid == 3)
        syslog(LOG_DEBUG, "mlib_readchn() len: %d", len);
        //len+sizeof(chnid_t)表示需要发送的数据的长度
        if(sendto(serversd, sbufp, len + sizeof(chnid_t), 0, (void*)&sndaddr, sizeof(sndaddr)) < 0)
        {
            syslog(LOG_ERR, "thr_channel(%d):sendto():%s", ent->chnid, strerror(errno));
            break;

        }
        sched_yield();
    }
    pthread_exit(NULL);

}
//创建一个处理频道的线程
int thr_channel_create(struct mlib_listentry_st *ptr)
{
    int err;
    err = pthread_create(&thr_channel[tid_nextpos].tid, NULL, thr_channel_snder, ptr);
    if(err)
    {
        syslog(LOG_WARNING, "pthread_create():%s", strerror(err));
        return -err;
    }
    thr_channel[tid_nextpos].chnid = ptr->chnid;//填写频道信息
    tid_nextpos++;
    return 0;

}
//销毁对应频道线程
int thr_channel_destroy(struct mlib_listentry_st *ptr)
{
    for(int i = 0; i < CHANNR; i++)
    {
        if(thr_channel[i].chnid == ptr->chnid)
            {
                if(pthread_cancel(thr_channel[i].tid) < 0)
                {
                    syslog(LOG_ERR, "pthread_cancel():thr thread of channel%d", ptr->chnid);
                    return -ESRCH;
                }
            }
        pthread_join(thr_channel[i].tid, NULL);
        thr_channel[i].chnid = -1;
        return 0;
    }
}
//销毁全部频道线程
int thr_channel_destroyall(void)
{
    for(int i = 0; i < CHANNR; i++)
    {
        if(thr_channel[i].chnid > 0)
        {
            if(pthread_cancel(thr_channel[i].tid) < 0)
            {
                syslog(LOG_ERR, "pthread_cancel():thr thread of channel:%ld", thr_channel[i].tid);
                return -ESRCH;
            }
            pthread_join(thr_channel[i].tid, NULL);
            thr_channel[i].chnid =  -1;
        }
    }
}

程序具体代码(包括Makefile和媒体库)如有需要可移步gitee下载,欢迎讨论交流。

gitee地址:

C代码: linux下的基本c代码

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值