03-udp-multicast-programming

本文详细讲解如何在Linux环境下使用UDP协议实现组播(多播)通信,包括组播地址分类、核心数据结构`struct ip_mreqn`的用法、完整的发送端和接收端实现,以及实际应用场景。

本文详细讲解如何在Linux环境下使用UDP协议实现组播(多播)通信,包括组播地址分类、核心数据结构struct ip_mreqn的用法、完整的发送端和接收端实现,以及实际应用场景。

一、实验目的

使用UDP实现组播发送消息

二、核心原理/知识点

1. 多播(组播)特点

组播组可以是永久的也可以是临时的 永久组播组:由官方分配固定IP地址范围,组成员动态变化(成员可以是任意的,甚至可以为零) 临时组播组:使用未被永久组占用的地址

2.IP多播地址分类

D类IP地址(224.0.0.0 ~ 239.255.255.255)用于多播:

地址范围 用途 说明
224.0.0.0 ~ 224.0.0.255 局部保留,不跨路由器 224.0.0.1:所有主机224.0.0.2:所有路由器
224.0.1.0 ~ 224.0.1.255 公用组播地址,可用于Internet 使用时需要申请
224.0.2.0 ~ 238.255.255.255 用户可用的临时组播地址 实验环境中常用
239.0.0.0 ~ 239.255.255.255 本地管理组播地址 仅在特定本地范围内有效示例代码中使用:239.0.0.2

三、核心函数详解

3.1 新函数解析

这里没有涉及新的函数,不在阐述

3.2 关键数据结构

1
2
3
4
5
struct ip_mreqn {
    struct in_addr imr_multiaddr;  // 多播组IP地址
    struct in_addr imr_address;    // 本地IP地址(0.0.0.0表示自动选择)
    int imr_ifindex;               // 网络接口索引
};

四、实现步骤分解

服务端

1.创建udp套接字
1
int sfd = socket(AF_INET,SOCK_DGRAM, 0);
2.绑定服务器地址
1
2
3
4
struct socket_in saddr;
saddr.sin_family = AF_INET;
saddr.sin_port = htnos(SERVER_PORT);
saddr.sin_addr.s_addr = htonl(INADDR_ANY);
3.配置多播参数
1
2
3
4
5
6
7
// 设置多播组和网络接口
inet_pton(AF_INET, "239.0.0.2", &group.imr_multiaddr);
inet_pton(AF_INET, "0.0.0.0", &group.imr_address);
group.imr_ifindex = if_nametoindex("ens33"); // 指定本机网卡

// 设置多播出口
setsockopt(sfd, IPPROTO_IP, IP_MULTICAST, &group, sizeof(group));
4. 发送多播数据
1
2
3
4
maddr.sin_family = AF_INET;
inet_pton(AF_INET, "239.0.0.2", &maddr.sin_addr);
maddr.sin_port = htons(CLIENT_PORT);
sendto(sfd, msg, strlen(msg), 0, (struct sockaddr*)&maddr, sizeof(maddr));

客户端

1. 创建UDP套接字并绑定
1
2
3
4
5
6
int confd = socket(AF_INET, SOCK_DGRAM, 0);
// 绑定到特定端口接收数据
client_addr.sin_family = AF_INET;
clinet_addr.sin_port = htons(CLIENT_POORT);
client_addr.sin_addr.s_addr = htonl(INADDR_ANY);
bind(conf, (struct sockaddr*)&client_addr, sizeof(client_addr));
2. 加入多播组
1
2
3
4
5
6
7
// 设置要加入的多播组
inet_pton(AF_INET, "239.0.0.2", &group.imr_multiaddr);
inet_pton(AF_INET, "0.0.0.0", &group.imr_address);
group.imr_ifindex = if_nametoindex("ens33");

// 加入组播组
setsockopt(confd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &group, sizeof(group));
3. 接收多播数据
1
2
// 循环接收组播消息
recvfrom(confd, buf, sizeof(buf)-1, 0, (struct sockaddr*)&client_addr, &addr_len);

重要注意事项

  1. 网络接口选择 代码中使用 if_nametoindex(“ens33”) 指定网卡。 实际部署时需要根据系统网卡名称调整(可能是eth0、wlan0等)。
  2. 端口使用 服务端:绑定在8000端口(发送端口)。 客户端:绑定在9000端口(接收端口)。 多播发送时指定目标端口为9000。
  3. 地址范围选择 实验环境建议使用 239.x.x.x 范围。 避免使用保留地址段。
  4. 一对多通信 一个服务端可以向多个客户端发送相同数据。 客户端只需加入相同的多播组即可接收。

五、完整代码实现

服务端

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <net/if.h>

#define GROUP "239.0.0.2"
#define CLIENT_PORT 9000
#define SERVER_PORT 8000

int main()
{
    int sfd;
    struct sockaddr_in saddr,maddr;
    struct ip_mreqn group;
    sfd = socket(AF_INET,SOCK_DGRAM,0);
    if(sfd < 0)
    {
        perror("socket failed");
        exit(1);
    }
    bzero(&saddr,sizeof(saddr));
    saddr.sin_family = AF_INET;
    saddr.sin_port = htons(SERVER_PORT);
    saddr.sin_addr.s_addr = htonl(INADDR_ANY);
    if(bind(sfd,(struct sockaddr*)&saddr,sizeof(saddr)) < 0)
    {
        perror("bind failed");
        exit(1);
    }
    //设置组播
    inet_pton(AF_INET,GROUP,&group.imr_multiaddr);
    inet_pton(AF_INET,"0.0.0.0",&group.imr_address);
    group.imr_ifindex = if_nametoindex("ens33");
    //组播出口网卡
    setsockopt(sfd, IPPROTO_IP,IP_MULTICAST_IF,&group,sizeof(group));
    //组播目标地址
    bzero(&maddr,sizeof(maddr));
    maddr.sin_family = AF_INET;
    inet_pton(AF_INET,GROUP,&maddr.sin_addr);
    maddr.sin_port = htons(CLIENT_PORT);
    //循环发送组播消息
    char msg[128];
    int cnt = 1;
    while(1)
    {
        snprintf(msg,sizeof(msg),"group msg %d:The villagers' fund reached %d",cnt, 36+cnt);
        sendto(sfd,msg,strlen(msg),0,(struct sockaddr*)&maddr,sizeof(maddr));
        printf("sent:%s\n",msg);
        cnt++;
        sleep(1);
    }
    close(sfd);
    return 0;
}

客户端

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <net/if.h>

#define GROUP "239.0.0.2"
#define CLIENT_PORT 9000

int main() {
    int confd;
    struct sockaddr_in client_addr;
    struct ip_mreqn group;
    char buf[128];
    socklen_t addr_len = sizeof(client_addr);

    if ((confd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
        perror("socket");
        exit(1);
    }

    //绑定监听端口
    bzero(&client_addr, sizeof(client_addr));
    client_addr.sin_family = AF_INET;
    client_addr.sin_port = htons(CLIENT_PORT);
    client_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    if (bind(confd, (struct sockaddr*)&client_addr, sizeof(client_addr)) < 0) {
        perror("bind");
        exit(1);
    }

    //设置组地址,加入组播组
    inet_pton(AF_INET, GROUP, &group.imr_multiaddr);
    inet_pton(AF_INET, "0.0.0.0", &group.imr_address);
    group.imr_ifindex = if_nametoindex("ens33");
    // 加入组播组
    setsockopt(confd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &group, sizeof(group));

    // 接收并打印组播消息
    printf("Waiting for group messages...\n");
    while (1) {
        memset(buf, 0, sizeof(buf));
        int n = recvfrom(confd, buf, sizeof(buf)-1, 0, (struct sockaddr*)&client_addr, &addr_len);
        if (n > 0) {
            printf("Received: %s\n", buf);
        }
    }

    close(confd);
    return 0;
}

六、编译与运行

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# 编译服务端
gcc -o server server.c

# 编译客户端
gcc -o client client.c
# 在不同终端运行/或者不同虚拟机
./server    # 启动服务端,开始发送组播消息
./client    # 启动客户端,加入组播组接收消息

# 可以启动多个客户端实例,都会收到相同的消息

重要注意事项

  1. 网络接口选择 代码中使用 if_nametoindex(“ens33”) 指定网卡

实际部署时需要根据系统网卡名称调整:

Ubuntu/CentOS: ens33, eth0, enp0s3

查看网卡名称:ip addr 或 ifconfig

  1. 端口使用 服务端:绑定在8000端口(发送端口)

客户端:绑定在9000端口(接收端口)

多播发送:指定目标端口为9000

  1. 地址范围选择 实验环境建议使用 239.x.x.x 范围

避免使用保留地址段(224.0.0.0~224.0.0.255)

  1. 一对多通信 一个服务端可以向多个客户端发送相同数据

客户端只需加入相同的多播组即可接收

组播比广播更节省网络带宽

七、应用场景

视频直播:一个源向多个接收者发送视频流

股票行情推送:实时向多个客户端推送行情数据

在线游戏:同步多个玩家的游戏状态

会议系统:多方视频/音频通信

八、实验总结与思考

总结:多播通过使用特定的IP地址范围(224.0.0.0~239.255.255.255),实现了高效的一对多通信。编程时需要注意正确设置多播组、网络接口,并确保发送和接收端口一致。

导航

本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。
使用 Hugo 构建
主题 StackJimmy 设计