一、简介
一个简单的流媒体项目,基于客户端/服务器模型(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;
}
}
}