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;
    }
    
  • 相关阅读:
    android 75 新闻列表页面
    android 74 下载文本
    android 73 下载图片
    android 72 确定取消对话框,单选对话框,多选对话框
    android 71 ArrayAdapter和SimpleAdapter
    android 70 使用ListView把数据显示至屏幕
    maven如何将本地jar安装到本地仓库
    Centos6.7搭建ISCSI存储服务器
    解决maven打包编译出现File encoding has not been set问题
    MySQL 解决 emoji表情 的方法,使用utf8mb4 字符集(4字节 UTF-8 Unicode 编码)
  • 原文地址:https://www.cnblogs.com/schips/p/12552534.html
Copyright © 2011-2022 走看看