利用TCP/UDP 协议制作一个飞秋聊天工具

 视频操作演示:

利用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等协议的实际编程运用,实现跨主机的数据交互。进一步提高自己的代码知识,项目代码编写更加规范。

猜你喜欢

转载自blog.csdn.net/aaa2540567665/article/details/126697010
今日推荐