目录
多播
多播的原理
多播的数据传输时的特点
TTL 的概念
TTL 和 多播组的配置方法
多播的编程与实现
发送者
接收者
多播
多播是一种介于单播和广播通信之间的技术方式,可以将发送者所需要发送的数据包分别发送给分散在不同子网中的一组接收者。
多播的原理
多播的基础概念是“组”。一个多播组(multicast group)就是一组希望接收特定数据流的接收者,这个组没有物理或者地理的边界:组内的主机可位于互联网或者专用网络的任何地方。
多播组中的每个节点被称为多播组成员(multicastgroupmember)。多播的数据传输协议基于UDP完成。采用多播方式时,可以同时向多个主机传递数据。
多播的数据传输时的特点
- 多播服务器端针对特定多播组,只发送 1 次数据。
- 即使只发送 1 次数据,但该组内的所有客户端都会接收数据。
- 多播组数可在IP地址范围内任意增加。
- 加入特定组即可接收发往该多播组的数据。
多播组的地址属于D类,即 224.0.0.0 ~ 239.255.255.255。在发送多播数据包时(路由器得支持这项功能),需要有机器加入到多播组中以接收发来的数据包,同时路由器担负起将该数据包复制并传递到多个主机中的任务。如下图所示:
TTL 的概念
传输多播数据包时,需要给程序设置TTL(TIme to Live 生存时间),这是决定数据包能否及时准确到达目标点的关键参数。
TTL用整数表示,每经过1个路由器该数值便会减1。当TTL变为0时,该数据包将无法再被传递,作销毁处理。因此,在TTL的值上需要合理设置,过大会影响网络流量,过小将无法将数据包及时传递到目标。
TTL 和 多播组的配置方法
在编程中,我们可以实现对 TTL 的设置。TTL相关的协议层为IPPROTO_IP,选项名为IP_MULTICAST_TTL,通过使用setsocketopt这个函数进行设置。
代码如下:
int sock;
int ttl = 64;
sock = socket(PF_INET, SOCK_DGRAM, 0);
setsockopt(send_sock, IPPROTO_IP, IP_MULTICAST_TTL, (void *)&ttl, sizeof(ttl));
同样,加入到多播组也是通过 setsocketopt 函数完成。多播组加入的协议层为IPPROTO_IP,选项名为IP_ADD_MEMBERSHIP。
代码如下:
int recv_sock;
struct ip_mreq groupjoin_adr;
recv_sock = socket(PF_INET, SOCK_DGRAM, 0);
join_adr.imr_multiaddr.s_addr = "多播组的地址";
join_adr.imr_interace.s_addr = "加入多播组的主机地址";
setsockopt(recv_sock , IPPROTO_IP , IP_ADD_MEMBERSHIP , (void*) & groupjoin_adr), sizeof(groupjoin_adr));
其中 ip_mreq 结构体,定义如下:
struct ip_mreq
{
/* 欲加入的多播组的地址. */
struct in_addr imr_multiaddr;
/* 所属主机的IP地址. 可以使用INADDR_ANY */
struct in_addr imr_interface;
};
多播的编程与实现
多播中区分 发送者(Sender) 和 接收者(Receiver)。发送者指的是发送多播数据的主体,接收者指的是位于多播组中的欲接收多播数据的主体。
发送者
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#define TTL 64
#define BUF_SIZE 1024
void Sender_message(char *message)
{
puts(message);
exit(1);
}
int main(int argc, char *argv[])
{
int send_sock;
struct sockaddr_in mul_addr;
int time2live = TTL;
FILE *fp;
char buf[BUF_SIZE];
send_sock = socket(PF_INET, SOCK_DGRAM, 0);
memset(&mul_addr, 0, sizeof(mul_addr));
mul_addr.sin_family = AF_INET;
mul_addr.sin_addr.s_addr = inet_addr(argv[1]); // 多播地址
mul_addr.sin_port = htons(atoi(argv[2])); // 多播端口号
setsockopt(send_sock, IPPROTO_IP, IP_MULTICAST_TTL, (void *)&time2live, sizeof(time2live));
fp = fopen(argv[3], "r");
if (fp == NULL)
{
Sender_message((char*)"file open error");
}
while (!feof(fp)) /* 发送数据 */
{
fgets(buf, BUF_SIZE, fp);
sendto(send_sock, buf, strlen(buf), 0, (struct sockaddr *)&mul_addr, sizeof(mul_addr));
}
fclose(fp);
close(send_sock);
return 0;
}
接收者
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#define BUF_SIZE 1024
//报错消息发送
void Sender_message(char *message)
{
puts(message);
exit(1);
}
int main(int argc, char *argv[])
{
int recv_sock;
int str_len;
char buf[BUF_SIZE];
struct sockaddr_in addr;
struct ip_mreq join_addr;
recv_sock = socket(PF_INET, SOCK_DGRAM, 0);
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(INADDR_ANY);
addr.sin_port = htons(atoi(argv[2]));
if (bind(recv_sock, (struct sockaddr *)&addr, sizeof(addr)) == -1)
{
Sender_message((char*)"bind error");
}
join_addr.imr_multiaddr.s_addr = inet_addr(argv[1]);
join_addr.imr_interface.s_addr = htonl(INADDR_ANY);
setsockopt(recv_sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, (void *)&join_addr, sizeof(join_addr));
while (1)
{
// 接收多播数据 其中第五、第六个参数在不知道主机地址时可设为NULL和0
str_len = recvfrom(recv_sock, buf, BUF_SIZE - 1, 0, NULL, 0);
if (str_len < 0)
{
break;
}
buf[str_len] = 0;
fputs(buf, stdout);
}
close(recv_sock);
return 0;
}
运行结果