什么是组播,组播与单播,组播与广播有什么差别?是初学者首先要搞清楚的问题。
我们知道单播、组播和广播都是IP报文网络传输的三种模式,它们的定义如下:

  • 单播是主机间一对一的通讯模式,网络中的设备根据网络报文中包含的目的地址选择传输路径,将单播报文传送到指定的目的地,只对接收到的数据进行转发,不会进行复制。它能够针对每台主机及时的响应,现在的网页浏览全部都是采用单播模式。

  • 广播是主机间一对所有的通讯模式,设备会将报文发送到网络中的所有可能接收者。设备简单地将它收到的任何广播报文都复制并转发到除该报文到达的接口外的每个接口。广播处理流程简单,不用选择路径。

  • 组播是主机间一对多的通讯模式, 组播是一种允许一个或多个组播源发送同一报文到多个接收者的技术。组播源将一份报文发送到特定的组播地址,组播地址不同于单播地址,它并不属于特定某个主机,而是属于一组主机。一个组播地址表示一个群组,需要接收组播报文的接收者都加入这个群组。

点到多点场景传输方式选择

假定场景

  • 网络中有用户A,用户B,用户C,用户D和用户E
  • 只有用户A,用户B和用户C有流量需求,想接收数据源发送的报文

现分别采用单播组播广播方式进行报文的传输:

单播流量发送

采用单播传输方式后,要确保有流量需求的用户都能收到流量,**数据源需要发送三份流量,相应的网络中设备B也需要承载三份流量**。

广播流量发送

采用广播传输方式后,**数据源仅发送一份流量**,有流量需求的用户可以收到流量,但是从图中可以看出,**因为广播的传输机制,无流量需求的用户D和用户E也收到了流量,存在流量的冗余**。

组播流量发送

采用组播传输方式后,**数据源仅发送一份流量**,有流量需求的用户就可以收到流量,而且**无流量需求的用户D和用户E也不会收到冗余的流量**。

三种方式比较

组播实现机制

组播组网实现用户A,用户B和用户C能接收到数据源的流量,需要做如下部署:

  • 全网设备相连的接口都需要配置单播IP地址

  • 全网要部署单播路由协议,确保数据源和用户A、用户B和用户C之间路由互通

  • 全网部署组播PIM协议(PIM-SM或者PIM-DM),并配置相应的BSR和RP,图中配置设备B为RP和BSR。

  • 用户A,用户B,用户C必须发送IGMP组加入报文,组加入报文中包含其需要加入的组IP地址225.1.1.1。

数据源发送数据流,该数据流二层目的MAC地址是组播的MAC地址,IP报文的目的地址为225.1.1.1。

组播实现机制总体来说就是接收者告诉一个中心节点(在组播协议里面称为RP),它需要哪些组地址的流量RP需要被告之数据源(在组播场景中我们称之为组播源,它的特点是流量二层头中目的MAC地址是组播MAC地址,IP报层IP报文头目的地址是组播IP地址)在哪,数据源往哪些组地址发流。RP知道了接收方和发送方的所有信息后,就会根据需要把流量发送到特定位置(它发送的过程中建立了组播分发树)。

组播地址

使用iperf测试UDP组播

  • client

    1
    iperf -c 225.0.0.20 -u --ttl 5 -t 10
  • server

    1
    iperf -s -u -B 225.0.0.20 -i 1

Linux UDP 组播编程

组播相关sockopt选项

Flag 说明
IP_ADD_MEMBERSHIP 加入到指定的多播组
IP_DROP_MEMBERSHIP 退出指定的多播组
IP_MULTICAST_IF 指定多播数据流从那个网络接口(interface)发出
IP_MULTICAST_TTL 设置发送多播数据的TTL值。默认为1,如果TTL等于0,将不会在其他任何子网(sub-network)传输,TTL大于1, 可分发到多个子网,每次经过一个路由,TTL减1
IP_MULTICAST_LOOP 设置是否将多播数据副本传送到发送主机

发送组播数据包

流程

代码示例

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
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
/* Send Multicast Datagram code example. */

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

struct in_addr localInterface;
struct sockaddr_in groupSock;
int sd;
char databuf[1024] = "Multicast test message lol!";
int datalen = sizeof(databuf);

#define UDP_MULTICAST_ADDR "226.1.1.1"
#define UDP_LOCAL_ADDR "192.168.167.11"
#define UDP_MULTICAST_PORT 4321

#define CONFIG_MULTICAST_LOOP_EN 0

int main (int argc, char *argv[])
{
/* Create a datagram socket on which to send. */
sd = socket(AF_INET, SOCK_DGRAM, 0);

if(sd < 0) {
perror("Opening datagram socket error");
exit(1);
}
else {
printf("Opening the datagram socket...OK.\n");
}

/* Initialize the group sockaddr structure with a group address and port. */
memset((char *) &groupSock, 0, sizeof(groupSock));
groupSock.sin_family = AF_INET;
groupSock.sin_addr.s_addr = inet_addr(UDP_MULTICAST_ADDR);
groupSock.sin_port = htons(UDP_MULTICAST_PORT);

/* setting loopback, to control wether you do not receive your own datagrams or not.*/
char loopch = CONFIG_MULTICAST_LOOP_EN;
if(setsockopt(sd, IPPROTO_IP, IP_MULTICAST_LOOP, (char *)&loopch, sizeof(loopch)) < 0) {
perror("Setting IP_MULTICAST_LOOP error");
close(sd);
exit(1);
}
else {
printf("Disabling the loopback...OK.\n");
}

/* Set local interface for outbound multicast datagrams. */
/* The IP address specified must be associated with a local, */
/* multicast capable interface. */
localInterface.s_addr = inet_addr(UDP_LOCAL_ADDR);
if(setsockopt(sd, IPPROTO_IP, IP_MULTICAST_IF, (char *)&localInterface, sizeof(localInterface)) < 0) {
perror("Setting local interface error");
exit(1);
}
else {
printf("Setting the local interface...OK\n");
}

/* Send a message to the multicast group specified by the groupSock sockaddr structure. */
if(sendto(sd, databuf, datalen, 0, (struct sockaddr*)&groupSock, sizeof(groupSock)) < 0) {
perror("Sending datagram message error");
}
else {
printf("Sending datagram message...OK\n");
}

/* Try the re-read from the socket if the loopback is not disable*/
#if CONFIG_MULTICAST_LOOP_EN
memset(databuf, 0, sizeof(databuf));
if(read(sd, databuf, datalen) < 0) {
perror("Reading datagram message error\n");
close(sd);
exit(1);

}
else {
printf("Reading datagram message from client...OK\n");
printf("The message is: %s\n", databuf);
}
#endif
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
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
/* Receiver/client multicast Datagram example. */

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

struct sockaddr_in localSock;
struct ip_mreq group;

int sd;
int datalen;
char databuf[1024];

#define CONFIG_ADDRESS_REUSER_EN 1
#define UDP_MULTICAST_ADDR "226.1.1.1"
#define UDP_LOCAL_ADDR "192.168.166.51"
#define UDP_MULTICAST_PORT 4321

int main(int argc, char *argv[])
{
/* Create a datagram socket on which to receive. */
sd = socket(AF_INET, SOCK_DGRAM, 0);
if(sd < 0) {
perror("Opening datagram socket error");
exit(1);
}
else {
printf("Opening datagram socket....OK.\n");
}

/* Enable SO_REUSEADDR to allow multiple instances of this
application to receive copies of the multicast datagrams. */
#if CONFIG_ADDRESS_REUSER_EN
int reuse = 1;
if(setsockopt(sd, SOL_SOCKET, SO_REUSEADDR, (char *)&reuse, sizeof(reuse)) < 0) {
perror("Setting SO_REUSEADDR error");
close(sd);
exit(1);
}
else {
printf("Setting SO_REUSEADDR...OK.\n");
}
#endif

/* Bind to the proper port number with the IP address */
/* specified as INADDR_ANY. */
memset((char *) &localSock, 0, sizeof(localSock));
localSock.sin_family = AF_INET;
localSock.sin_port = htons(UDP_MULTICAST_PORT);
localSock.sin_addr.s_addr = INADDR_ANY;

if(bind(sd, (struct sockaddr*)&localSock, sizeof(localSock))) {
perror("Binding datagram socket error");
close(sd);
exit(1);
}
else {
printf("Binding datagram socket...OK.\n");
}

/* Join the multicast group 226.1.1.1 on the local 203.106.93.94 */
/* interface. Note that this IP_ADD_MEMBERSHIP option must be */
/* called for each local interface over which the multicast */
/* datagrams are to be received. */
group.imr_multiaddr.s_addr = inet_addr(UDP_MULTICAST_ADDR);
group.imr_interface.s_addr = inet_addr(UDP_LOCAL_ADDR);

if(setsockopt(sd, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char *)&group, sizeof(group)) < 0) {
perror("Adding multicast group error");
close(sd);
exit(1);
}
else {
printf("Adding multicast group...OK.\n");
}

/* Read from the socket. */
datalen = sizeof(databuf);
if(read(sd, databuf, datalen) < 0)
{
perror("Reading datagram message error");
close(sd);
exit(1);
}

else {
printf("Reading datagram message...OK.\n");
printf("The message from multicast server is: \"%s\"\n", databuf);
}

return 0;
}

组播应用

IPTV

参考引用

什么是组播
组播IP地址到底是谁的IP??
c/c++:UDP(udp通信、广播、组播),本地套接字
局域网UDP组播与设备自动发现测试
NETWORK PROGRAMMING LINUX SOCKET PART 13: MULTICAST