多播

article/2025/9/9 21:48:07

19.1 概述

单播地址标识单个接口,广播地址标识子网上的所有接口,多播地址标识一组接口。单播和广播是编制方案的两个极端(要么一个要么全部),多播的目的就在于提供一种折衷方案。多播数据报仅由对该数据报感兴趣的接口接收,也就是说,由运行希望参加多播会话应用系统的主机上的接口接收。广播一般局限于局域网,而多播既可用于局域网,也可跨越广域网。

 

19.2. 多播地址

IPv4多播地址和IPv6多播地址

IPv4中的D类地址(从224.0.0.0到239.255.255.255)是多播地址。D类地址的低28位构成了多播组ID(group ID),而整个32位地址则称为组地址(group address)。

下面是多播地址映射到以太网地址的方法:

以太网地址第一个字节的低序2位说明该地址是一个统一管理的组地址。统一管理意味着高序24位由IEEE分配,组地址需由接收接口进行特别识别和处理。

下面是几个特殊的IPv4多播地址:

224.0.0.1是一个所有主机(all-hosts)组。子网上所有具有多播能力的主机必须在所有具有多播能力的接口上加入该组。

224.0.0.2是一个所有路由器(all-routers)组。所有多播路由器必须在所有具有多播能力的接口加入该组。

介于224.0.0.0到224.0.0.255间的地址(我们也将它写成224.0.0.0/24)称为链路局部(link local)地址。这些地址被保留用于低级拓扑发现和维护协议,以这些地址为目的地址的数据报不能被多播路由器转发。

 

IPv6多播地址

IPv6多播地址的高序字节值为ff

以太网地址第一字节的低序2位表明该地址是一个本地管理组地址。本地管理组意味着不能保证地址的唯一性,可能有除IPv6外的其他协议组共享同一网络并使用同样的以太网地址高序2字节值。正如我们前面提到的,组地址由接收接口进行特殊识别和处理。

4位的多播标志用于区分众所周知(well-known)多播组(值为0)和临时(transient)多播组(值为1)。该字段的高3位保留。IPv6多播地址还包含一个4位的范围(scope)字段。

下面是一些特殊的IPv6多播地址:

ff02::1是一个所有节点(all-nodes)组。子网上的具有多播能力的所有主机必须在具有多播能力的所有接口上加入该组。

ff02::2是一个所有路由器(all-routers)组。所有子网上的多播路由器必须在具有多播能力的接口上加入该组。这与IPv4的224.0.0.2多播地址相类似。

 

多播地址的范围

IPv6多播地址有一个4位的显示范围字段,该字段决定了多播数据报能够游走的范围。IPv6分组也有一个跳限字段,它限制分组被路由器转发的次数。下面是几个已经分配了的范围字段值:

1:节点局部即局部于节点(node-local)

2:链路局部即局部于链路(link-local)

3:网点局部即局部于网点(site-local)

8:组织局部即局部于组织(orgainization-local)

14:全球(global)

其他值或者还没有分配或者已经保留。节点局部数据报禁止从接口输出,链路局部数据报不能被路由器转发,网点和组织的定义由该网点或组织的多播路由器管理员决定。

 

19.3. 局域网上多播和广播的比较

非完备过滤(imperfect filtering)机制是接口利用以太网目的地址实现的,我们之所以说它不完备是因为当我们告诉接口接收以某个具体以太网组地址为目的地址的帧时,它也可能接收其他以太网组地址为目的地址的帧。

 

19.4. 广域网上的多播

多播路由协议(multicast routing protocol)

在广域网上的两个稍差的多播替代方法是广播泛滥(broadcast flooding)和给每一个接收者都发送一个单独的数据报复本。

 

19.5. 多播套接口选项

IP_ADD_MEMBERSHIP和IPV6_ADD_MEMBERSHIP

在一个指定的本地接口上加入一个多播组。我们用IPv4中的单播地址或IPv6中的接口索引去指定本地接口。当加入或离开一个组时,要用到下面两个结构。

复制代码
struct ip_mreq
{struct in_addr  imr_multiaddr;  /* IPv4 class D multicast addr */struct in_addr  imr_interface;  /* IPv4 add of local interface */
};
struct ipv6_mreq
{struct in6_addr  ipv6mr_multiaddr;  /* IPv6 multicast addr */sunsigned int  ipv6mr_interface;  /* interface index or 0 */
};
复制代码

如果本地接口指定为通配地址(在IPv4中为INADDR_ANY)或者IPv6中的索引0,那么本地接口就由内核来选择。

如果某台主机上当前有一个或多个进程属于某个接口上的一个给定多播组,我们就称该主机在那个接口上属于所给定组。

 

IP_DROP_MEMBERSHIP 和 IPV6_DROP_MEMBERSHIP

在一个指定的本地接口上离开一个多播组。

如果一个进程加入了一个组但从未明确地离开那个组,当套接口关闭时(或者显示地关闭或者因进程终止),成员关系将自动地去掉。同一个主机上的多个进程都加入同一个组也是可以的,这种情况下,主机一直是那个组的成员,知道最后一个进程离开那个组。

 

IP_MULTICAST_IF 和 IPV6_MULTICAST_IF

给从本套接口上发送的外出多播数据报指定接口。这个接口在IPv4中被指定为in_addr结构,在IPv6中被指定为接口索引。如果其值为IPv4中的INADDR_ANY或IPv6中的接口索引0,这将去掉以前通过这个选项指派给此套接口的接口,系统于是给每个外出数据报选择接口。

 

IP_MULTICAST_TTL 和 IPV6_MULTICAST_HOPS

给外出的多播数据报设置IPv4的TTL或IPv6的跳限。如果不指定,那么缺省值都将为1,也就是限制数据报在本地子网。

 

IP_MULTICAST_LOOP 和 IPV6_MULTICAST_LOOP

打开或关闭多播数据报的本地自环即回馈。缺省时回馈是打开的:如果一个主机在发送接口上属于一个多播组,该主机上的进程发送的每个数据报的复本也会回馈,被主机当作接收的数据报处理。

 

19.6. mcast_join和相关函数

复制代码
#include "unp.h"
int mcast_join(int sockfd, const struct sockaddr * sa, socklen_t salen, const char * ifname, u_int ifindex);  //返回: 成功时为0,出错时为-1
int mcast_leave(int sockfd, const struct sockaddr * sa, socklen_t salen); //返回: 成功时为0,出错时为-1
int mcast_set_if(int sockfd, const char * ifname, u_int ifindex); //返回: 成功时为0,出错时为-1
int mcast_set_loop(int sockfd, int flag);  //返回: 成功时为0,出错时为-1
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
复制代码

mcast_join函数加入一个多播组,这个组的IP地址在由addr指向的套接口地址结构中,它的长度由salen指定。

mcast_leave离开一个多播组,这个组的IP地址在由addr指向的套接口地址结构中。

mcast_set_if给外出多播数据报设置缺省的接口索引。如果ifname非空,则指定接口名字,否则ifindex大于0,则指定了接口索引。

mcast_set_loop设置回馈选项为1或0

mcast_set_ttl设置IPv4的TTL或IPv6的跳限。

 

复制代码
#include "unp.h"
#include <net/if.h>
int mcast_join(int sockfd, const SA * sa, socklen_t salen, const char * ifname, u_int ifindex)
{switch(sa->sa_family){case AF_INET:{struct ip_mreq  mreq;struct ifreq  ifreq; memcpy(&mreq.imr_multiaddr, &((struct sockaddr_in *)sa)->sin_addr, sizeof(struct in_addr));if(ifindex > 0) /* 在套接口地址结构中的IPv4多播地址被复制到一个ip_mreq结构中,如果指定了索引,我们就调用if_indextoname,把名字存到ifreq结构中。这成功后,我们向前跳转以调用ioctl */{if(if_indextoname(ifindex, ifreq.ifr_name) == NULL){errno = ENXIO; /* i/f index not found */return(-1);}goto dioctl;}else if(ifname != NULL){/* 调用者的名字被复制到ifreq结构中,SIOCGIFADDR的ioctl调用返回与此名字相关联的单播地址 */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)); /* 成功后,IPv4地址被复制到ip_mreq结构中的imr_interface成员中*/}elsemreq.imr_interface.s_addr = htonl(INADDR_ANY); /* 如果索引和名字都未指定,接口将被设置为通配地址,告诉内核去选择接口 */return(setsockopt(sockfd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq))); /* setsockopt函数执行加入操作 */}
#ifdef IPV6case AF_INET6:{struct ipv6_mreq  mreq6; /* 首先IPv6的多播地址被从套接口地址结构中复制到ipv6_mreq结构中 */memcpy(&mreq6.ipv6mr_multiaddr, &((struct sockaddr_in6 * )sa)->sin6_addr, sizeof(struct in6_addr));if(ifindex > 0) /* 如果指定了索引,这个索引将被复制到ipv6mr_interface成员中 */mreq6.ipv6mr_interface = ifindex;else if(ifname != NULL){/* 否则,如果指定了名字,则通过调用if_nametoindex来获取索引 */if((mreq6.ipv6mr_interface = if_nametoindex(ifname)) == 0){errno = ENXIO; /* i/f name not found */return(-1);}}elsemreq6.ipv6mr_interface = 0; /* 名字和索引都没有指定时,把接口索引置为0调用setsockopt,告诉内核去选择接口,这就加入了组 */return(setsockopt(sockfd, IPPROTO_IPV6, IPV6_ADD_MEMBERSHIP, &mreq6, sizeof(mreq6)));}
#endifdefault:errno = EPROTONOSUPPORT;return(-1);}
}
复制代码

 

复制代码
// mcast_set_loop函数
/* 由于参数是一个套接口描述字,而不是一个套接口地址结构,于是调用自己的sockfd_to_family函数来获取套接口的地址族。随后设置相应的套接口选项 */
#include "unp.h"
int mcast_set_loop(int sockfd, int onff)
{switch(sockfd_to_family(sockfd)){case AF_INET:{u_char flag;flag = onoff;return(setsockopt(sockfd, IPPROTO_IP, IP_MULTICAST_LOOP, &flag, sizeof(flag)));}
#indef  IPV6case AF_INET6:{u_int  flag;flag = onoff;return(setsockopt(sockfd, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, &flag, sizeof(flag)));}
#endifdefault:errno = EPROTONOSUPPORT;return(-1);}
}
复制代码

 

19.7. 使用多播的dg_cli函数

 

19.8. 接收MBone会话声明

多点播送主干MBone(Multicast backbone)
MBone可以被认为是因特网收音机和电视。与视频点播不同的是,视频点播强调的是呼叫并观看存储在服务器上的预压缩电影,而MBone是用于通过因特网向全世界广播数字形式的实时音频和视频。

SAP(Session Announcement Protocol,会话通告协议)

SDP(Session Description Protocol,会话描述协议)

SIP(Session Initiation Protocol,会话初始协议)

 

19.9. 发送和接收

 

19.10. SNTP简单网络时间协议

NTP(网络时间协议)是一个跨越广域网或局域网的复杂的同步时钟协议,它通常可获得毫秒级的精度。

我们将开发一个SNTP客户程序,它监听与它连接的所有网络上的NTP广播或多播,接着输出NTP数据报中的时间和主机的当前时间之差。

复制代码
//NTP数据报格式的基本定义
//ntpdata结构是48字节的NTP数据报格式
#define JAN_1970 2208988800UL /* 1970-1990 in seconds */
struct l_fixedpt /* 64-bit fixed-point */
{uint32_t int_part;uint32_t fraction;
};
struct s_fixedpt /* 32-bit fixed-point */
{u_short int_part;u_short fraction;
};
struct ntpdata /* NTP header */
{u_char status;u_char straturn;u_char ppoll;int precision:8;struct s_fixedpt distance;struct s_fixedpt dispersion;uint32_t refid;struct l_fixedpt reftime;struct l_fixedpt org;struct l_fixedpt rec;struct l_fixedpt xmt;
};
#define VERSION_MASK 0x38
#define MODE_MASK 0x07
#define MODE_CLIENT 3
#define MODE_SERVER 4
#define MODE_BROADCAST 5
复制代码

main函数

复制代码
#include "sntp.h"
int main(int argc, char * * argv)
{int sockfd;char buf[MAXLINE];ssize_t n;socklen_t salen, len;struct ifi_info * ifi;struct sockaddr * mcastsa, * wild, * from;struct timeval now;if(argc != 2)err_quit("usage: ssntp< IPaddress >");sockfd = Udp_client(argv[1], "ntp", (void * * )&mcastsa, &salen);wild = Malloc(salen); /* 我们给另一个套接口地址结构分配空间 */memcpy(wild, mcastsa, salen); /* copy family and port */sock_set_wild(wild, salen); /* 设置IP地址为通配地址 */Bind(sockfd, wild, salen); /* bind wildcard */
#ifdef  MCAST/* obtain interface list and process each one */for( ifi = Get_ifi_info(mcastsa->sa_family, 1); ifi != NULL; ifi = ifi->ifi_next ){/* get_ifi_info函数返回所有接口和地址的信息,我们查询的地址族是从udp-client基于命令行参数填充的套接口地址结构中取得的 */if(ifi->ifi_flags & IFF_MULTICAST){ /* 调用mcast_join把每一个能多播的接口都加入命令行参数指定的多播组,所有的加入操作都是在程序使用的套接口上进行的 */Mcast_join(sockfd, mcastsa, salen, ifi->ifi_name, 0);printf("joined %s on %s \n", Sock_ntop(mcastsa, salen), ifi->ifi_name);}}
#endiffrom = Malloc(salen); /* 另一个套接口地址结构被分配用于保存recvfrom返回 */for( ; ; ){ /* 读入本机收到的所有NTP数据报并调用sntp_proc函数去处理 */len = salen; n = Recvfrom(sockfd, buf, sizeof(buf), 0, from, &len);Gettimeofday(&now, NULL); /* 取得当前时间 */sntp_proc(buf, n, &now);}
}
复制代码

sntp_proc函数处理实际的NTP数据报

复制代码
#include "sntp.h"
void sntp_proc(char * buf, ssize_t n, struct timeval * nowptr)
{int version, mode;uint32_t nsec, useci;double usecf;struct timeval curr, diff;struct ntpdata * ntp;if( n < sizeof(struct ntpdata) ){/* 首先检查数据报的大小 */printf("\n packet to small: %d bytes \n", n);return;}ntp = (struct ntpdata *)buf;version = (ntp->status & VERSION_MASK) >> 3;mode = ntp->status & MODE_MASK;printf("\n v%d, mode %d, strat %d,", version, mode, ntp->stratum ); /* 输出版本,模式,服务器层次 */if(mode == MODE_CLIENT){/* 如果模式是MODE_CLIENT, 那么这个数据报是一个客户请求,而不是一个应答,于是我们忽略它  */printf("client\n");return;}nsec = ntohl(ntp->xmt.int_part) - JAN_1970;nseci = ntohl(ntp->xmt.fraction); /* 32-bit integer fraction */usecf = useci;  /* integer fraction -> double */usecf /= 4294967296.0; /* divide by 2 * * 32 -> [0, 1.0) */useci = usecf * 1000000.0; /* fraction -> parts per million */curr = * nowptr;  /* make a copy as we might modify it below */if((diff.tv_usec = curr.tv_usec - useci) < 0){diff.tv_usec += 1000000;diff.tv_sec--;}diff.tv_sec = curr.tv_sec - nsec;useci = (diff.tv_sec * 1000000) + diff.tv_usec; /* diff in microsec */printf("clock difference = %d usec \n", useci);
}
复制代码

现在用额外的特性扩展SNTP例子。首先,当程序启动时,它对每个单播地址,每个广播地址和所加入多播组的每个接口分别创建一个套接口。这样做的目的是确定我们收到的数据报的目的地址。其次,当程序启动时,它从所有连接的接口上广播和多播一个SNTP客户请求,得到一个起始时间差估计。

警告:这个源代码不是编写SNTP客户程序推荐的方法。我们的目的是更多的了解广播和多播,特别是在多宿主机或路由器上,以及广播和多播的回馈特性。我们选SNTP来展示这些特性的原因是它是一个有用的现实存在的应用系统。我们的客户程序在启动时从所有能广播和多播的接口发送一个SNTP客户请求,其目的是给出某些程序在启动时是如何执行资源发现的。对于SNTP客户程序我们并不建议这样做,更好的技术是只去监听服务器的广播和多播数据报。

下面是组成我们程序的函数功能的概览

get_ifi_info函数来获得接口的列表。对每个接口我们创建一个UDP套接口并bind该接口的单播地址,创建另一个UDP套接口并bind该接口的广播地址,在创建一个UDP套接口并bind NTP的多播组,然后在这个接口上加入这个多播组。sntp_send函数从每个能广播的套接口向所连接子网上的所有NTP服务器发送一个广播请求,以获取他们的当前时间,接着从所有能多播的套接口发送多播请求。这第一批发送的目的是找到所连接子网上的所有NTP服务器并得到一个当前时间的初始估计。程序接着进入了一个无穷循环read_loop,读入到来的任何应答。我们首先期望sntp_send发出的数据报的应答,然后再接收从所连接子网上的NTP服务器定期发送的内容。

 

定义Addrs结构

复制代码
#include "unpifi.h"
#include "ntp.h"
#define MAXNADDRS 128  /* max # of addresses to bind() */
typedef struct
{struct sockaddr * addr_sa;  /* ptr to bound address */socklen_t  addr_salen;  /* socket address length */const char * addr_ifname;  /* interface name, for multicasting */int addr_fd;  /* socket descriptor */int addr_flags; /* ADDR_xxx flags(see below) */
} Addrs;
Addrs addrs[MAXNADDRS]; /* the actual array of structs */
int naddrs; /* index into the array */
#define ADDR_BCAST 1
#define ADDR_MCAST 2
const int on;  /* for setsockopt() */
/* function prototypes */
void bind_mcast(const char * , SA * , socklen_t, int);
void bind_ubcast(SA *, socklen_t, int, int, int);
void read_loop(void);
void sntp_proc(char *, ssize_t nread, struct timeval *);
void sntp_send(void);
复制代码

main函数

复制代码
#include "sntp.h"
const int on = 1;  /* for setsockopt() flags */
int main(int argc, char * * argv)
{int sockfd, port;socklen_t salen;struct ifi_info  * ifi;struct sockaddr * mcastsa, * wild;if(argc != 2){err_quit("usage: sntp<IPaddress>");}sockfd = Udp_client(argv[1], "ntp", (void * *)&mcastsa, &salen); /* 命令行参数ntp.mcast.net,服务名字是ntp,udp_client函数给正确类型的套接口地址结构分配空间,并将多播地址和端口号存入这个结构 */port = sock_get_port(mcastsa, salen);Close(sockfd);/* obtain interface list and process each one */for( ifi = Get_ifi_info(mcastsa->sa_family, 1); ifi != NULL; ifi=ifi->ifi_next){/* 传给get_ifi_info的第二个参数是1,这是通知它返回有关别名地址的信息 */bind_ubcast(ifi->ifi_addr, salen, port, ifi->ifi_myflags & IFI_ALIAS, 0); /* unlcast */if(ifi->ifi_flags & IFF_BROADCAST)bind_ubcast(ifi->ifi_brdaddr, salen, port, ifi->ifi_myflags & IF_ALIAS, 1) /* bcast *//* 每个地址调用函数bind_ubcast一次或两次,第一次调用的最后一个参数是0,表明第一个参数指向的是单播地址,第二次调用时为1,表明第一个参数指向的是广播地址 */
#ifdef MCAST/* 如接口能多播,就对每个单播地址也调用bind_mcast函数,因为我们希望在每个接口上加入多播组 */if(ifi->ifi_flags & IFF_MULTICAST)bind_mcast(ifi->ifi_name, mcastsa, salen, ifi->ifi_myflags & IF_ALIAS); /* mcast */
#endif}wild = Malloc(salen); /* socket address struct for wildcard */memcpy(wild, mcastsa, salen);sock_set_wild(wild, salen);bind_ubcast(wild, salen, port, 0, 0);sntp_send(); /* send first queries */  /* sntp_send从所有能广播的接口上广播一个SNTP请求,并在所有能多播的接口上多播一个SNTP请求 */read_loop(); /* never returns */ /* read_loop接着读入对这些请求的所有应答以及之后收到的任何NTP广播或多播数据报 */
}
复制代码

函数bind_ubcast

复制代码
#include "sntp.h"
void bind_ubcast(struct sockaddr * sabind, socklen_t salen, int port, int alias, int bcast)
{int i, fd;/* first see if we have already bound this address */for(i = 0; i < naddrs; i++){/* 首先查看这个地址是否已经被捆绑。这种情况对单播地址是不会发生的,然而别名共享同一个广播地址时,我们只想捆绑广播地址一次 */if(sock_cmp_addr(addrs[i].addr_sa, sabind, salen) == 0)return;}fd = Socket(sabind->sa_family, SOCK_DGRAM, 0); /* 创建UDP套接口 */sock_set_port(sabind, salen, port); /* 设置端口 */Setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)); /* 设置选项 */printf("binding %s \n", Sock_ntop(sabind, salen))if(bind(fd, sabind, salen) < 0) /* bind所有端口的套接口地址结构是由get_ifi_info填入后返回的 */{if(errno == EADDRINUSE){printf(" (address already in use) \n");close(fd);return;}else /* 我们允许bind失败,这种情况只在 */err_sys("bind error");}/* 这些信息被存入Addrs结构,如果笨地址是一个广播地址,则在包含一个标志 */addrs[naddrs].addr_sa = sabind; /* save ptr to sockaddr{} */addrs[naddrs].addr_salen = salen;addrs[naddrs].addr_fd = fd;if(bcast)addrs[naddrs].addr_flags = ADDR_BCAST;naddrs++;
}
复制代码

函数bind_mcast

复制代码
#include "sntp.h"
void bind_mcast(const char * ifname, SA * mcastsa, socklen_t salen, int alias)
{
#ifdef MCASTint fd;struct sockaddr * msa;if(alias) /* 如果地址是一个别名,立即返回 */return;  /* only one mcast join per interface */printf("joining %s on %s \n", Sock_ntop_host(mcastsa, salen), ifname);fd = Socket(mcastsa->sa_family, SOCK_DGRAM, 0);Setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));Bind(fd, mcastsa, salen);Mcast_join(fd, mcastsa, salen, ifname, 0);addrs[naddrs].addr_sa = mcastsa;addrs[naddrs].addr_salen = salen;addrs[naddrs].addr_ifname = ifname; /* save pointer, not string copy */addrs[naddrs].addr_fd = fd;addrs[naddrs].addr_flags = ADDR_MCAST;naddrs++;
#endif
}
复制代码

函数sntp_send

复制代码
#include "sntp.h"
void sntp_send(void)
{int fd;Addrs * aptr;struct ntpdata  msg;/* use the socket bound to 0.0.0.0/123 for sending */fd = addrs[naddrs-1].addr_fd;Setsockopt(fd, SOL_SOCKET, SO_BROADCAST, &on, sizeof(on));bzero(&msg, sizeof(msg));/* 设置LI(润年指示器)为0,版本为3,模式为MODE_CLIENT */msg.status = ( 0 << 6) | ( 3 << 3) | MODE_CLIENT;  /* see RFC 2030 */for(aptr = &addrs[0];  aptr < &addrs[naddrs]; aptr++){if(aptr->addr_flags & ADDR_BCAST){ /* 如果地址是一个广播地址,请求就是广播请求 */printf("sending broadcast to %s \n", Sock_ntop(aptr->addr_sa, aptr->addr_salen));Sendto(fd, &msg, sizeof(msg), 0, aptr->addr_sa, aptr->addr_salen); /* 绑定了通配地址的套接口 */}
#ifdef MCAST/* 如果地址是一个多播地址,首先在套接口上指定多播数据报的外出接口(mcast_set_if),接着禁止这个套接口上的回馈(mcast_set_loop) */if(aptr->addr_flags & ADDR_MCAST){/* must first set outgoing i/f approriately */Mcast_set_if(fd, aptr->addr_ifname, 0);Mcast_set_loop(fd, 0);  /* disable loopback */printf("sending multicast to %s on %s \n", Sock_ntop(aptr->addr_sa, aptr->addr_salen), aptr->addr_ifname);Sendto(fd, &msg, sizeof(msg), 0, aptr->addr_sa, aptr->addr_salen);}
#endif}
}
复制代码

函数read_loop

复制代码
#include "sntp.h"
static int  check_loop(struct sockaddr *, socklen_t);
static int  check_dup(socklen_t);
static char buf1[MAXLINE], buf2[MAXLINE]; /* 分配两个缓冲区用于接收数据报 */
static char * buf[2] = { buf1, buf2 };
struct sockaddr * from[2]; /* 两个套接口地址结构用于存储相应的源地址 */
static ssize_t nread[2] = { -1, -1 };
static int  currb = 0, lastb = 1; /* 相应于当前数据报的下标在currb中,相应于最近数据报的下标在lastb中 */
void read_loop(void)
{int nsel, maxfd;Addrs * aptr;fd_set rset, allrset;socklen_t len;struct timeval now;/* allocate two socket address structure */from[0] = Malloc(addrs[0].addr_salen);from[1] = Malloc(addrs[0].addr_salen);maxfd = -1;for(aptr = & addrs[0]; aptr < &addrs[naddrs]; aptr++){ /* 给select准备一个描述字集并计算能读的最大描述字 */FD_SET(aptr->addr_fd, &allrset);if(aptr->addr_fd > maxfd)maxfd = aptr->addr_fd;}for( ; ; ){rset = allrset;nsel = Select(maxfd+1, &rset, NULL, NULL, NULL);Gettimeofday(&now, NULL);  /* get time when select returns */for(aptr = &addrs[0]; aptr < &addrs[naddrs]; aptr++){if(FD_ISSET(aptr->addr_fd, &rset)){len = aptr->addr_salen;nread[currb] = recvfrom(aptr->addr_fd, buf[currb], MAXLINE, 0, from[currb], &len);if(aptr->addr_flags & ADDR_MCAST){printf("%d bytes from %s", nread[currb], Sock_ntop(from[currb], len));printf("multicast to %s", aptr->addr_ifname);}else  if(aptr->addr_flags & ADDR_BCAST){printf("%d bytes from %s", nread[currb], Sock_ntop(from[currb], len));printf("broadcast to %s", Sock_ntop(aptr->addr_sa, len));}else{printf("%d bytes from %s", nread[currb], Sock_ntop(from[currb], len));printf(" to %s", Sock_ntop(aptr->addr_sa, len));}if(check_loop(from[currb], len)){printf("(ignored) \n");continue;  /* it is one of ours, looped back */}if(check_dup(len)){printf("(dup) \n");continue;  /* it is a duplicate */}sntp_proc(buf[lastb], nread[lastb], &now);if(--nsel <= 0)break;  /* all done with selectable descriptors */}}}
}int check_loop(struct sockaddr * sa, socklen_t salen)
{Addrs * aptr;for(aptr = &addrs[0]; aptr < &addrs[naddrs]; aptr++){if(sock_cmp_addr(sa, aptr->addr_sa, salen) == 0)return(1);  /* it is one of our addresses */}return(0);
}int check_dup(socklen_t salen)
{int temp;if(nread[currb] == nread[lastb] && memcmp(from[currb], from[lastb], salen) == 0 && memcmp(buf[currb], buf[lastb], nread[currb]) == 0){return(1); /* it is a duplicate */}temp = currb;  /* swap currb and lastb */currb = lastb;lastb = temp;return(0);
}
复制代码

 

19.11. 小结


http://chatgpt.dhexx.cn/article/vDmegHyZ.shtml

相关文章

单播 、多播(组播)、广播

作者&#xff1a;yhthu 链接&#xff1a;https://www.jianshu.com/p/cc62e070a6d2#comments 来源&#xff1a;简书 著作权归作者所有。商业转载请联系作者获得授权&#xff0c;非商业转载请注明出处。 目录 单播、多播(组播)、广播、任播单播组播广播任播 单播、多播(组播)、广…

UDP之多播/组播

目录 一.什么是多播(组播)&#xff1f;为什么出现多播(组播)&#xff1f;二.组播地址三.主机网卡对应的编号 ifconfig命令ip ad (ip adress)获取网卡对应的编号四.多播实现 一.什么是多播(组播)&#xff1f;为什么出现多播(组播)&#xff1f; 由上节课讲到的广播&#xff0c;可…

单播、多播和广播经典详解

1 什么是单播、多播和广播 “单播”&#xff08;Unicast&#xff09;、“多播”&#xff08;Multicast&#xff09;和“广播”&#xff08;Broadcast&#xff09;这三个术语都是用来描述网络节点之间通讯方式的术语。那么这些术语究竟是什么意思&#xff1f;区别何在…

多播--概念和编程

11.3 多播 单播用于两个主机之间的端对端通信&#xff0c;广播用于一个主机对整个局域网上所有主机上的数据通信。单播和广播是两个极端&#xff0c;要么对一个主机进行通信&#xff0c;要么对整个局域网上的主机进行通信。实际情况下&#xff0c;经常需要对一组特定的主机进…

单播、多播(主播)、广播简介

单播 简介 单播&#xff08;unicast&#xff09;是指封包在计算机网络的传输中&#xff0c;目的地址为单一目标的一种传输方式。每次只有两个实体相互通信&#xff0c;发送端和接收端都是唯一确定的。它是现今网络应用最为广泛&#xff0c;通常所使用的网络协议或服务大多采用…

IP多播

部分转载自&#xff1a;http://www.firewall.cx/networking-topics/general-networking/107-network-multicast.html 剩下的基本参考谢希仁计算机网络7th 1. 基本概念 IP多播直观上可以按照下图理解&#xff0c;源主机只需要发送一份数据&#xff0c;而网络中的路由器在转发…

多播(组播)、单播、任播和广播

定义 单播(unicast): 是指封包在计算机网络的传输中&#xff0c;目的地址为单一目标的一种传输方式。它是现今网络应用最为广泛&#xff0c;通常所使用的网络协议或服务大多采用单播传输&#xff0c;例如一切基于TCP的协议。组播(multicast): 也叫多播&#xff0c; 多点广播或…

IP多播(计算机网络-网络层)

目录 一对多通信的应用需求 单播 vs 多播 多播路由器&#xff08;Multicast Router&#xff09; IP 多播的一些特点 D 类 IP 地址与以太网多播地址的映射关系 IP多播需要两种协议 互联网组管理协议 IGMP 多播路由选择协议 两种多播路由选择方法 建议的IP多播路由选择协…

组播,多播

组播&#xff0c;多播&#xff1a;当网络中进行了组播网部署后&#xff0c;一个台设备仅需要基于一个流量进行一次封装及可将该流量转发到所有的组员处&#xff0c;这些组员可处在网络的任何位置&#xff1b;对非组员不产生影响。再未进行组播网络部署的环境下&#xff0c;以组…

网络-单播、多播(组播)和广播的区别

网络-单播、多播&#xff08;组播&#xff09;和广播的区别 转载声明 本文大量内容系转载自以下文章&#xff0c;有删改&#xff0c;并参考其他文档资料加入了一些内容&#xff1a; 单播、多播&#xff08;组播&#xff09;和广播的区别 作者&#xff1a;Roger Luocnblogs 带…

【TCP/IP】多播 - 定义、原理及编程实现 (TTL、多播组、多播消息)

目录 多播 多播的原理 多播的数据传输时的特点 TTL 的概念 TTL 和 多播组的配置方法 多播的编程与实现 发送者 接收者 多播 多播是一种介于单播和广播通信之间的技术方式&#xff0c;可以将发送者所需要发送的数据包分别发送给分散在不同子网中的一组接收者。 多播的原…

socket之UDP组播(多播)

1. 概述 1.1 单播用于两个主机间单对单的通信 1.2广播用于一个主机对整个局域网上所有主机上的数据通信 1.3单播和广播是两个极端&#xff0c;要么对一个主机进行通信&#xff0c;要么对整个局域网的主机进行通信 1.4实际情况下&#xff0c;经常需要对一组特定的主机进行通信&a…

IP多播技术详解

文章目录 前言IP多播技术的相关基本概念IP多播地址和多播组 在局域网上进行硬件多播IP多播地址和多播MAC地址映射关系 在因特网上进行IP多播网际组管理协议IGMP多播路由选择协议 前言 随着计算机网络的发展和个人计算机的普及&#xff0c;人们能够方便的在网络上畅游&#xff…

UDP多播

一、多播概念 1.1、多播 多播又称为&#xff1a;组播。 一个人发数据&#xff0c;只有加入到多播组的人接收数据 1.2、多播的特点 1、多播地址标示一组接口 2、多播可以用于广域网使用 3、在IPv4中&#xff0c;多播是可选的 1.3、多播地址 IPv4的D类地址是多播地址…

多播(组播)

什么是多播 单播用于两个主机之间的端对端通信&#xff0c;广播用于一个主机对整个局域网上所有主机上的数据通信。单播和广播是两个极端&#xff0c;要么对一个主机进行通信&#xff0c;要么对整个局域网上的主机进行通信。实际情况下&#xff0c;经常需要对一组特定的主机…

多播与广播

多播 多播(Multicast)方式的数据传输是基于UDP完成的。因此&#xff0c;与UDP服务器端/客户端的实现方式非常接近。区别在于&#xff0c;UDP数据传输以单一目标进行&#xff0c;而多播数据同时传递到加入(注册)特定组的大量主机。换言之&#xff0c;采用多播方式时&#xff0c…

多播(IP多播-网络层)与单播

多播&#xff08;IP多播-网络层&#xff09;与单播 多播&#xff08;IP多播-网络层&#xff09;简介多播组地址&#xff08;IP地址中的D类地址&#xff09;【多播组地址——D类地址】中一些不能随意使用的地址 IP多播的分类 在局域网上进行的硬件多播网际组管理协议IGMP和多播路…

多播的概念

一、多播概述 多播&#xff1a;数据的收发仅仅在同一组中进行 &#xff08;相当于我往一个群里发&#xff0c;只有加入这个群的人才能收到&#xff09; 多播的特点&#xff1a; ①多播地址标示一组接口。 ②多播可以用于广域网使用。 ③在IPv4&#xff0c;多播是可选的。 二…

win10——telnet 开启

1.win10默认没有开启 2.找到控制面板 3.找到Telnet客户端 4.测试 5.退出 ctrl ‘]’ 之后q

windows service2008 2R 开启telnet

1&#xff0c;什么是telnet 可以把telnet当作一种通信协议&#xff0c;对于入侵者而言telnet是一种远程登录的工具。 2&#xff0c;windows开启telnet&#xff08;默认情况下windows系统的telnet功能是关闭的&#xff09; 步骤&#xff1a; ①打开控制面板 输入winR 输入contro…