《Linux網路開發必學教程》17_深入 UDP 資料收發 (下)

TianSong發表於2022-05-16
問題: UDP 是否還有其他一對多的資料傳送方式?

UDP 通訊中的多播

多播是向特定組中的所有主機傳輸資料的方法,多播也稱之為組播
多播資料傳輸的特點:
  • 多播傳送者針對特定的多播組,只傳送 1 次資料,組內主機均可接收到資料
  • 主機加入特定組,即可接收該組中的多播資料
  • 多播組可在 IP 地址範圍內任意新增

關鍵問題:如何收發多播資料

多播組是一個 D 類地址 (224.0.0.0 - 239.255.255.255)
"加入多播組"可理解為 UDP 網路程式進行的申請
  • 如:申請接收發往 239.234.111.222 的多播資料
  • 即:設定屬性 (IPPROTO_IP, IP_ADD_MEMBERSHP
傳送多播資料的方式,與傳送普通 UDP 資料的方式相同
  • 預備操作:設定屬性,如:(IPPROTO_IP, IP_MULTICAST_TTL

注意事項

加入同一個多播組的主機不一定在同一個網路中
因此,必須設定多播資料的最多轉發次數(TTL)
  • TTL(即:Time To Live) 是決定資料傳輸距離的主要因素
  • TTL 用整數表示,並且每經過 1 個路由就減少 1
  • 當 TTL 變為 0 時,資料無法繼續傳遞,只能銷燬

多播程式設計:傳送端

IP_MULTCAST_TTL: 用於設定多播資料的”最遠傳輸距離“,預設 1
IP_MULTICAST_IF: 用於設定多播資料從哪一個網路介面(網路卡)傳送出去,預設:0.0.0.0
預設 0.0.0.0 情況下作業系統會自主選擇使用哪個網路介面,但在[多網路卡主機]的[實際工程應用]中,最好手工指定!!
IP_MULTCAST_LOOP: 用於設定多播資料是否傳送回本機,預設1 (1,傳送回本機)
remote.sin_family = AF_INET;
remote.sin_addr.s_addr = inet_addr("224.1.1.168");
remote.sin_port = htons(8888);

setsockopt(sock, IPPROTO_IP, IP_MULTICAST_TTL, &ttl, sizeof(ttl));

do {
    len = sizeof(remote);
    r = sendto(sock, buf, strlen(buf), 0, (struct sockaddr*)&remote, len);
    sleep(1);
}while(r > 0);

多播程式設計:接收端

IP_ADD_MEMBRESHIP: 用於申請加入多播組,引數為:多播組和本機地址
struct ip_mreq {
    // group address
    struct in_addr inmr_multiaddr;
    // local host address
    struct in_addr imr_interface
}

?

struct ip_mreq group = {0};

group.imr_multiaddr.s_addr = inet_addr("224.1.1.168");
group.imr_interface.s_addr = htonl(INADDR_ANY);  // 監聽所有網路卡

setsockopt(sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, &group, sizeof(group));

do {
    len = sizeof(remote);
    r = recvfrom(sock, buf, sizeof(buf), 0, (struct sockaddr*)&remote, &len);
    buf[r] = 0;
    printf("r = %d\n", r);
    printf("msg = %s\n", buf);
}while (r > 0)

退出多播組

quit = setsockopt(sock, IP_PROTO_IP, IP_DROP_MEMBERSHIP, &group, sizeof(group));

if (quit == 0) {
    break;
}

程式設計實驗:UDP 多播程式設計

mul_tx.c
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>

int main()
{
    int sock = 0;
    struct sockaddr_in remote = {0};
    socklen_t len = 0;
    char buf[128] = "D.T.Software";
    struct in_addr addr = {0};

    int ttl = 0;
    int loop = 0;

    sock = socket(PF_INET, SOCK_DGRAM, 0);

    if (sock == -1) {
        printf("socket error\n");
        return -1;
    }

    remote.sin_family = AF_INET;
    remote.sin_addr.s_addr = inet_addr("224.1.1.168");
    remote.sin_port = htons(8888);

    //-------------------------
    ttl = 0;
    len = sizeof(ttl);
    getsockopt(sock, IPPROTO_IP, IP_MULTICAST_TTL, &ttl, &len);  // 可以不設定,ttl 預設為 1 
    printf("default ttl = %d\n", ttl);

    ttl = 32;
    len = sizeof(ttl);
    setsockopt(sock, IPPROTO_IP, IP_MULTICAST_TTL, &ttl, len); 
    printf("current ttl = %d\n", ttl);

    loop = 0;
    len = sizeof(loop);
    getsockopt(sock, IPPROTO_IP, IP_MULTICAST_LOOP, &loop, &len);    // 可以不設定,ttl 預設為 1 ,即本機也會收到
    printf("default loop = %d\n", loop);

    // addr.s_addr = inet_addr("192.168.3.221");  // 具體指定使用哪一塊網路卡進行資料廣播
    addr.s_addr = htonl(INADDR_ANY);              // 交由作業系統進行選擇
    len = sizeof(addr);
    setsockopt(sock, IPPROTO_IP, IP_MULTICAST_IF, &addr, len);    // 可以不設定,預設為 INADDR_ANY 即 "0.0.0.0", 交由作業系統選擇使用哪個網路卡
    printf("current if = %s\n", inet_ntoa(addr));                // 當主機有多個網路卡,在實際工程使用時,最好手工指定使用那塊網路卡廣播資料
    //-------------------------

    while (1) {
        len = sizeof(remote);

        sendto(sock, buf, strlen(buf), 0, (struct sockaddr*)&remote, len);

        sleep(1);
    }

    close(sock);

    return 0;
}
mul_rx.c
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>

int main()
{
    int server = 0;
    struct sockaddr_in saddr = {0};
    struct sockaddr_in remote = {0};
    struct ip_mreq group = {0};
    int len = 0;
    char buf[32] = {0};
    int r = 0;

    server = socket(PF_INET, SOCK_DGRAM, 0);

    if (server == -1) {
        printf("server socket error");
        return -1;
    }

    saddr.sin_family = AF_INET;
    saddr.sin_addr.s_addr = htonl(INADDR_ANY);
    saddr.sin_port = htons(8888);

    if (bind(server, (struct sockaddr*)&saddr, sizeof(saddr)) == -1) {
        printf("udp server bind error\n");
        return -1;
    }

    printf("udp server start sucess\n");

    group.imr_multiaddr.s_addr = inet_addr("224.1.1.168");
    group.imr_interface.s_addr = htonl(INADDR_ANY);

    setsockopt(server, IPPROTO_IP, IP_ADD_MEMBERSHIP, &group, sizeof(group));

    while (1) {
        len = sizeof(remote);

        r = recvfrom(server, buf, sizeof(buf), 0, (struct sockaddr*)&remote, &len);

        if (r > 0) {
            buf[r] = 0;

            printf("Recvive: %s\n", buf);
        } else {
            break;
        }
    }

    close(server);

    return 0;
}

小結

單播:一對一資料傳送,即:指定目標主機傳送資料
廣播 (必須同一區域網)
  • 本地廣播:本地區域網廣播資料,所有主機均可接收資料
  • 直接廣播:指定網路廣播資料,目標網路中的主機均可接收資料
多播(組播)(可不同一區域網)
  • 向指定的多播地址傳送資料,”訂閱“該地址的主機均可接收資料

相關文章