UNP卷一chapter21 组播

1、IPv4的D类地址(组播)和IPv6组播地址

IPv4的D类地址(从224.0.0.0到239.255.255.255)是IPv4多播地址。D类地址的低序28位构成多播组ID,整个32位地址称为组地址。其与以太网地址的映射关系见下图。

IPv6多播地址的高序字节值为ff,其与以太网地址的映射关系见下图。


若干个特殊的IPv4多播地址:

i、224.0.0.1是所有主机组。子网上所有具有多播能力的节点必须在所有具有多播能力的接口上加入该组。

ii、224.0.0.2是所有路由器组。子网上所有多播路由器必须在所有具有多播能力的接口上加入该组。

iii、介于224.0.0.0到224.0.0.255之间的地址称为链路局部的多播地址。

IPv6多播地址定义有两种格式,当P标志为0时,T标志区分众所周知多播组(值为0)还是临时多播组(值为1)。P标志值为1表示多播地址是基于某单播前缀赋予的。当P标志为1时,T标志必须也为1(即基于单播的多播地址总是临时的)。

若干特殊的IPv6多播地址:

i、ff01::1和ff02::1是所有节点组。

ii、ff01::2、ff02::2和ff05::2是所有路由器组。

IPv4和IPv6范围划分规则:

IPv4和IPv6多播地址范围
范围 IPv6范围 IPv4  
    TTL范围 可管理范围
接口局部 1 0  
链路局部 2 1 224.0.0.0到224.0.0.225
网点局部 5 <32 239.255.0.0到239.195.255.255
组织机构局部 8   239.192.0.0到239.195.255.255
全球 14 <=255 224.0.1.0到238.255.255.255

2、局域网上多播


接收端启动接收操作:如上图右侧主机,接收应用进程启动,创建一个UDP套接字,捆绑端口123到套接字,然后加入多播组224.0.0.1,IPv4层内部保存上述信息,并告知合适的数据链路接收目的以太网地址为01:00:5e:00:01:01(映射关系见顶图)的以太网帧。

发送端启动发送操作:发送应用进程创建一个UDP套接字,往IP地址224.0.0.1的123端口发送一个数据报。(发送无需任何处理,但最关键的是发送主机会把该IP地址转换成相应的多组以太网目的地址),再发送承载该数据报的以太网帧。需注意的是该帧中同时含有目的以太网地址(由接口检查)和目的IP地址(由IP层检查)。

广域网上的多播实现技术比较复杂,此处不再给出,详情见P438-P440。

3、多播套接字选项

多播套接字选项主要分为6个影响多播数据报的接收和3个影响多播数据报的发送(外出接口、TTL或跳限及回馈)。

i、IP_ADD_MEMBERSHIP、IPV6_JION_GROUP和MCAST_JOIN_GROUP

在一个指定的本地接口上加入一个不限源的多播组。对于IPv4版本,本地接口使用某个单播地址指定;对于IPv6和与协议无关的API,本地接口使用某个接口索引指定。其结构分别如下所示:

struct ip_mreq {
	struct in_addr	imr_multiaddr;/*IPv4 class D multicast addr*/
	struct in_addr	imr_interface;/*IPv4 addr of local interface*/
};

struct ipv6_mreq {
	struct in6_addr	ipv6mr_multiaddr;/*IPv6 multicast addr*/
	unsigned int	ipv6mr_interface;/*interface index, or 0*/
};

struct group_req {
	unsigned int	gr_interface;/*interface index, or 0*/
	struct sockaddr_storage	gr_group;/*IPv4 or IPv6 multicast addr*/
};

ii、IP_DROP_MEMBERSHIP、IPV6_LEAVE_GROUP和MCAST_LEAVE_GROUP

离开指定的本接口上不限源的多播组。(与i相对应哈)

iii、IP_BLOCK_SOURCE和MCAST_BLOCK_SOURCE(源阻塞)

对于一个所指定本地接口上已存在的一个不限源的多播组,在本套接字上阻塞接收来自某个源的多播分组。其结构如下所示:

struct ip_mreq_source {
	struct in_addr	imr_multiaddr;/*IPv4 class D multicast addr*/
	struct in_addr	imr_sourceaddr;/*IPv4 source addr*/
	struct in_addr	imr_interface;/*IPv4 addr of local interface*/
};

struct group_source_req {
	unsigned int	gsr_interface;/*interface index, or 0*/
	struct sockaddr_storage	gsr_group;/*IPv4 or IPv6 multicast addr*/
	struct sockaddr_storage	gsr_source;/*IPv4 or IPv6 source addr*/
};

iv、IP_UNBLOCK_SOURCE和MCAST_UNBLOCK_SOURCE

开通一个先前被阻塞。(与iii相对应哈)

v、IP_ADD_SOURCE_MEMBERSHIP和MCAST_JOIN_SOURCE_GROUP

在一个指定的本地接口上加入一个特定于源的多播组。值得注意,一个接口要么加入不限源的多播组,要么就加入特定源的多播组,二者只能只取其一。

vi、IP_DROP_SOURCE_MEMBERSHIP和MCAST_LEAVE_SOURCE_GROUP

在一个指定的本地接口上离开一个特定于源的多播组。(与v相对应哈)

vii、IP_MULTICAST_IF和IPV6_MULTICAST_IF(此处发送的多播数据报指定的是外出接口,注意区分加入多播组时指定的本地接口,其主要用来收)

指定通过本套接字发送的多播数据报的外出接口。对于IPv4版本,该接口由某个in_addr结构指定(INADDR_ANY表示由内核指定);对于IPv6,该接口由某个接口索引指定(0表示由内核指定)。

viii、IP_MULTICAST_TTL和IPV6_MULTICAST_HOPS(TTL或跳限)

 给外出的多播数据报设置IPv4的TTL或IPv6的跳限。如果不指定,这俩版本都默认为1,从而把多播数据报限制在本地子网。

ix、IP_MULTICAST_LOOP和IPv6_MULTICAST_LOOP

开启或禁止多播数据报的本地自环(即回馈)。

以下一句话表示多播实现的特点:

为了接收目的地址为某个组地址且目的端口为某个端口的多播数据组,进程必须加入该多播组,并捆绑该端口到某个UDP套接字。此两操作是截然不同的,不过都是必需的。多播组加入操作告知所在主机的IP层和数据链路层接收发往该组的多播数据报。端口捆绑操作则是应用进程向UDP指示它想接收发往该端口的数据报的手段。

4、mcast_join及其实现和相关函数

int mcast_join(int sockfd, const struct sockaddr *grp, socklen_t grplen, 
	const char *ifname, u_int ifindex);
int mcast_leave(int sockfd, const struct sockaddr* grp, socklen_t grplen);
int mcast_block_source(int sockfd,
	const struct sockaddr* src, socklen_t srclen,
	const struct sockaddr* grp, socklen_t grplen);
int mcast_unblock_source(int sockfd,
	const struct sockaddr* src, socklen_t srclen,
	const struct sockaddr* grp, socklen_t grplen);
int mcast_join_source_group(int sockfd,
	const struct sockaddr* src, socklen_t srclen,
	const struct sockaddr* grp, socklen_t grplen,
	const char* ifname,u_int ifindex);
int mcast_leave_source_group(int sockfd,
	const struct sockaddr* src, socklen_t srclen,
	const struct sockaddr* grp, socklen_t grplen);
int mcast_set_if(int sockfd, const char* ifname, u_int ifindex);
int mcast_set_loop(int sockfd, int flag)
int mcast_set_ttl(int sockfd, int ttl);
//以上均返回:若成功则为0,若出错则为-1

int mcast_get_if(int sockfd);//返回:若成功则为非负接口索引,若出错则为-1
int mcast_get_loop(int sockfd);//返回:若成功则为当前回馈标志,若出错则为-1
int mcast_get_ttl(int sockfd);//返回:若成功则为当前TTL或跳限,若出错则为-1

以上函数具体解析说明见书上P446。

mcast_join函数的实现

/* include mcast_join1 */
#include	"unp.h"
#include	<net/if.h>

int
mcast_join(int sockfd, const SA *grp, socklen_t grplen,
	const char *ifname, u_int ifindex)
{
#ifdef MCAST_JOIN_GROUP
	struct group_req req;
	if (ifindex > 0) {
		req.gr_interface = ifindex;//给定接口索引,便直接使用
	}
	else if (ifname != NULL) {
		if ((req.gr_interface = if_nametoindex(ifname)) == 0) {//给定名字,转换成索引
			errno = ENXIO;	/* i/f name not found */
			return(-1);
		}
	}
	else
		req.gr_interface = 0;//两不然由内核选择接口
	if (grplen > sizeof(req.gr_group)) {
		errno = EINVAL;
		return -1;
	}
	memcpy(&req.gr_group, grp, grplen);//将给定的组播地址结构复制到一个group_req结构中
	return (setsockopt(sockfd, family_to_level(grp->sa_family),//通过setsockopt函数执行多组加操作
		MCAST_JOIN_GROUP, &req, sizeof(req)));
#else
	/* end mcast_join1 */

	/* include mcast_join2 */
	switch (grp->sa_family) {
	case AF_INET: {
		struct ip_mreq		mreq;
		struct ifreq		ifreq;

		memcpy(&mreq.imr_multiaddr,//指定复制一个多播组IP
			&((const struct sockaddr_in *) grp)->sin_addr,
			sizeof(struct in_addr));

		if (ifindex > 0) {
			if (if_indextoname(ifindex, ifreq.ifr_name) == NULL) {//将接口转换成接口请求名字
				errno = ENXIO;	/* i/f index not found */
				return(-1);
			}
			goto doioctl;
		}
		else if (ifname != NULL) {
			strncpy(ifreq.ifr_name, ifname, IFNAMSIZ);
		doioctl:
			if (ioctl(sockfd, SIOCGIFADDR, &ifreq) < 0)
				return(-1);
			memcpy(&mreq.imr_interface,
				&((struct sockaddr_in *) &ifreq.ifr_addr)->sin_addr,
				sizeof(struct in_addr));
		}
		else
			mreq.imr_interface.s_addr = htonl(INADDR_ANY);//没有指定接口号与接口名字,由内核选定一个本地接口

		return(setsockopt(sockfd, IPPROTO_IP, IP_ADD_MEMBERSHIP,//通过setsockopt函数执行多组加操作
			&mreq, sizeof(mreq)));
	}
				  /* end mcast_join2 */

				  /* include mcast_join3 */
#ifdef	IPV6
#ifndef	IPV6_JOIN_GROUP		/* APIv0 compatibility */
#define	IPV6_JOIN_GROUP		IPV6_ADD_MEMBERSHIP
#endif
	case AF_INET6: {
		struct ipv6_mreq	mreq6;//类比IPv4学习以下代码,只是由接口地址变成了接口号

		memcpy(&mreq6.ipv6mr_multiaddr,
			&((const struct sockaddr_in6 *) grp)->sin6_addr,
			sizeof(struct in6_addr));

		if (ifindex > 0) {
			mreq6.ipv6mr_interface = ifindex;
		}
		else if (ifname != NULL) {
			if ((mreq6.ipv6mr_interface = if_nametoindex(ifname)) == 0) {
				errno = ENXIO;	/* i/f name not found */
				return(-1);
			}
		}
		else
			mreq6.ipv6mr_interface = 0;

		return(setsockopt(sockfd, IPPROTO_IPV6, IPV6_JOIN_GROUP,
			&mreq6, sizeof(mreq6)));
	}
#endif

	default:
		errno = EAFNOSUPPORT;
		return(-1);
	}
#endif
}
/* end mcast_join3 */

以上知识点来均来自steven先生所著UNP卷一(version3),刚开始学习网络编程,如有不正确之处请大家多多指正。

猜你喜欢

转载自blog.csdn.net/tt_love9527/article/details/80602638