TCP/IP组播编程

TCP/IP下的组播编程基础

 

 

本文讨论基于TCP/IP下的组播编程基础。所有的例子都可以Windows下台运行。

本文不会涉及到组播协议的实现,只是讨论组播编程需要的环境及API函数。关于组播的实现和需要的协议笔者将会另写文章讨论。

组播描述

  组播(Multicast)是网络一种点对多(one to many)的通信方式,通过报文复制完成网络中一台server对应多台接收者的高效数据传送。对其形象的比喻就是类似于广播电台和电视台节目的发送。电台或电视台向特定频道发送他们的节目,而接收者可以根据自己的喜好选择频道来收听或收看节目。

传统网络的通信方式单播(Unicast)在许多场合下并不合适,如果强行采于单播方式只是会增加网络上传送的报文,还会影响server端的运行效率,如网络游戏,网络视频会议等。这些场合下由于接收者需要的是同样的数据,如果有N个接收者,那么server就需要把这份数据做成N个报文分别发送给接收者,这样当接收者增多的情况下,server端发送的报文也同样需要增多,server端承受这样的通信量肯定有一个极值,当接收者到一定程度的时候,不单单是网络流量增大的问题了,server面临的只有当机。

  组播在这种场合下就有了用武之地了。Server端需要做的就是向特写的Group发送一个报文,当网络的多个接收者对这个报文感兴趣的时候,他们可以自由复制得到这个报文。这样的情况下,无论接收者增加多少人,Server需要做的只是发送一个报文,而网络上传送的是只是一份报文。

单播图例:


  多播图例:



组播相关

组播地址

  32位的IP地址被分成了A, B, C, D, E五种类型,前三种是我们日常生活常见的,由一个权威部分统一分配。而D类地址我们需要的多播地址,前三种地址我们可以形象的认为是一个点,而一个D类多播地址可以认为是一个频道。这样对多播的理解可以形象一点。

关于IP地址的图例就免了,请查阅相关IP分类的相关资料,只要记住多播地址是从224.0.0.0239.255.255.255就行了。224开头的为保留地址,239开头的为实验用地址。这些地址不能随便使用,它们也是有权威部门(IANA)管理,类似于TCP,UDP的保留端口。

常见多播保留地址

地址

用途

介绍

224.0.0.1

本网段所有的主机(All Hosts

[RFC 1112, JBP]

224.0.0.2

相邻的所有的路由器(All Multicast Routers

[JBP]

224.0.0.5

传送OSPF协议用(OSPF Routers

[RFC 1583, JXM1]

224.0.0.6

OSPF Designated Routers

[RFC 1583, JXM1]

224.0.0.9

RIP2 Routers

[RFC 1723, SM11]

224.0.0.12

DHCP Server/Relay Agent

[RFC 1884]

224.0.0.13

All PIM Routers

[Farinacci]

  

 

 

 

 

 

 

 

    实验:可在主机上执行ping 224.0.0.1,看看有多少主机有回应。

IP组播地址到链路层地址的转换

IP多播地址的有效位为28位,是不是可以把这28位都可以影射到链路层地址,如以太网,FDDIToken ring(我们常见的为以太网地址,就是大部分计算机上的Rj45接口的网卡所拥有的地址)。以太网地址为48位,IANA组织把00- 00- 5e开始的地址分配了用于多播的地址,以太网多播地址的最高二位必须为01,也就变成了01- 00- 5e,以太网用于多播地址的范围也就变成了 01- 00 – 5e – 00 – 00 - 00 01 – 00 – 5e – ff – ff – ff

也就是剩下了24位地址,这24位最高位必须为1,只剩下了23位(L不知道为什么要这么麻烦)。

问题出现了,IP多播地址的28位有效位如何影射到以太网的23L,现在解决的方法是只影射IP多播地址的后23位,也就是IP剩下了5位不太影射,所以出现了多个IP多播地址(最多32个)影射一个以太网地址的情况。好像有点跑题了L

组播主机需要的环境

如果要在一台主机上运行多播程序,这台主机支持IGMP协议。此协议用于加入一个多播组,离开一个多播组等而向路由器发送命令。现在的IGMP协议最高版本为v3(rfc3376),现在有很少数的UNIX实现了,WindowXp己实现。大部分主机,路由器实现的版本为v2rfc2236),而v1版本(rfc1112)的运行是从v2中可以实现。V2v1的区别就是把V1版本中的4位版本字段跟4位操作类型字段合并成了8位操作类型,在V1中不用使用的8位字段在V2中被称之为最大响应时间。用C写V2的IGMP结构即为:

struct igmp

{

u_char igmp_type;

u_char igmp_code;

u_char igmp_cksum;

struct in_addr igmp_addr;

};

我记得第二个字段(Max Resp Time)在《TCP/IP详解卷2》中被写成了igmp_code,今天下午刚刚看的,不太清楚了,也不知道为什么起这么一个名字,明天再看看:) 可以看得出IGMP报文为定长的(8个字节)。可能这是最简单的协议结构了(什么,UDP也简单?再想想UDP)。

组播需要的网络环境

如果主机想获得多播报文,相邻的路由器也必须支持IGMP,如果想获得Internet上的多播报文,主机到Server的这个路径中所遇到的路由器必须全部支持IGMP,路由器还必须支持源发现协议,如MSDPPIM_DMPIM_SM等。

组播的等级

Level 0 不支持IP多播

Level 1只支持向多播组发送数据而不能接收多播组的数据

Level 2IP多播全支持

对这三个等级的理解应该从SOCKET上。建立了一个SOCKET以后可以对它进行设置,看它需求什么。一般现在存在的网络程序就就是Level0了,因为它们不支持多播,如用于连接WEB服务器获取网页内容的那个SOCKET就应该属于Level0

一个演唱会现场网络直播,由于采用了多播,服务器要向一个多播组发送报文,因为他不需要获取接收者的报文,所以可以建立一个SOCKET只向特定的多播组发送数据就可以了,这个SOCKET应该就是Level 1

一个网络会议的例子,由于会议是有多个人参加的,每个人都需要接收其它人的报文,所以建立了一个SOCKET,首先把这个SOCKET加入到一个多播组,使其能接收多播组的数据,然后它也可以用这个SOCKET向自己加入的多播组发送自己的状态。这个SOCKET就应该是Level 2 J

组播编程相关的socket结构和函数

int setsockopt(SOCKET s, int level, int optname, const char FAR * optval, int optlen);

int getsockopt(SOCKET s, int level, int optname, char FAR * optval, int FAR * optlen);

level必须为IPPROTO_IP。不要问为什么。这两个函数在组播干什么呢?获取系统对组播的设置(如TTL),加入一个多播组,离开一个多播组就用这两个函数的其中一个,哪果你要问是哪个?就不要往下看了L

optname就是在组播起到最主要作用的一个字段,与组播相关的可取值

可取值

setsockopt

getsockopt

IP_MULTICAST_LOOP

支持

支持

IP_MULTICAST_TTL

支持

支持

IP_MULTICAST_IF

支持

支持

IP_ADD_MEMBERSHIP

支持

不支持

IP_DROP_MEMBERSHIP

支持

不支持

1.IP_MULTICAST_LOOP

当接收者加入到一个多播组以后,再向这个多播组发送数据,这个字段的设置是是

否允许再返回到本身。

2.IP_MULTICAST_TTL

默认情况下,多播报文的TTL被设置成了1,也就是说到这个报文在网络传送的

时候,它只能在自己所在的网络传送,当要向外发送的时候,路由器把TTL减1以后

变成了0,这个报文就已经被Discard了。例:

char ttl;

ttl = 2;

setsockopt(s, IPPROTO_IP, IP_MULTICAST_TTL, (char*)ttl, sizeof(ttl));

3.IP_MULTICAST_IF

发送多播报文时用的本地接口,默认情况下被设置成了本地接口的第一个地址。

未完

4.IP_ADD_MEMBERSHIP

这个option和下面的option是实现多播必不可少的,它用于加入一个多播组,例:

struct ip_mreq ipmr;

ipmr.imr_interface.s_addr = htonl(INADDR_ANY);

ipmr.imr_multiaddr.s_addr = inet_addr("234.5.6.7");

setsockopt(s, IPPROTO_IP, IP_ADDR_MEMBERSHIP, (char*)&ipmr, sizeof(ipmr));

5.IP_DROP_MEMBERSHIP

用于离开一个多播组,使用方法同IP_ADDR_MEMBERSHIP

struct ip_mreqipmr;

intlen;

setsockopt(s, IPPROTO_IP, IP_DROP_MEMBERSHIP, (char*)&ipmr, &len);

向一个特定的多播组发送数据

向多播组发送数据最简单了,多播只支持UDP没有说吧?不知道在什么地方写L,下一版改过来J,发送数据不需要先加入这个多播组。只是简单的把目的地址设成多播地址就可以了,如:

SOCKETs;

SOCKADDR_INmultiaddr;

s = socket(….);

/*不需要bind*/

/*添充结构,向234.5.6.7多播组发送数据*/

….

multiaddr.sin_addr.s_addr = inet_addr(“234.5.6.7”);

….

/*发送buf就可以了*/

sendto(s, buf, len, 0, &multiaddr, &len);

从一个多播组接收数据

SOCKETs;

SOCKADDR_INmultiaddr;

s = socket(….);

/*bind的目的就是要指定一个本地接口的特定端口。这点应该很好理解*/

bind(s, …..);

/*下面就把这个SOCKET加入到一个多播组,比如加入到234.5.6.7*/

struct ip_mreq remote;

int len = sizeof(remote);

remote.imr_multiaddr.s_addr = inet_addr(“234.5.6.7”);

remote.imr_interface.s_addr = htonl(INADDR_ANY);

/*执行了这句就已经加入到了多播组了*/

setsockopt(s, IPPROTO_IP, IP_ADD_MEMBERSHIP, &remote, len);

/*可以接收这个多播组的数据了*/

recvfrom(s, buf, maxlen, …….);

/*离开这个多播组*/

setsockopt(s, IPPROTO_IP, IP_DROP_MEMBERSHIP, &remote, &len);

组播程序的例子

不知道为什么组播的等级定义成三级,下面笔者只做自己加上一级便以讨论。

Level 3只接收多播组的数据而不向多播组发放数据,其实这就是一个Level 2的例子

只不过只是接收数据而一了,就像网络会议旁听者一样J

下面只是列出两个例子,读者可以根本这两个例子举一反三,编译环境要加上ws2_32.lib 还用到了以下的宏,可以放在文件的开始,或者放在单独的h文件。

#define ReportErr(str)/

{/

printf("%s %d/n", __FILE__, __LINE__);/

printf("%s%d/n", str, GetLastError()); /

}

#define WSAReportErr(str) /

{/

printf("%s %d/n", __FILE__, __LINE__);/

printf("%s%d/n", str, WSAGetLastError()); /

}

#define MC_ADDR"234.5.6.7"

#define MC_PORT10000

#define MAXLEN256

一个Level 1例子

只向多播组发送数据,就像网络电视的Server

#include <winsock2.h>

#include <ws2tcpip.h>

#include <stdio.h>

void main(int argc, char **argv)

{

SOCKETs;

SOCKADDR_INmcAddr;

intnMcLen;

intn;

charbuf[MAXLEN];

WSADATAwsaData;

intnErr;

if(WSAStartup(0x0202, &wsaData) != 0)

{

ReportErr("WSAStartup(..)");

return;

};

s = socket(AF_INET, SOCK_DGRAM, 0);

if(s == INVALID_SOCKET)

{

WSAReportErr("socket(...)");

WSACleanup();

return;

}

mcAddr.sin_family= AF_INET;

mcAddr.sin_addr.s_addr= inet_addr(MC_ADDR);

mcAddr.sin_port= htons(MC_PORT);

nMcLen = sizeof(mcAddr);

n = 0;

while(1)

{

Sleep(1000);

sprintf(buf, "This send to multicast group %s. seq: %d", MC_ADDR, n++);

nErr = sendto(s, buf, strlen(buf), 0, (struct sockaddr*)&mcAddr, nMcLen);

if(nErr == SOCKET_ERROR)

{

WSAReportErr("sendto(...)");

break;

}

printf("%s/n", buf);

}

WSACleanup();

}

一个Level 3例子

#include <winsock2.h>

#include <ws2tcpip.h>

#include <stdio.h>

void main(int argc, char **argv)

{

SOCKETs;

SOCKADDR_INmcAddr;

intnMcLen;

charbuf[MAXLEN];

struct ip_mreqipmr;

SOCKADDR_INlocalAddr;

WSADATAwsaData;

intnErr;

if(WSAStartup(0x0202, &wsaData) != 0)

{

ReportErr("WSAStartup(..)");

return;

};

s = socket(AF_INET, SOCK_DGRAM, 0);

if(s == INVALID_SOCKET)

{

WSAReportErr("socket(...)");

WSACleanup();

return;

}

/* */

memset(&localAddr, 0, sizeof(localAddr));

localAddr.sin_family= AF_INET;

localAddr.sin_addr.s_addr= htonl(INADDR_ANY)/*inet_addr(MC_ADDR)*/;

localAddr.sin_port= htons(MC_PORT);

nErr = bind(s, (struct sockaddr*)&localAddr, sizeof(localAddr));

if(nErr == SOCKET_ERROR)

{

WSAReportErr("bind(...)");

WSACleanup();

return;

}

/* Join multicast group */

ipmr.imr_multiaddr.s_addr= inet_addr(MC_ADDR);

ipmr.imr_interface.s_addr= htonl(INADDR_ANY);

nErr = setsockopt(s, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char*)&ipmr, sizeof(ipmr));

if(nErr == SOCKET_ERROR)

{

WSAReportErr("setsockopt(.. IP_ADD_MEMBERSHIP ..)");

WSACleanup();

return;

}

mcAddr.sin_family= AF_INET;

mcAddr.sin_addr.s_addr= inet_addr(MC_ADDR);

mcAddr.sin_port= htons(MC_PORT);

nMcLen = sizeof(mcAddr);

while(1)

{

Sleep(1000);

nErr = recvfrom(s, buf, MAXLEN, 0, (struct sockaddr*)&mcAddr, &nMcLen);

if(nErr == SOCKET_ERROR)

{

continue;

}

printf("%s/n", buf);

}

WSACleanup();

}

参考书目

lMulticast over TCP/IP HOWTO

Juan-manriano de Geyenechejmseyas@dit.upm.es

lftp://ftp-eng.cisco.com/ipmulticast/

Cisco Co.

lTCP/IP详解12

W. Richard Stevens,译者: 范建华等

lWindows网络编程(2)

Anthony Jones, Jim Ohlund,译者:杨合庆

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值