题目:
编程实践4:设计开发广播服务器,向服务器所在子网广播消息。服务器程序定时从广播信息文件读取需要广播的信息,按照信息指定发布时间将信息广播出去。设计开发客户端,接收广播消息,打印输出到显示器。
要求使用C/C++编成,提交程序源代码;程序功能完整正确,源代码格式化良好、适当注释、模块划分合理。
测试效果截图:
因为按指定发布时间将信息广播要等待的时间比较长,不方便测试,所以我的广播信息文件是这样写的:
冒号(:)前面的数字用来代表时间,单位是秒。冒号后面是要广播的信息(会去掉空格)。若冒号前面的数字是n,那么在发送一条广播信息后,服务端会暂定n秒(使用sleep函数)。
另外,因为广播的时候,客户端要绑定端口,一般一个端口只能有一个程序绑定,这样广播只能是一对一的,所以我在客户端设置广播的时候,同时设置了端口复用,这样就可以有多个客户端接收到服务端发送的消息:
下面开始正式测试
如图,客户端收到消息,会显示收到的时间。为了方便测试,我设置了服务端不断读取广播文件进行广播,每次广播间隔了5s。每次广播结束后会显示本次广播的时间。
对比一下广播文件和客户端收到信息的时间:
可以看到,客户端每次收到消息的时间与接收文件的时间一致。
接下来运行多个客户端进行测试:
可以看到,因为设置了端口复用,是可以对多个客户端进行广播的。
测试完毕。
程序源代码:
服务端代码:
#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <string.h>
#include <netinet/in.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <pthread.h>
#include <stdbool.h>
#include <sys/stat.h>
#include <fcntl.h>
#define broadcast_ip "192.168.110.255"//设置广播地址,必须为本机/虚拟机的广播地址才行
#define broadcast_port 8080 //设置广播端口
#define buff_len 1024 //设置缓冲区长度
#define Maxline 1024 //设置最大读取文件行数
char buffer[buff_len];//缓冲区
int get_time();//将buffer前面的数字提取出来,作为暂停时间
void get_char();//从buffer中要发送的信息提取出来,
int main(int argc,void *argv[])
{
int err; //用来返回错误信息
int t; //用来表示每次发送信息后暂停的时间(单位是秒)
struct sockaddr_in broadcast_addr; //创建一个地址结构
char message[buff_len]={0};//用来发送消息
int sockfd = socket(AF_INET, SOCK_DGRAM, 0); //创建套接字
if(sockfd < 0){ /*出错*/
printf("socket error");
return -1;
}
//开启广播
int opt =1;
err = setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, &opt, sizeof(opt));//将套接字设置为广播模式
if(err < 0) { /*出错*/
printf("setsockopt error");
return -1;
}
memset(&broadcast_addr, 0, sizeof(broadcast_addr)); //初始化为0
broadcast_addr.sin_family = AF_INET;//初始化地址族IPV4
broadcast_addr.sin_port = htons(broadcast_port); //设置端口号(网络字节序号)
broadcast_addr.sin_addr.s_addr = inet_addr(broadcast_ip);//初始化绑定地址, 用INADDR_ANY--表示绑定本机地址
int k=1;
//读取文件发送数据
while(1){
FILE* fp=fopen("test.txt","r");//以只读的方式打开文件
if(fp == NULL){
printf("failed to open file\n");
break;
}
int time_sum=0;//记录本次广播所花的时间
printf("------第 %d 次广播------\n",k);
while(fgets(buffer,Maxline,fp)){//以行为单位读取文件并将数据发送到客户端
t=get_time(buffer);
time_sum+=t;
//strcpy(message,get_char(buffer));
get_char();
strcpy(message,buffer);
err = sendto(sockfd, message, sizeof(message), 0, (struct sockaddr*)&broadcast_addr, sizeof(broadcast_addr));
if(err <0){
printf("sendto error"); //发送错误,关闭连接后结束程序
//close(sockfd);
continue;
}
memset(buffer,0,buff_len);//清空缓存区
memset(message,0,buff_len);
sleep(t); //暂停ts
}
fclose(fp); //关闭文件指针
k++;
printf("本次广播共花时间%d秒\n",time_sum);
sleep(5);//暂停5s
}
close(sockfd); //关闭套接字
return 0;
}
int get_time(char s[])//将一行字符串前面的数字提取出来,作为暂停时间
{
int t=0;
for(int i=0;i<strlen(s);i++)
if(s[i]>='0'&&s[i]<='9') t=t*10+s[i]-'0';
else if(s[i]==':') break;
return t;
}
void get_char()//将buffer中要发送的信息提取出来
{
char s[buff_len];
strcpy(s,buffer);
memset(buffer,0,buff_len);//清空
int i=0,j=0;
while(s[i]!=':')i++;
i++;
while(s[i]==' ')i++;
for(;i<strlen(s);i++)buffer[j++]=s[i];
//printf("%s\n",buffer);
}
客户端代码:
#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <string.h>
#include <netinet/in.h>
#include <unistd.h>
#include <semaphore.h>
#include <stdbool.h>
#include <arpa/inet.h>
#include <sys/epoll.h>
#include <time.h>
#define MAXSIZE 100
#define broadcast_port 8080 //设置广播端口
#define buff_len 1024 //设置缓冲区长度
int main(void)
{
int err; //错误信息
char buffer[buff_len]={0},message[buff_len]={0};//创建数据缓冲区
struct sockaddr_in client_addr; //客户端地址结构
struct sockaddr_in server_addr; //服务端地址结构
socklen_t len = sizeof(server_addr); //服务端地址结构长度
struct tm *p;
time_t t; //用于显示当前时间
time(&t);
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);//创建套接字 UDP--SOCK_DGRAM
if(sockfd < 0){/*出错*/
printf("socket error\n");
return -1;
}
//设置端口复用SO_REUSEADDR
int opt = 1;
err = setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
if(err < 0) { /*出错*/
printf("setsockopt error");
return -1;
}
memset(&client_addr, 0, sizeof(client_addr)); //初始化为0
client_addr.sin_family = AF_INET;//初始化地址族IPV4
client_addr.sin_port = htons(broadcast_port); //设置端口号(网络字节序号)
client_addr.sin_addr.s_addr = INADDR_ANY;//初始化绑定地址, 用INADDR_ANY--表示绑定本机地址
err = bind(sockfd, (struct sockaddr*)&client_addr, sizeof(client_addr));//地址绑定
if(err < 0){/*出错*/
printf("bind error\n");
return -1;
}
//接收数据
while(1){
memset(buffer,0,sizeof(buffer));//清零
err = recvfrom(sockfd, buffer, sizeof(buffer), 0, (struct sockaddr*)&server_addr, &len);
if(err < 0){
printf("recvfrom error\n");
close(sockfd);
return -1;
}
time(&t);//获取当前时间
p=gmtime(&t);//
//构造时间进行输出
sprintf(message,"%d-%d-%d %02d:%02d:%02d", 1900+p->tm_year,1+p->tm_mon, p->tm_mday,8+p->tm_hour,p->tm_min, p->tm_sec);
printf("当前时间:%s,接收到消息:\n",message);
printf("%s\n", buffer);
}
//关闭套接字
close(sockfd);
return 0;
}
广播信息文件test.txt的内容:
1:hello,client,i am server
2:welcome to udp broadcast
3:this is a test!
4:yes,hhhhhhhhhh
4:you don't need to reply
3:just a test
5: don't worry
结语
这一次的实践比较简单,但是我还是做了好久,做做停停,不在状态。不然可以半天搞定的。
测试是在虚拟机上测试的,然后用QQ截的图,不知道为啥有点模糊,大概能看就行。
没想到,慢慢吞吞做的还是被老师拿来点评了,老师说我没有按题目要求去做啊,题目要求是按指定的时间去发送消息,你却按暂停的时间来发送。
老师还说我能想到端口复用这个点说明我确实去做了,发现问题并解决问题,但是广播的一对多中的多并不是指一台主机上的多个程序,而是指多个主机,所以实际上是不需要端口复用的。
嗯,虽然没有按题目完成,但是老师还是给了我挺高分的,谢谢老师(><)
最后说的那个时间的问题,我没有处理,因为当时已经提交了(不能重复提交),我又比较懒,所以就没改。大家感兴趣的去解决一下吧~