需求
目前需要实现基于客户机和服务器模型的网络音频广播/点播系统。
本音频可以广泛应用在语音教室和公共广播等多种场景。该软件分为客户端和服务器两个部分,服务器运行在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