基於 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 \n", (char *)inet_ntoa(recvaddr.sin_addr));
                printf(" recv server port : %d \n", ntohs(recvaddr.sin_port) );
                printf(" recv server msg :%s \n", 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 \n", (char *)inet_ntoa(recvaddr.sin_addr));
                printf(" recv client port : %d \n",ntohs(recvaddr.sin_port));
                printf(" recv msg :%s \n", 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 !\n");
                }
        }

        // 離開多播組
        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\n",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;
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM