项目分析:基于UDP的IPV4流媒体广播,主要广播mp3文件,根据李慧芹老师的课学习完成。
代码下载:放最后了(包括QT实现客户端)
服务器主要完成内容:
1)参数初始化,命令行设置
2)网络配置
3)守护进程设置
4)节目单获取
5)节目单发送线程
6)各频道节目发送线程
客户端主要完成内容:
1)参数初始化,命令行设置
2)网络配置
3)接收节目单 选择频道
4)父进程读取频道数据 写给子进程
5)子进程读取父进程数据 交给媒体解析器解析
客户端和服务器数据交换文件,便是共用库文件来源:proto.h,主要包含频道包结构体和节目单包结构体
#ifndef PROTO_H__
#define PROTO_H__
#define DEFAULT_MGROUP "224.2.2.2"
#define DEFAULT_RCVPORT "6666"
#define CHNNR 100
#define LISTCHNID 0
#define MINCHNID 1
#define MAXCHNID (CHNNR+MINCHNID -1)
#define MSG_CHANNEL_MAX (65536 -20 -8) //推荐长度 -ip包的长度 - udp包的报头
#define MAX_DATA (MSG_CHANNEL_MAX - sizeof(chnid_t))
#define MSG_LIST_MAX (65536 -20 -8) //推荐长度 -ip包的长度 - udp包的报头
#define MAX_ENTRY (MSG_LIST_MAX - sizeof(chnid_t))
#include "sitetype.h"
/* 一个频道包的结构体 */
struct msg_channel_st
{
chnid_t chinid;
uint8_t data[1];
}__attribute__((packed));
/* 节目包结构体 */
struct msg_listentry_st
{
chnid_t chnid; /* channel num */
uint16_t len;
uint8_t desc[1]; /* describe channel information*/
}__attribute__((packed));
/* 总包结构体 包括节目单和节目描述*/
struct msg_list_st
{
chnid_t chnid; /* channel num */
struct msg_listentry_st entry[1];
}__attribute__((packed));
#endif
其中包含的sitetype.h是为了隐藏实际用的数据类型
#ifndef SITETYPE_H__
#define SITETYPE_H__
#include
typedef uint8_t chnid_t;
#endif
主函数文件:(根据服务器要完成的各个步骤分步骤完成),其中节目单获取部分我与李慧芹老师讲的写的不同,我直接使用的是发送节目单的结构体struct msg_list_st
,李慧芹老师使用的是 struct mlib_listentry_st
,各函数功能和代码注释已在代码中标明。
server.c 如下:
#include
#include
#include "proto.h"
#include "server.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include /* superset of previous */
#include
#include "../include/mlib.h"
#include "list.h"
#include "channel.h"
#include
/* 需要释放的malloc空间个数 */
#define PTRMAXNUM 10
/*
* -M Specify a multicast group
* -P designated port
* -F Front desk operation
* -D designate media library location
* -H display help information
*
* */
/* 初始化参数配置*/
struct server_conf_st server_conf = {
.rcvport = DEFAULT_RCVPORT,
.mgroup = DEFAULT_MGROUP,
.runmode = RUN_DAEMON,
.media_dir = DEFAULT_MEDIADIR,
.ifname = DEFAULT_IF
};
int sfd;
struct ip_mreqn mreqn;
struct sockaddr_in sndaddr;
void *freeptr[PTRMAXNUM];
int free_ptrnum;
static void printf_help()
{
printf(" * -M --mgroup 指定多播组\n\
* -P --port 指定接受端口\n\
* -F --player 指定前台运行\n\
* -D 指定媒体库文件目录\n\
* -H --help 显示帮助* \n");
}
/* 把当前进程设置为守护进程,一般为固定步骤 */
static int daemonize() // create daemon process
{
pid_t pid;
int fd;
pid = fork();
if (pid < 0)
{
syslog(LOG_ERR, "fork():%s", strerror(errno));
perror("fork()");
exit(1);
}
if (pid > 0)
{
exit(0);
}
fd = open("/dev/null", O_RDWR);
if (fd < 0)
{
syslog(LOG_ERR, "open():%s", strerror(errno));
perror("open()");
exit(1);
}
dup2(fd, 0);
dup2(fd, 1);
dup2(fd, 2); //脱离标准终端
if (fd > 2) //防止内存泄漏
{
close(fd);
}
setsid();
if (chdir("/") < 0) //把守护进程指定到根目录
{
syslog(LOG_ERR, "chdir()%s", strerror(errno));
}
umask(0);
return 0;
}
/* 异常退出,之后执行线程收尸 结构体指针释放 */
static void daemon_exit() //用于被信号终止之后调用
{
for(int i = 0; i < free_ptrnum; i++)
{
free(freeptr[i]);
}
thr_list_destroy();
syslog(LOG_INFO, "daemon_exit():%s", strerror(errno));
closelog();
close(sfd);
exit(0);
}
/* 网络设置函数 */
static int socket_init()
{
sfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sfd < 0)
{
syslog(LOG_ERR, "socket():%s", strerror(errno));
exit(1);
}
inet_pton(AF_INET, server_conf.mgroup, &mreqn.imr_multiaddr);
inet_pton(AF_INET, "0.0.0.0", &mreqn.imr_address);
mreqn.imr_ifindex = if_nametoindex("ens33");
if (setsockopt(sfd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreqn, sizeof(mreqn)) < 0) //man 7 ip 把当前sdf加入到多播组
{
syslog(LOG_ERR, "setsockopt():%s", strerror(errno));
exit(1);
}
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)
{
int c;
/* 异常信号处理 */
struct sigaction sa; //设置要处理的信号集合
sa.sa_handler = daemon_exit; //处理函数
sigemptyset(&sa.sa_mask); //清空信号集里的信号
sigaddset(&sa.sa_mask, SIGQUIT); //把指定信号加入信号集
sigaddset(&sa.sa_mask, SIGTERM);
sigaddset(&sa.sa_mask, SIGINT);
sigaction(SIGTERM, &sa, NULL); // 对指定信号处理
sigaction(SIGINT, &sa, NULL);
sigaction(SIGQUIT, &sa, NULL);
openlog("netradio", LOG_PID | LOG_PERROR, LOG_DAEMON); //打开系统日志
/*1) cmd _ analyse*/
while (1)
{
c = getopt(argc, argv, "M:P:FD:H");
if (c < 0)
{
break;
}
switch (c)
{
case 'M':
server_conf.mgroup = optarg;
break;
case 'P':
server_conf.rcvport = optarg;
break;
case 'F':
server_conf.runmode = RUN_FOREGROUND;
break;
case 'D':
server_conf.media_dir = optarg;
break;
case 'H':
printf_help();
exit(0);
break;
default:
abort();
break;
}
}
/* 2) initialize socket */
socket_init();
/* 3) come ture 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.");
fprintf(stderr, "EINVAL\n");
exit(1);
}
/* 4) obtain channel information*/
struct msg_list_st *list = NULL; //节目单总列表
int list_num = 0; //节目单列表里频道个数
int err;
err = mlib_getchnlist(&list, &list_num); //获取回填
if (err)
{
syslog(LOG_ERR, "mlib_getchnlist()%s", strerror(errno));
exit(1);
}
struct msg_listentry_st *ptr;
#ifdef DEBUG
printf("list_num = %d\n", list_num);
/*****变长结构体的一般处理方式,不能使用list[i]进行访问********************/
int i = 0;
for (ptr = list->entry; i < list_num; ptr = (void *)((char *)ptr) + ntohs(ptr->len))
{
i++;
printf("channel:%d:%s", ptr->chnid, ptr->desc);
}
#endif // DEBUG
/* 5) create program list pthread*/
err = thr_list_create(list, list_num);
if (err)
{
syslog(LOG_ERR, "thr_list_create()%s", strerror(errno));
exit(1);
}
/* create chanel pthread*/
int i;
i = 0;
for (ptr = list->entry; i < list_num; ptr = (void *)((char *)ptr) + ntohs(ptr->len))
{
i++;
err = thr_channel_create(ptr);
if (err)
{
syslog(LOG_ERR, "thr_channel_create()%s", strerror(errno));
exit(1);
}
}
freeptr[0] = ptr;
free_ptrnum = 1;
while (1)
{
pause();
}
}
server.c,主要是大框架设置,比较简单
项目主要难点在于节目单列表和频道的获取,在mlib.c中完成
在获取节目单函数int mlib_getchnlist(struct msg_list_st **mchnarr, int *index)
中,先解析频道目录的个数,申请当前频道数需要的最大内存 entrylistptr = malloc(MSG_LIST_MAX/CHNNR*globres.gl_pathc);
,然后在根据path2entry函数解析到的频道为节目单结构体进行赋值,需要注意的是由于结构体是变长结构体,不可以直接用下标数组访问、赋值等,具体做法见代码。
mlib.c:
#include
#include
#include "sitetype.h"
#include "mlib.h"
#include
#include
#include "mytbf.h"
#include
#include "proto.h"
#include "server.h"
#include
#include
#include
#include
#include
#include"mytbf.h"
#include"server.h"
#define PATHSIZE 1024
#define LINEBUFSIZE 1024
#define MP3_BITRATE 320 * 1024 // 采样率
struct channel_context_st
{
chnid_t chnid;
char *desc;
glob_t mp3glob;
int pos;
int fd;
off_t offset;
mytbf_t *tbf;
};
struct channel_context_st channel[CHNNR + 1];
int totalsize;
struct channel_context_st *path2entry(char *gl_pathv)
{
printf("current path: %s\n", gl_pathv);
syslog(LOG_INFO, "current path: %s\n", gl_pathv);
char pathstr[PATHSIZE] = {'\0'};
char linebuf[LINEBUFSIZE];
FILE *fp;
struct channel_context_st *me;
static chnid_t cur_id = MINCHNID; // 由于是一个静态变量所以相当于一直在操作同一块内存 有叠加效果
strcat(pathstr, gl_pathv); //拼接两个字符串
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 channe; dir (can not find desc.txt)", gl_pathv);
return NULL;
}
if (fgets(linebuf, LINEBUFSIZE, fp) == NULL)
{
syslog(LOG_INFO, "%s is not a channel dir(cant get the desc.text)", gl_pathv);
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\n", strerror(errno));
return NULL;
}
me->desc = strdup(linebuf);
strncpy(pathstr, gl_pathv, PATHSIZE);
strncat(pathstr, "/*.mp3", PATHSIZE - 1);
if (glob(pathstr, 0, NULL, &me->mp3glob) != 0)
{
// cur_id++;
syslog(LOG_ERR, "%s is not a channel dir(can not find mp3 files", gl_pathv);
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 = cur_id;
cur_id++;
return me;
}
int mlib_freechnlist(struct msg_listentry_st *ptr)
{
free(ptr);
return 0;
}
/* 根据目录解析频道 */
int mlib_getchnlist(struct msg_list_st **mchnarr, int *index)
{
char path[PATHSIZE];
glob_t globres;
int size = 0;
int i;
int num = 0;
/****************换msg_listentry_st************************************/
struct msg_listentry_st *ptr = NULL;
struct msg_list_st *entrylistptr; //节目单总包
struct channel_context_st *res;
for (i = 0; i < CHNNR + 1; i++)
{
channel[i].chnid = -1;
}
snprintf(path, PATHSIZE, "%s/*", server_conf.media_dir);
if (glob(path, 0, NULL, &globres) < 0)
{
return -1;
}
totalsize = sizeof(chnid_t);
/*****************************malloc**************************/
entrylistptr = malloc(MSG_LIST_MAX/CHNNR*globres.gl_pathc);
entrylistptr->chnid = LISTCHNID;
if(entrylistptr == NULL)
{
syslog(LOG_ERR, "malloc() failed.%s", strerror(errno));
exit(1);
}
ptr = entrylistptr->entry;
for (i = 0; i < globres.gl_pathc; i++)
{
// globres.gl_pathv[i] -> "/var/media/ch1"
res = path2entry(globres.gl_pathv[i]);
if (res != NULL)
{
syslog(LOG_DEBUG, "path2entry () :%d %s\n", res->chnid, res->desc);
totalsize += sizeof(struct msg_listentry_st)+strlen(res->desc);
// ptr = realloc(ptr, totalsize);
/* (1)sizeof一般是用来求某种类型的变量所占用的内存大小的。
(2)strlen顾名思义,是用来求string的length的。
*/
size = sizeof(struct msg_listentry_st)+strlen(res->desc); //如果用sizeof(res->decs)算出来的是指针的大小 会丢失数据
ptr->chnid = res->chnid;
ptr->len = htons(size);
strcpy((char *) ptr->desc, res->desc);
ptr = (void *)(((char *) ptr) + size); // 向后移动ptr
/* 把可以用的频道结构体保存 */
memcpy(channel + res->chnid, res, sizeof(*res)); //加res->chnid 代表第几个频道 与&channel[res->chnid]效果相同
num++;
#ifdef DEBUG
printf("sizeof(*res)%d\n", sizeof(*res));
#endif // DEBUG
}
}
*mchnarr = realloc(entrylistptr, totalsize); //重新分配内存大小
if (*mchnarr == NULL)
{
syslog(LOG_ERR, "realloc() failed.");
}
*index = num;
#ifdef DEBUG
printf("totalsize = %d\n", totalsize);
#endif // DEBUG
return 0;
}
static int open_next(chnid_t chnid)
{
for (int i = 0; i < channel[chnid].mp3glob.gl_pathc; ++i)
{
channel[chnid].pos++; // 更新下一个媒体文件偏移
if (channel[chnid].pos == channel[chnid].mp3glob.gl_pathc)
{
syslog(LOG_INFO, "没有文件了\n");
channel[chnid].pos = 0;
}
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
{
syslog(LOG_INFO, "打开新的文件\n");
channel[chnid].offset = 0;
return 0;
}
}
syslog(LOG_ERR, "None of mp3 in channel %d id available.", chnid);
return -1;
}
ssize_t mlib_readchn(chnid_t chnid, void *buf, size_t size)
{
int tbfsize;
int len;
// get token number
tbfsize = mytbf_fetchtoken(channel[chnid].tbf, size);
syslog(LOG_INFO, "current tbf():%d\n", mytbf_checktoken(channel[chnid].tbf));
while (1)
{
/* 从fd中读取tbfsize大小的数据到buf中,读取地址=文件开始+offset*/
len = pread(channel[chnid].fd, buf, tbfsize, channel[chnid].offset); // 读取tbfsize数据到从offset处开始的buf
/*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));
if(open_next(chnid) <0)
{
syslog(LOG_ERR, "channel %d no successed open",channel[chnid].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
open_next(chnid);
break;
}
else /*len > 0*/ //真正读取到了数据
{
channel[chnid].offset += len;
break;
}
}
// remain some token
if (tbfsize - len > 0)
mytbf_returntoken(channel[chnid].tbf, tbfsize - len);
printf("current chnid :%d\n", chnid);
return len; //返回读取到的长度
}
在获得节目单列表list后直接交给节目单发送线程发送即可:
list.c
#include
#include
#include "proto.h"
#include "server.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include /* superset of previous */
#include
#include "mlib.h"
#include "list.h"
#include "channel.h"
#include
struct msg_list_st *list_entry;
int num_list_entry;
pthread_t list_tid;
struct msg_list_st *entrylistptr; //节目单总包
struct msg_listentry_st *entryptr; //每一个节目的包
void *thr_list(void *thr)
{
int ret;
struct msg_listentry_st *ptr;
#ifdef DEBUG
int i = 0;
for (ptr = list_entry->entry; i < num_list_entry; ptr = (void *)((char *)ptr) + ntohs(ptr->len))
{
i++;
printf("channel:%d:%s", ptr->chnid, ptr->desc);
}
#endif // DEBUG
while (1)
{
syslog(LOG_INFO, "thr_list sndaddr :%d\n", sndaddr.sin_addr.s_addr);
ret = sendto(sfd, list_entry, totalsize, 0, (void *)&sndaddr,
sizeof(sndaddr)); // 频道列表在广播网段每秒发送entrylist
syslog(LOG_DEBUG, "List sent content len:%d\n", totalsize);
if (ret < 0)
{
syslog(LOG_WARNING, "sendto(serversd, enlistp...:%s", strerror(errno));
}
else
{
syslog(LOG_DEBUG, "List sendto(serversd, enlistp....):success");
}
sleep(1);
}
}
int thr_list_create(struct msg_list_st *listptr, int num_ent)
{
int err;
list_entry = listptr; //接收节目单的包 所有的节目单包的首地址
num_list_entry = num_ent; //频道的个数
err = pthread_create(&list_tid, NULL, thr_list, NULL);
if (err)
{
syslog(LOG_ERR, "pthread_create():%s\n", strerror(errno));
return -1;
}
return 0;
}
// 销毁节目单线程
int thr_list_destroy(void)
{
pthread_cancel(list_tid);
pthread_join(list_tid, NULL);
free(entrylistptr);
return 0;
}
频道发送和节目单发送类似,也是在mlib.c 解析需要的的数据,分给线程发送,
解析频道数据在mlib_readchn(chnid_t chnid, void *buf, size_t size)
函数中完成,主要完成把mp3文件打开回填数据到发送buf中(即频道包结构体里的data中),然后发送给客户端,发送线程代码如下:
channel.c
#include
#include
#include
#include
#include
#include
#include "proto.h"
#include "list.h"
#include "mlib.h"
#include "server.h"
#include "server.h"
#include
#include
#include
#include /* superset of previous */
int num_list_entry;
static int tid_nextpos = 0;
struct thr_channel_entry_st
{
chnid_t chnid;
pthread_t tid;
};
struct thr_channel_entry_st thr_channel[CHNNR];
void *thr_channel_snder(void *ptr)
{
struct msg_channel_st *sbufp;
int len;
struct msg_listentry_st *entry = ptr;
sbufp = malloc(MSG_CHANNEL_MAX);
if (sbufp == NULL)
{
syslog(LOG_ERR, "malloc():%s", strerror(errno));
exit(1);
}
sbufp->chinid = entry->chnid; // 频道号处理
printf("sbufp->chinid %d\n", sbufp->chinid);
// 频道内容读取
while (1)
{
len = mlib_readchn(entry->chnid, sbufp->data, 320 * 1024 / 8); // 320kbit/s 读取某一个频道的媒体文件
syslog(LOG_DEBUG, "mlib_readchn() len: %d", len);
if (len < 0)
{
break;
}
if (sendto(sfd, sbufp, len + sizeof(chnid_t), 0, (void *)&sndaddr, sizeof(sndaddr)) < 0)
{
syslog(LOG_ERR, "thr_channel(%d):sendto():%s", entry->chnid,
strerror(errno));
break;
}
syslog(LOG_ERR, "thr_channel()%d:sendto():%s", entry->chnid,
strerror(errno));
sched_yield(); //主动出让调度器
}
pthread_exit(NULL);
}
int thr_channel_create(struct msg_listentry_st *list)
{
int err;
err = pthread_create(&thr_channel[tid_nextpos].tid, NULL, thr_channel_snder, list);
if (err)
{
syslog(LOG_ERR, "pthread_create():%s\n", strerror(errno));
return -1;
}
thr_channel[tid_nextpos].chnid = list->chnid; //填写频道信息
tid_nextpos++;
return 0;
}
int thr_channel_destroy(struct mlib_listentry_st *ptr)
{
int i;
for (i = 0; i < CHNNR; 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;
break;
}
}
return 0;
}
int thr_channel_destroyall(void)
{
for (int i = 0; i < CHNNR; 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;
}
}
return 0;
}
令牌桶发送流量控制,与之前讲的没什么区别
mytbf.c
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include"pthread.h"
#include "mytbf.h"
static int min(int a, int b) { return a < b ? a : b; }
struct mytbf_st {
int cps; // c per second
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;
static void alrm_handle(int sig) {
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);
}
static void *thr_alrm(void *p)
{
struct itimerval tick;
memset(&tick, 0, sizeof(tick));
tick.it_value.tv_sec = 1; // sec
tick.it_value.tv_usec = 0; // micro sec.
tick.it_interval.tv_sec = 1;
tick.it_interval.tv_usec = 0;
signal(SIGALRM, alrm_handle);
setitimer(ITIMER_REAL, &tick, NULL);
while (1)
{
pause();
}
return 0;
}
// 模块卸载函数
static void module_unload() {
int i;
pthread_cancel(tid);
pthread_join(tid, NULL);
for (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() {
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(); // 开启定时token派发
pthread_once(&once_init, module_load); // 限定只开启一次
int pos;
// 初始化mytbf
me = malloc(sizeof(*me));
if (me == NULL) {
return NULL;
}
me->cps = cps;
me->burst = burst;
me->token = 0;
pthread_mutex_init(&me->mut, NULL); // 初始化该令牌桶的mutex
pthread_cond_init(&me->cond, NULL); // 初始化该令牌桶的conditional variable
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;
}
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_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;
}
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;
}
相对简单,主要完成以下内容:
1)参数初始化,命令行设置
2)网络配置加入多播组
3)接收节目单 选择频道
4)父进程读取频道数据 写给子进程
5)子进程读取父进程数据 交给媒体解析器解析
client.c 和 client.h如下:
#include
#include
#include
#include "client.h"
#include "proto.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
/*
* -M --mgroup 指定多播组
* -P --port 指定接受端口
* -p --player 指定播放器
* -H --help 显示帮助
* */
#define DEFAULT_PLAYER "/usr/bin/mpg123 - > /dev/null"
#define IPSIZE 30
struct client_conf_st client_conf =
{
.rcvport = DEFAULT_RCVPORT,
.mgroup = DEFAULT_MGROUP,
.player_cmd = DEFAULT_PLAYER};
static int sfd;
void printf_help()
{
printf(" * -M --mgroup 指定多播组\n\
* -P --port 指定接受端口\n\
* -p --player 指定播放器\n\
* -H --help 显示帮助* \n");
}
int writen(int fd, const void *buf, size_t nbyte)
{
int len;
int write_len = 0;
if (nbyte > 0)
{
while (1)
{
len = write(fd, buf, nbyte);
if (len < 0)
{
if (len = -EINTR)
{
continue;
}
else
{
perror("write()");
exit(1);
}
}
if (len == 0)
{
break;
}
nbyte = nbyte - len;
write_len += len;
}
}
return write_len;
}
int main(int argc, char **argv)
{
/*
*初始化
* 级别:默认值 配置文件 环境变量 命令行参数
*
* */
int pd[2];
int c;
int index;
pid_t pid;
uint64_t receive_buf_size = 1024;
struct ip_mreqn mreqn;
struct option argarr[] = {
{"port", 1, NULL, 'p'},
{"mgroup", 1, NULL, 'M'},
{"player", 1, NULL, 'p'},
{"help", 1, NULL, 'H'},
{NULL, 0, NULL, 0}};
struct sockaddr_in laddr, seraddr, raddr;
socklen_t seraddr_len, raddrlen;
// while (1)
// {
// c = getopt_long(argc, argv, "M:P:p:H", argarr, &index); //从命令行里搜索这些关键词 并且后面有参数
// printf("c = %d\n", c);
// c = 0;
// 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':
// printf_help();
// break;
// default:
// abort();
// break;
// }
// }
/* 网络配置 */
sfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sfd < 0)
{
perror("socket()");
exit(1);
}
inet_pton(AF_INET, client_conf.mgroup, &mreqn.imr_multiaddr); //IPv4点分式转二进制数 把地址填入mreqn.imr_multiaddr
inet_pton(AF_INET, " 0.0.0.0", &mreqn.imr_address);
mreqn.imr_ifindex = if_nametoindex("ens33");
if (setsockopt(sfd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreqn, sizeof(mreqn)) < 0) //man 7 ip 把当前sdf加入到多播组
{
perror("setsockopt()");
exit(1);
}
int val;
val = 1;
if (setsockopt(sfd, IPPROTO_IP, IP_MULTICAST_LOOP, &(val), sizeof(val)) < 0)
{
perror("setsockopt()");
exit(1);
}
// improve efficiency
uint64_t receive_buff_size = 20 * 1024 * 1024; //20M
if (setsockopt(sfd, 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(sfd, (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)
{
close(sfd);
close(pd[1]);
dup2(pd[0], 0); //把管道的读端复制作为0号文件描述符
if (pd[0] > 0) //如果的管道读端不是标准输入,关闭
{
close(pd[0]);
}
execl("/bin/sh", "sh", "-c", client_conf.player_cmd, NULL);
perror("execl");
exit(0);
}
else //父进程 读网络数据 写管道
{
char ipstr_raddr[IPSIZE], ipstr_server_addr[IPSIZE];
//收节目单
struct msg_list_st *msg_list;
msg_list = malloc(MSG_LIST_MAX);
if (msg_list == NULL)
{
perror("malloc");
exit(1);
}
int len;
seraddr_len = sizeof(seraddr);
while (1)
{
len = recvfrom(sfd, msg_list, MSG_LIST_MAX, 0, (void *)&seraddr, &seraddr_len);
if (len < 0)
{
perror("recvform()");
exit(1);
}
if (len < (int)sizeof(msg_list))
{
fprintf(stderr, "message is too small\n");
continue;
}
if (msg_list->chnid != LISTCHNID)
{
fprintf(stderr, "chinnal id 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:%s", pos->chnid, pos->desc);
}
int ret = 0;
int chosenid;
while (ret < 1)
{
ret = scanf("%d", &chosenid);
if (ret != 1)
exit(1);
}
//收频道包,发给子进程
struct msg_channel_st *msg_channel;
msg_channel = malloc(MSG_CHANNEL_MAX);
if (msg_channel == NULL)
{
perror("malloc()");
exit(1);
}
raddrlen = sizeof(raddr);
while (1)
{
len = recvfrom(sfd, msg_channel, MSG_CHANNEL_MAX, 0, (void *)&raddr, &raddrlen);
if (raddr.sin_addr.s_addr != seraddr.sin_addr.s_addr) //防止其他ip恶意发包
{
/*******把IP的大整数转化为字符串************/
inet_ntop(AF_INET, &raddr.sin_addr.s_addr, ipstr_raddr, 30);
inet_ntop(AF_INET, &seraddr.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);
continue;
}
if (len < sizeof(msg_channel))
{
fprintf(stderr, "msg_channel too small\n");
continue;
}
if (msg_channel->chnid == chosenid)
{
fprintf(stdout, "accepted msg : %s \n", msg_channel->data);
writen(pd[1], msg_channel->data, len - sizeof(chnid_t));
// sleep(2);
}
}
close(sfd);
free(msg_channel);
}
}
client.h
#ifndef CLIENT_H__
#define CLIENT_H__
#include
#include
#include
#define DEFAULT_PLAYERCMD "/usr/bin/mpg123 - > /dev/null" //mpg123 - 表示从标准输入读区文件进行解析
struct client_conf_st
{
char *rcvport;
char *mgroup;
char *player_cmd;
};
extern struct client_conf_st client_conf;
#endif
代码:https://download.csdn.net/download/qq_43745917/87167158
服务器QT客户端:git地址