?视频操作演示:
利用TCP/UDP 协议制作一个飞秋聊天工具演示视频
?
int udp_broadcast(char const*argv[])
{
//1.创建对象
udp_socket = socket(AF_INET, SOCK_DGRAM, 0);
if(udp_socket<0)
{
perror("");
return -1;
}
//开启广播功能
int on=1; //开启
int ret = setsockopt(udp_socket,SOL_SOCKET,SO_BROADCAST,&on,sizeof(on));
if(ret < 0)
{
perror("广播开启失败:");
return -1;
}else
{
printf("广播开启成功\n");
}
//设置广播地址
dest_addr.sin_family=AF_INET; //设置网络协议
dest_addr.sin_port=htons(udp_port); //所有处于192.168.64网段且,端口为 6666 的进程都可以收到数据
dest_addr.sin_addr.s_addr=inet_addr("192.168.1.255"); //设置广播地址
//绑定IP地址信息
struct sockaddr_in server_addr;
server_addr.sin_family=AF_INET; //设置网络协议
server_addr.sin_port=htons(udp_port); //所有处于192.168.64网段且,端口为 6666 的进程都可以收到数据
server_addr.sin_addr.s_addr = INADDR_ANY;
ret=bind(udp_socket,(struct sockaddr *)&server_addr,sizeof(server_addr));
if(ret < 0)
{
perror("");
// return -1;
} else{
printf("绑定udp成功\n");
}
bind_tcp_serv(8989); //绑定接收文件的tcp服务器
bind_sharetcp_serv(9898); //绑定接收文件的tcp服务器9898
//创建一个线程接收udp数据
pthread_t tid;
pthread_create(&tid, NULL,recv_task, NULL);
//创建一个线程接收tcp发送的文件
pthread_t tid1;
pthread_create(&tid1, NULL,recv_tcpfile, NULL);
//拼接回发通知协议
char *weather_str = getweather((char *)argv[3]); //(char *)argv[3]
if(weather_str == NULL)
{
printf("http请求获取签名失败 可能网络问题 请重新尝试\n");
//return -1;
exit(0);
}
//printf("你的个性签名:%s\n", weather_str);
sprintf(people,"%s %s %s 个性签名:%s %s %s\n","##reply",argv[1],argv[2],weather_str,share_path,all_share_file);
//拼接登陆通知协议
char loin[1024]={0};
sprintf(loin,"%s %s %s %s %s %s\n","##loin",argv[1],argv[2],weather_str,share_path,all_share_file);
//设计登陆通知协议 loin 姓名 IP
//发送一个UDP 广播数据报
sendto(udp_socket,loin,strlen(loin),0,(struct sockaddr *)&dest_addr,sizeof(dest_addr));
}
设计目标:利用TCP/UDP 协议制作一个飞秋聊天工具?
功能描述:
1.实现上.下线通知。 2.实现上线获取在线好友,下线删除好友。 3.实现单独聊天,群聊 (组播) 4.实现文件传输 5.实现文件共享 6.通过http请求获取天气信息,显示到 个性签名中, 心情中
**设计方案**
设计一个好友结构体: ?
struct people
{
? //姓名
? //IP信息
? //通信描述符
? //个性签名
? //所在组播
? //共享的文件路径 ?
?
? //设计链表 单链,双链,内核链
}
利用tcp/udp/http协议多线程等实现网络的通信,设计网络通信协议和用单链表来实现不同主机之间信息的交互。
系统框架
界面设计?
此次之间用的终端实现聊天室的功能,简单用文字来实现
printf("======网络聊天系统====\n");
printf("1.获取在线好友 2.指定好友聊天 3.群聊 4.发送文件 5.共享文件 6.退出系统\n");
printf("======================\n\n");
实现过程
主函数
//主函数
int main(int argc, char const*argv[])
{
signal(2, catch_sigint);//捕捉ctrl+c信号
if(argc != 4)
{
printf("请输入姓名和IP ./main 姓名 IP 城市\n");
return -1;
}
printf("请输入共享文件路径\n");
scanf("%s",share_path);
serch_dir(share_path);
printf("%s||%s\n",share_path,all_share_file);
//0.创建单链表头结点 设置共享
head = malloc(sizeof(struct node));
udp_broadcast(argv);//开启广播
while (1)
{
int n;
printf("======网络聊天系统====\n");
printf("1.获取在线好友 2.指定好友聊天 3.群聊 4.发送文件 5.共享文件 6.退出系统\n");
printf("======================\n\n");
scanf("%d", &n);
if(n == 1)
{
//遍历链表
Traver_Nodes(head);
}else if( n == 2)
{
char buf_name[256] = {0};
char buf_ip[124] = {0};
char buf_msg[1024] = {0};
char all_msg[1024] = {0};
//指定好友发送消息
printf("请输入好友的IP\n");
scanf("%s", buf_ip);
//获取 好友 的 ip 和端口
struct sockaddr_in dest_addr;
dest_addr.sin_family=AF_INET; //设置网络协议
dest_addr.sin_port=htons(udp_port); //atoi(argv[4])
dest_addr.sin_addr.s_addr=inet_addr(buf_ip);
printf("进入聊天成功!按quit退出\n");
while (1)
{
printf("请输入发送的信息\n");
scanf("%s", buf_msg);
sprintf(all_msg,"##msg %s",buf_msg);
if(strcmp(buf_msg,"quit")== 0)
{
break;
}
sendto(udp_socket,all_msg,strlen(all_msg),0,(struct sockaddr *)&dest_addr,sizeof(dest_addr));
}
}else if( n == 3)
{
//群聊 组播
char group[124] = {0};
printf("请输入群聊的组播号:\n");
scanf("%s", group);
//1.加入组播
struct ip_mreq a;
bzero(&a, sizeof(a));
a.imr_interface.s_addr = INADDR_ANY; //所有网卡地址加入组播
a.imr_multiaddr.s_addr = inet_addr(group); // 指定多播地址
//开启组播功能
int ret = setsockopt(udp_socket, IPPROTO_IP, IP_ADD_MEMBERSHIP, &a, sizeof(a));
if(ret < 0)
{
perror("");
}else{
printf("开启组播成功\n");
}
printf("进入群聊成功!按quit退出\n");
while (1)
{
//4.往组播地址中发送数据
struct sockaddr_in arry_addr;
arry_addr.sin_family=AF_INET;
arry_addr.sin_port=htons(udp_port);
arry_addr.sin_addr.s_addr=inet_addr(group); //所有网卡地址
printf("请输入内容\n");
char buf[512]={0};
char msgbuf[1024]={0};
scanf("%s",buf);
sprintf(msgbuf,"##all %s %s", argv[1], buf);//拼接群里消息标志##all
if(strncmp(buf,"quit",4) == 0)
{
break;
}
sendto(udp_socket,msgbuf,strlen(msgbuf),0,(struct sockaddr *)&arry_addr,sizeof(arry_addr));
}
}else if( n == 4)
{
//发送文件
char file_name[124] = {0};
char ip[124] = {0};
printf("发送文件中。。|请输入文件名\n");
scanf("%s",file_name);
printf("请输入好友的ip\n");
scanf("%s",ip);
send_file(file_name,ip,8989); 发送文件 端口设置8989
}else if( n == 5){
//文件共享 共享自己的文件路径 如果别人进入点进来下载 你再发送这个文件给他 他接收 即可
//1.查看好友的共享文件
char ip[125] = {0};
char pathname[512] = {0};
char msgbuf[1024] = {0};
char file[256] = {0};
Traver_sharepath_Nodes(head);
printf("请输入好友的ip\n");
scanf("%s",ip);
struct node *p= Find_IP_Nodes(head,ip);//利用ip查找该节点
printf("%s\n",p->share_file);//打印节点的共享文件信息
printf("请输入你要下载文件好友的文件\n");
scanf("%s",file);
sprintf(pathname,"%s%s",p->pathname,file);
//发送udp数据告诉好友 需要下载
//获取 好友 的 ip 和端口
struct sockaddr_in dest_addr;
dest_addr.sin_family=AF_INET; //设置网络协议
dest_addr.sin_port=htons(udp_port); //
dest_addr.sin_addr.s_addr=inet_addr(ip);
sprintf(msgbuf,"##share %s", pathname);//拼接群里消息标志##all
sendto(udp_socket,msgbuf,strlen(msgbuf),0,(struct sockaddr *)&dest_addr,sizeof(dest_addr));
recv_sharefile();//接收共享文件
}else if( n == 6)
{
//退出
//拼接退出通知协议
char quit[1024]={0};
sprintf(quit,"%s %s %s\n","##quit",argv[1],argv[2]);
//设计退出通知协议 quit 姓名 IP
//发送一个UDP 广播数据报
int ret = sendto(udp_socket,quit,strlen(quit),0,(struct sockaddr *)&dest_addr,sizeof(dest_addr));
if(ret < 0 )
{
perror("");
}
close(udp_socket);
system("killall -9 main");//有时退出的时候进程会还在 得关了否则会出现udp绑定失败
return -1;
}
}
return 0;
}
//广播消息
int udp_broadcast(char const*argv[])
{
//1.创建对象
udp_socket = socket(AF_INET, SOCK_DGRAM, 0);
if(udp_socket<0)
{
perror("");
return -1;
}
//开启广播功能
int on=1; //开启
int ret = setsockopt(udp_socket,SOL_SOCKET,SO_BROADCAST,&on,sizeof(on));
if(ret < 0)
{
perror("广播开启失败:");
return -1;
}else
{
printf("广播开启成功\n");
}
//设置广播地址
dest_addr.sin_family=AF_INET; //设置网络协议
dest_addr.sin_port=htons(udp_port); //所有处于192.168.64网段且,端口为 6666 的进程都可以收到数据
dest_addr.sin_addr.s_addr=inet_addr("192.168.1.255"); //设置广播地址
//绑定IP地址信息
struct sockaddr_in server_addr;
server_addr.sin_family=AF_INET; //设置网络协议
server_addr.sin_port=htons(udp_port); //所有处于192.168.64网段且,端口为 6666 的进程都可以收到数据
server_addr.sin_addr.s_addr = INADDR_ANY;
ret=bind(udp_socket,(struct sockaddr *)&server_addr,sizeof(server_addr));
if(ret < 0)
{
perror("");
// return -1;
} else{
printf("绑定udp成功\n");
}
bind_tcp_serv(8989); //绑定接收文件的tcp服务器
bind_sharetcp_serv(9898); //绑定接收文件的tcp服务器9898
//创建一个线程接收udp数据
pthread_t tid;
pthread_create(&tid, NULL,recv_task, NULL);
//创建一个线程接收tcp发送的文件
pthread_t tid1;
pthread_create(&tid1, NULL,recv_tcpfile, NULL);
//拼接回发通知协议
char *weather_str = getweather((char *)argv[3]); //(char *)argv[3]
if(weather_str == NULL)
{
printf("http请求获取签名失败 可能网络问题 请重新尝试\n");
//return -1;
exit(0);
}
//printf("你的个性签名:%s\n", weather_str);
sprintf(people,"%s %s %s 个性签名:%s %s %s\n","##reply",argv[1],argv[2],weather_str,share_path,all_share_file);
//拼接登陆通知协议
char loin[1024]={0};
sprintf(loin,"%s %s %s %s %s %s\n","##loin",argv[1],argv[2],weather_str,share_path,all_share_file);
//设计登陆通知协议 loin 姓名 IP
//发送一个UDP 广播数据报
sendto(udp_socket,loin,strlen(loin),0,(struct sockaddr *)&dest_addr,sizeof(dest_addr));
}
//线程接收消息函数
void *recv_task(void *arg)
{
char sign_buf[124] ={0};
char ip_buf[50] ={0};
char name_buf[50] ={0};
char path[125] ={0};
char share_file[4096] ={0};
struct sockaddr_in clien_addr;
int len=sizeof(clien_addr);
//等待用户回发数据
while (1)
{
char msg[1024]={0};
int ret = recvfrom(udp_socket,msg,1024,0,(struct sockaddr*)&clien_addr,&len);
char *ip = inet_ntoa(clien_addr.sin_addr);
unsigned short port = ntohs(clien_addr.sin_port);
//2.设置接收者的地址
struct sockaddr_in dest_addr;
dest_addr.sin_family = AF_INET; //设置网络协议
dest_addr.sin_port = htons(port);
dest_addr.sin_addr.s_addr = inet_addr(ip);
// printf("ret%d msg:%s\n",ret,msg);
if(ret > 0)
{
if(strncmp(msg,"##loin",6)==0) //登陆通知
{
sscanf(msg,"##loin %s %s %s %s %s",name_buf,ip_buf,sign_buf,path,share_file);
printf("[%s]:%s上线了", name_buf, ip_buf);
printf("个性签名:%s\n",sign_buf);
//判断是否存在链表中 不存在则加入链表
if(FindNodes(head, name_buf) == NULL)
{
//加入链表中
Insert_Node_tail(head,udp_socket,name_buf,ip_buf,sign_buf,share_path,share_file);
}
//回发自己的信息
sendto(udp_socket,people,strlen(people),0,(struct sockaddr*)&dest_addr,len);
continue;
}
if(strncmp(msg,"##reply",7)==0) //回发自己信息 让别人的好友列表有你在!
{//##reply 小明 192.168.1.211 个性签名:city:揭阳Time:2022-09-0420:55:08weather:多云temp:32.00 ./ ||1.png
sscanf(msg,"##reply %s %s 个性签名:%s %s %s",name_buf,ip_buf,sign_buf,path,share_file);
//判断是否存在链表中 不存在则加入链表
if(FindNodes(head, name_buf) == NULL)
{
//加入链表中
Insert_Node_tail(head,udp_socket,name_buf,ip_buf,sign_buf,share_path,share_file);
}
continue;
}
if(strncmp(msg,"##quit",6)==0) //退出通知
{
sscanf(msg,"##quit %s %s",name_buf,ip_buf);
printf(" [%s] %s:下线了\n",ip_buf, name_buf);
//判断是否存在链表中 存在则删除
if(FindNodes(head, name_buf) != NULL)
{
Remove_Node(head, name_buf);
}
continue;
}
if(strncmp(msg,"##share",7)==0) //接收共享文件请求 发送文件给好友
{
sscanf(msg,"##share %s",share_path);
printf("收到[%s]共享文件下载请求\n",ip);
//tcp发送共享文件
send_file(share_path,ip,9898);
continue;
}
if(strncmp(msg,"##msg",5) == 0) //接收好友私聊
{
char strmsg[1024] = {0};
sscanf(msg,"##msg %s",strmsg);
printf("[%s] %d: %s\n",ip, port, strmsg );
continue;
}
if(strncmp(msg,"##all",5) == 0) //接收群聊消息
{ //##all[小明] 192.168.1.211: 66
char strname[128] = {0};
char msg1[1024] = {0};
sscanf(msg,"##all %s %s",strname,msg1);
printf("群聊消息: %s[%s] : %s\n",strname,ip, msg1);
}
}
}
}
接收文件线程
//接收文件线程
void *recv_tcpfile(void *arg)
{
printf("服务器启动了。。\n");
while (1)
{
int ret =recv_file();//接收文件 端口设置6666
if(ret == -1)
{
break;
}
}
}
捕捉ctrl+c信号 ?
void catch_sigint(int arg)
{
//退出
printf("触发键盘信号函数\n");
//拼接退出通知协议
char quit[1024]={0};
char name_buf[512] = {0};
char ip_buf[128] = {0};
sscanf(people,"##reply %s %s 个性签名:",name_buf,ip_buf);
sprintf(quit,"%s %s %s\n","##quit",name_buf,ip_buf);
//设计退出通知协议 quit 姓名 IP
//发送一个UDP 广播数据报
int ret = sendto(udp_socket,quit,strlen(quit),0,(struct sockaddr *)&dest_addr,sizeof(dest_addr));
if(ret < 0 )
{
perror("");
}
close(udp_socket);
system("killall -9 main");//有时退出的时候进程会还在 得关了否则会出现udp绑定失败
}
?tcp实现文件接收发送协议:
好友先绑定tcp服务器的端口,自己连接服务器 发送文件信息头(名字+大小)给好友,好友接收到头信息回发 可以发送了 创建文件并使用read接收文件 write写入文件,然后自己发送文件的内容write/send 好友接收并保存。?
//文件发送函数
int send_file(char *pathname, char *ip ,int port)
{
//发送文件名与大小给服务器 file 文件名 文件大小
int fd = open(pathname,O_RDWR);
if(fd < 0)
{
perror("");
return -1;
}
//1.创建客户端通信socket
int tcp_socket1 = socket(AF_INET, SOCK_STREAM, 0);
if(tcp_socket1 < 0 )
{
perror("");
}
//2.设置服务器信息
struct sockaddr_in addr;
addr.sin_family = AF_INET; //ipv4
addr.sin_port = htons(port);//端口为 8686
addr.sin_addr.s_addr = inet_addr(ip); //本地网卡地址
//链接服务器
int ret=connect(tcp_socket1,(struct sockaddr *)&addr,sizeof(addr));
if(ret < 0)
{
perror("链接失败\n");
return -1;
}else{
printf("连接好友成功 准备发送文件\n");
}
//获取文件大小
struct stat file_size;
stat(pathname,&file_size);
char file_msg[1024]={0};
//拼接协议
sprintf(file_msg,"file %s %ld",pathname,file_size.st_size);
//发送给服务器
int a = write(tcp_socket1,file_msg,strlen(file_msg));
//等待服务器应答
char req[1024]={0};
read(tcp_socket1,req,1024);
if(strcmp(req,"get_file_msg") == 0) //应答成功
{
//发送文件内容
while (1)
{
//读取源文件数据
char data[4096]={0};
int size = read(fd,data,4096);
if(size <= 0)
{
printf("发送完毕\n");
break;
}
//发送到网络中
write(tcp_socket1,data,size);
}
}
//等待服务器接收完毕
bzero(req,1024);
read(tcp_socket1,req,1024);
// write(new_socet,"down_ok",strlen("down_ok"));
if(strcmp(req,"down_ok") == 0)
{
printf("关闭所有链接\n");
close(tcp_socket1);
close(fd);
}
}
//接收文件
int recv_file()
{
//4.接收客户端的连接请求
printf("等待客户端发送文件\n");
int new_socet = accept(tcp_socket,NULL,NULL);
if(new_socet > 0) //连接成功
{
printf("开始接收………\n");
//接收文件名+文件大小
char file_msg[1024]; //file 文件名 文件大小
read(new_socet,file_msg,1024);
//获取文件名和文件大小
char file_name[1024];
int file_size;
if(strstr(file_msg,"file"))
{
sscanf(file_msg,"file %s %d",file_name,&file_size);
}else{
printf("解析文件失败\n");
close(new_socet);
//continue;
}
printf("对方发送的文件名 %s,文件大小 %d\n",file_name,file_size);
//告诉发送端,已经得到了文件的信息
write(new_socet,"get_file_msg",strlen("get_file_msg"));
//创建文件
int fd = open(file_name,O_RDWR|O_CREAT|O_TRUNC,0777);
//下载大小
int down_size=0;
//不断接收数据
while (1)
{
//读取网络数据
char data[4096]={0};
int size = read(new_socet,data,4096);
down_size+=size;
//写入本地文件
write(fd,data,size);
//判断是否下载完毕
if(down_size >= file_size)
{
printf("下载完毕\n");
//告诉发送端已经下载完毕,可以断开链接
write(new_socet,"down_ok",strlen("down_ok"));
close(fd);
close(new_socet);
break;
}else{
printf("下载进度 %d %%\n",down_size*100/file_size);
}
}
}
}
链表操作:
struct node
{
char name[50];
char ip[50];
int socket; //描述符
char signature[1024];//个性签名
char pathname[512];//共享文件路径
char share_file[2048];//共享文件夹下的所有文件
struct node *next;
};
项目难点
第一个是udp/tcp协议的拼接使用,通过判断标志位来分别数据的不同和数据操作传输。
第二个是由于自己校园网 使用ucp比较繁琐 最终使用路由器解决 (开发板连接路由器,电脑连接路由器的网)但开发板有个缺点不支持udp组播。?
得设置绑定本地所有网卡 开发板或者虚拟机才能正常使用,广播发送接收数据。。。。 一开始的绑定自己的ip?
第三个是关闭的时候有时进程还在运行 得用killall -9 把进程关闭
心得体会?
此次网络编程项目运用的知识点比较广泛,基本覆盖了整个所学的知识,认识到udp/tcp/http等协议的实际编程运用,实现跨主机的数据交互。进一步提高自己的代码知识,项目代码编写更加规范。
|