zoukankan      html  css  js  c++  java
  • 基于 UDP 的 组播、广播详解

    背景

    有些时候我们在网络通信中也需要用到 组播(多播)、广播。现在我们来介绍如何实现。

    建议:在此之前,关闭防火墙。

    ubuntu: service ufw stop
    windows: 控制面板关闭

    有关知识

    基本概念

    1. 单播:两个主机间单对单的通信
    2. 广播:一个主机对整个局域网上所有主机上的数据通信(网络地址全1)

    单播和广播是两个极端,要么对一个主机进行通信,要么对整个局域网的主机进行通信

    1. 组播:实际情况下,经常需要对一组特定的主机进行通信,而不是所有局域网上的主机
    • IP组播(也称多址广播或多播),是一种允许一台或多台主机发送数据包到多台主机的TCP/IP网路技术。

    • 多播是 IPv6 数据包的 3 种基本目的地址类型之一,多播是一点对多点的通信, IPv6 没有采用 IPv4 中的组播术语,而是将广播看成是多播的一个特殊例子。

    多播组只能用UDP 或者原始套接字实现,不能用TCP。

    广播地址

    在使用TCP/IP 协议的网络中,主机标识段host ID 为全1 的IP 地址为广播地址,广播的分组传送给host ID段所涉及的所有计算机。

    传输层只有UDP可以广播 。

    组播地址

    IP 组播通信必须依赖于 IP 多播地址,在 IPv4 中它是一个 D 类 IP 地址,范围从 224.0.0.0 到 239.255.255.255,并被划分为局部链接多播地址、预留多播地址和管理权限多播地址3类:

    • 局部链接多播地址范围在 224.0.0.0~224.0.0.255,这是为路由协议和其它用途保留的地址,路由器并不转发属于此范围的IP包;

    • 预留多播地址为 224.0.1.0~238.255.255.255,可用于全球范围(如Internet)或网络协议;

    • 管理权限多播地址为 239.0.0.0~239.255.255.255,可供组织内部使用,类似于私有 IP 地址,不能用于 Internet,可限制多播范围。

    组播地址与MAC地址的映射

    使用同一个 IP 多播地址接收多播数据包的所有主机构成了一个主机组,也称为多播组。一个多播组的成员是随时变动的,一台主机可以随时加入或离开多播组,多播组成员的数目和所在的地理位置也不受限制,一台主机也可以属于几个多播组。

    这个我们可以这样理解,多播地址就类似于 QQ 群号,多播组相当于 QQ 群,一个个的主机就相当于群里面的成员。

    设备驱动程序就必须接收所有多播数据帧,然后对它们进行过滤,这个过滤过程是网络驱动或IP层自动完成。(设备驱动程序会对多播数据进行过滤,将其发到相应的位置)

    组播应用

    1. 单点对多点应用

    点对多点应用是指一个发送者,多个接收者的应用形式,这是最常见的多播应用形式。典型的应用包括:媒体广播、媒体推送、信息缓存、事件通知和状态监视等。

    1. 多点对单点应用

    多点对点应用是指多个发送者,一个接收者的应用形式。通常是双向请求响应应用,任何一端(多点或点)都有可能发起请求。典型应用包括:资源查找、数据收集、网络竞拍、信息询问等。

    1. 多点对多点应用

    多点对多点应用是指多个发送者和多个接收者的应用形式。通常,每个接收者可以接收多个发送者发送的数据,同时,每个发送者可以把数据发送给多个接收者。典型应用包括:多点会议、资源同步、并行处理、协同处理、远程学习、讨论组、分布式交互模拟(DIS)、多人游戏等。

    组播编程

    多播程序框架主要包含套接字初始化、设置多播超时时间、加入多播组、发送数据、接收数据以及从多播组中离开几个方面。其步骤如下:

    1)建立一个socket。

    2)然后设置接收方多播的参数,例如超时时间TTL、本地回环许可LOOP等。

    3)设置接收方加入多播组。

    4)发送和接收数据。

    5)从多播组离开。

    我们需要用到 setsocket 函数 ,使用这些参数:

    int setsockopt(int sockfd, int level, int optname,
                          const void *optval, socklen_t optlen);
    
    struct ip_mreq          
    { 
    
        struct in_addr imn_multiaddr; // 多播组 IP,类似于 群号
    
        struct in_addr imr_interface; // 将要添加到多播组的 IP,类似于 成员号
    
    };
    
    struct in_addr
    {
        in_addr_t s_addr;
    }
    
    // 当imr_interface 为 INADDR_ANY 时,选择的是默认组播接口。
    

    level :

    • IPPROTO_IP

    optname:

    • IP_MULTICAST_LOOP 支持多播数据回送
    • IP_ADD_MEMBERSHIP 加入多播组
    • IP_DROP_MEMBERSHIP 离开多播组

    默认情况下,当本机发送组播数据到某个网络接口时,在IP层,数据会回送到本地的回环接口,选项IP_MULTICAST_LOOP用于控制数据是否回送到本地的回环接口。

    使用IP_ADD_MEMBERSHIP选项每次只能加入一个网络接口的IP地址到多播组,但并不是一个多播组仅允许一个主机IP地址加入,可以多次调用IP_ADD_MEMBERSHIP选项来实现多个IP地址加入同一个广播组,或者同一个IP地址加入多个广播组。

    optval:

    • IP_MULTICAST_LOOP 选项对应传入 unsigned int 来确认是否支持多播数据回送
    • IP_ADD_MEMBERSHIP 传入 ip_mreq
    • IP_DROP_MEMBERSHIP 传入 ip_mreq

    组播例程

    group_client.c

    /*
    #    Copyright By Schips, All Rights Reserved
    #    https://gitee.com/schips/
    #
    #    File Name:  group_client.c
    #    Created  :  Mon 23 Mar 2020 04:00:49 PM CST
    */
    
    #include <stdio.h>
    #include <string.h>
    #include <unistd.h>
    #include <arpa/inet.h>
    #include <sys/types.h>          /* See NOTES */
    #include <sys/socket.h>
    
    
    #define IP_FOUND "IP_FOUND"
    #define IP_FOUND_ACK "IP_FOUND_ACK"
    
    
    /*
    广播与多播只支持UDP协议,因为TCP协议是端到端,这与广播与多播的理念相冲突
    广播是局域网中一个主机对所有主机的数据通信,而多播是一个主机对一组特定的主机进行通信.多播可以是因特网,而广播只能是局域网。多播常用于视频电话,网上会议等。
    
    setsockopt设置套接字选项可以设置多播的一些相关信息
    
    IP_MULTICAST_TTL //设置多播的跳数值
    IP_ADD_MEMBERSHIP //将主机的指定接口加入多播组,以后就从这个指定的接口发送与接收数据
    IP_DROP_MEMBERSHIP //主机退出多播组
    IP_MULTICAST_IF //获取默认的接口或设置多播接口
    IP_MULTICAST_LOOP //设置或禁止多播数据回送,即多播的数据是否回送到本地回环接口
    
    例子:
    int ttl=255;
    setsockopt(socket,IPPROTO_IP,IP_MULTICAST_TTL,&ttl,sizeof(ttl));//设置跳数
    
    socket           -套接字描述符
    PROTO_IP         -选项所在的协议层
    IP_MULTICAST_TTL -选项名
    &ttl             -设置的内存缓冲区
    sizeof(ttl)      -设置的内存缓冲区长度
    
    struct in_addr in;
    
    setsockopt(socket,IPPROTO_IP,IP_MUTLICAST_IF,&in,sizeof(in));//设置组播接口
    
    int yes=1;
    setsockopt(socket,IPPROTO_IP,IP_MULTICAST_LOOP,&yes,sizeof(yes));//设置数据回送到本地回环接口
    
    struct ip_mreq addreq;
    setsockopt(socket,IPPROTO_IP,IP_ADD_MEMBERSHIP,&req,sizeof(req));//加入组播组
    
    struct ip_mreq dropreq;
    setsockopt(socket,IPPROTO_IP,IP_DROP_MEMBERSHIP,&dropreq,sizeof(dropreq));//离开组播组
    
    
    */
    
    
    #define MCAST_ADDR "224.0.0.88"
    
    int main(int argc ,char **argv)
    {
            int ret,count;
            int sock_fd;
            char send_buf[20];
            char recv_buf[20];
    
            struct sockaddr_in server_addr; //多播地址
            struct sockaddr_in our_addr;
            struct sockaddr_in recvaddr;
            int so_broadcast=1;
    
            socklen_t  socklen;
    
            sock_fd =  socket(AF_INET, SOCK_DGRAM, 0);
    
    
            memset(&server_addr,0,sizeof(server_addr));
            server_addr.sin_family = AF_INET;
    
            //server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
            server_addr.sin_addr.s_addr=inet_addr(MCAST_ADDR);  //多播地址
            server_addr.sin_port = htons(6666);
    
    
        	//客户端绑定通信端口,否则系统自动分配
            memset(&our_addr,0,sizeof(our_addr));
            our_addr.sin_family = AF_INET;
            our_addr.sin_port = htons(7777);
            our_addr.sin_addr.s_addr = htonl(INADDR_ANY); //MCAST_ADDR
            //自定义地址如果为有效地址
            //则协议栈将自定义地址与端口信息发送到接收方
            //否则协议栈将使用默认的回环地址与自动端口
            //our_addr.sin_addr.s_addr = inet_addr("127.0.0.10");
    
            ret = bind(sock_fd, (struct sockaddr *)&our_addr, sizeof(our_addr) );
            if(ret == -1)
            {
                    perror("bind !");
            }
    
        socklen = sizeof(struct sockaddr);
            strncpy(send_buf,IP_FOUND,strlen(IP_FOUND)+1);
    
            for(count=0;count<1;count++)
            {
                    ret = sendto(sock_fd, send_buf, strlen(send_buf)+1, 0,(struct sockaddr *)&server_addr, socklen);
                    if(ret != strlen(send_buf)+1)
                    {
                            perror(" send to !");
                    }
    
                    ret = recvfrom(sock_fd, recv_buf, sizeof(recv_buf), 0,(struct sockaddr *)&recvaddr, &socklen);
                    if(ret < 0 )
                    {
                            perror(" recv! ");
                    }
    
                    printf(" recv server addr : %s 
    ", (char *)inet_ntoa(recvaddr.sin_addr));
                    printf(" recv server port : %d 
    ", ntohs(recvaddr.sin_port) );
                    printf(" recv server msg :%s 
    ", recv_buf);
    
            }
    
            close(sock_fd);
    
            return 0;
    }
    

    group_server.c

    /*
    #    Copyright By Schips, All Rights Reserved
    #    https://gitee.com/schips/
    #
    #    File Name:  group_server.c
    #    Created  :  Mon 23 Mar 2020 04:02:12 PM CST
    */
    
    #include <stdio.h>
    #include <string.h>
    #include <unistd.h>
    //#include <linux/in.h>
    #include <arpa/inet.h>
    #include <sys/types.h>          /* See NOTES */
    #include <sys/socket.h>
    
    #define IP_FOUND  "IP_FOUND"
    #define IP_FOUND_ACK  "IP_FOUND_ACK"
    #define MCAST "224.0.0.88"
    
    //说明:设置主机的TTL值,是否允许本地回环,加入多播组,然后服务器向加入多播组的主机发送数据,主机接收数据,并响应服务器。
    
    int main(int argc,char **argv)
    {
    
            int sock_fd,client_fd;
            int ret;
            struct sockaddr_in localaddr;
            struct sockaddr_in recvaddr;
            socklen_t  socklen;
            char recv_buf[20];
            char send_buf[20];
            int ttl = 10;//如果转发的次数等于10,则不再转发
            int loop=0;
    
            sock_fd = socket(AF_INET, SOCK_DGRAM , 0);
            if(sock_fd == -1)
            {
                    perror(" socket !");
            }
    
            memset(&localaddr,0,sizeof(localaddr));
            localaddr.sin_family = AF_INET;
            localaddr.sin_port = htons(6666);
            localaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    
            ret = bind(sock_fd, (struct sockaddr *)&localaddr,sizeof(localaddr));
            if(ret == -1)
            {
                    perror("bind !");
            }
    
             socklen = sizeof(struct sockaddr);
    
            //设置多播的TTL值
            if(setsockopt(sock_fd, IPPROTO_IP, IP_MULTICAST_TTL, &ttl, sizeof(ttl))<0){
                    perror("IP_MULTICAST_TTL");
                    return -1;
            }
            //设置数据是否发送到本地回环接口
            if(setsockopt(sock_fd, IPPROTO_IP, IP_MULTICAST_LOOP, &loop, sizeof(loop))<0){
                    perror("IP_MULTICAST_LOOP");
                    return -1;
            }
            //加入多播组
            struct ip_mreq mreq;
            mreq.imr_multiaddr.s_addr=inet_addr(MCAST);//多播组的IP
            mreq.imr_interface.s_addr=htonl(INADDR_ANY);//本机的默认接口IP,本机的随机IP
            if(setsockopt(sock_fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) < 0){
                    perror("IP_ADD_MEMBERSHIP");
                    return -1;
            }
    
            while(1)
            {
                    ret = recvfrom(sock_fd, recv_buf, sizeof(recv_buf), 0,(struct sockaddr *)&recvaddr, &socklen);
                    if(ret < 0 )
                    {
                            perror(" recv! ");
                    }
    
                    printf(" recv client addr : %s 
    ", (char *)inet_ntoa(recvaddr.sin_addr));
                    printf(" recv client port : %d 
    ",ntohs(recvaddr.sin_port));
                    printf(" recv msg :%s 
    ", recv_buf);
    
                    if(strstr(recv_buf,IP_FOUND))
                    {
                            //响应客户端请求
                            strncpy(send_buf, IP_FOUND_ACK, strlen(IP_FOUND_ACK) + 1);
                            ret = sendto(sock_fd, send_buf, strlen(IP_FOUND_ACK) + 1, 0, (struct sockaddr*)&recvaddr, socklen);//将数据发送给客户端
                            if(ret < 0 )
                            {
                                    perror(" sendto! ");
                            }
                            printf(" send ack  msg to client !
    ");
                    }
            }
    
            // 离开多播组
            ret = setsockopt(sock_fd, IPPROTO_IP, IP_DROP_MEMBERSHIP, &mreq, sizeof(mreq));
            if(ret < 0){
                    perror("IP_DROP_MEMBERSHIP");
                    return -1;
            }
    
            close(sock_fd);
    
            return 0;
    }
    

    广播编程

    广播的实现比较简单,只需要设置socket允许广播(想广播的终端设置即可),然后在发送数据前指定为广播地址即可。我们直接看例程吧。

    boardcast_client.c

    /*
    #    Copyright By Schips, All Rights Reserved
    #    https://gitee.com/schips/
    #
    #    File Name:  boardcast_client.c
    #    Created  :  Mon 23 Mar 2020 04:29:48 PM CST
    */
    
    #include <stdio.h>
    #include <unistd.h>
    #include <sys/types.h>          /* See NOTES */
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <netinet/ip.h>
    #include <arpa/inet.h>
    
    int main()
    {
        char send_buf[100] = "GET Msg";
        char recv_buf[100];
    
        // 1 创建一个套接字,用于网络通信
        int sk_fd = socket(PF_INET, SOCK_DGRAM, 0);
        if (sk_fd == -1)
        {
            perror("socket");
            return -1;
        }
    
        // 2 绑定服务的IP与端口
        struct sockaddr_in ser_addr;
        ser_addr.sin_family  = PF_INET;
        ser_addr.sin_port  = htons (6666) ;
        ser_addr.sin_addr.s_addr = inet_addr("192.168.1.88");
        int ret =  bind(sk_fd, (struct sockaddr *)&ser_addr,sizeof(ser_addr));
    
        if (ret == -1)
        {
             perror("bind");
             return -1;
        }
        // 3 等待 服务器广播
        struct sockaddr_in src_addr;
        socklen_t size = sizeof(ser_addr);
        ret =  recvfrom(sk_fd, recv_buf, sizeof(recv_buf), 0,(struct sockaddr *)&src_addr, &size);
        if (ret == -1)
        {
           perror("reveform");
           return -1;
        }
        printf("recv :%s
    ",recv_buf);
    
        // 4 关闭套接字
        close(sk_fd);
    
        return 0;  
    }
    

    boardcast_server.c

    /*
    #    Copyright By Schips, All Rights Reserved
    #    https://gitee.com/schips/
    #
    #    File Name:  boardcast_server.c
    #    Created  :  Mon 23 Mar 2020 04:29:48 PM CST
    */
    
    // UDP 服务端
    #include <stdio.h>
    #include <unistd.h>
    #include <string.h>
    
    #include <sys/types.h>          /* See NOTES */
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    
    
    int main()
    {
    	char send_buf[100] = "hello client  6666";
    	char recv_buf[100];
    
    	// 1 创建一个套接字,用于网络通信
    	int sk_fd = socket(PF_INET, SOCK_DGRAM, 0);
    	if (sk_fd == -1)
    	{
    	    perror("socket");
    	    return -1;
    	}
    
    	//设置广播使能
    	int on = 1;
    	setsockopt(sk_fd, SOL_SOCKET, SO_BROADCAST, &on, sizeof(on));
    
    
        // 2 绑定服务的IP与端口
        struct sockaddr_in ser_addr;
        ser_addr.sin_family  = PF_INET;
        ser_addr.sin_port  = htons (6666) ;
        ser_addr.sin_addr.s_addr = inet_addr("192.168.1.88");
        int ret =  bind(sk_fd, (struct sockaddr *)&ser_addr,sizeof(ser_addr));
        if (ret == -1)
        {
             perror("bind");
             return -1;
        }
    
    
        // 3 广播数据
        struct sockaddr_in client_addr;
        client_addr.sin_family  = PF_INET;
        client_addr.sin_port  = htons (6666) ;
        client_addr.sin_addr.s_addr = inet_addr("192.168.1.255");
        socklen_t size = sizeof(struct sockaddr_in);
    
        while(1) 
        {
    
            ret =  sendto(sk_fd, send_buf, sizeof(send_buf), 0,(struct sockaddr *)&client_addr, size);
            if (ret == -1)
            {
                perror("sendto");
                return -1;
             }
    
        }
    
         // 4 关闭套接字
         close(sk_fd);
    
         return 0;
    }
    
  • 相关阅读:
    poj 1014||hdu 1059 dividing(多重背包 二进制优化)
    Java多线程循环打印ABC的5种实现方法
    java资料搜索网站
    idea 离线安装 lombok插件
    解决TIME_WAIT过多造成的问题
    JAVA线程池详解
    linux vmstat命令
    Mysql慢查询
    sql中强制使用索引
    JAVA 利用 jmc或jvisualvm 监控 本地或者远程JVM
  • 原文地址:https://www.cnblogs.com/schips/p/12552534.html
Copyright © 2011-2022 走看看