多播

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成员中*/
             }
             else
               mreq.imr_interface.s_addr = htonl(INADDR_ANY); /* 如果索引和名字都未指定,接口将被设置为通配地址,告诉内核去选择接口 */
             return(setsockopt(sockfd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq))); /* setsockopt函数执行加入操作 */
    }
#ifdef IPV6
    case 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);
               }
             }
             else
               mreq6.ipv6mr_interface = 0; /* 名字和索引都没有指定时,把接口索引置为0调用setsockopt,告诉内核去选择接口,这就加入了组 */
             return(setsockopt(sockfd, IPPROTO_IPV6, IPV6_ADD_MEMBERSHIP, &mreq6, sizeof(mreq6)));
    }
#endif
    default:
      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  IPV6
    case AF_INET6:
    {
      u_int  flag;
      flag = onoff;
      return(setsockopt(sockfd, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, &flag, sizeof(flag)));
    }
#endif
    default:
      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);
    }
  }
#endif
  from = 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 MCAST
  int 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. 小结

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值