使用广播可以将封包发送到网络中的各个节点,使用多播则仅将封包发送到网络节点的一个集合。
1.1 多播地址
为了发送IP多播数据,发送者需要一个合适的多播地址,这个地址代表一个组,IP多播地址采用D类IP地址确定多播的组,地址的范围是224.0.0.0到239.255.255.255。不过有些多播地址保留为特殊的目的使用。
224.0.0.0 -------------------------------基地址(保留)
224.0.0.1 -------------------------------本子网上的所有节点
224.0.0.2 -------------------------------本子网上的所有路由器
224.0.0.4 -------------------------------网段中所有的DVMRP路由器
224.0.0.5 -------------------------------所有的OSPF路由器
224.0.0.6 -------------------------------所有的OSPF指派路由器
224.0.0.9 -------------------------------所有RIPv2路由器
224.0.0.13------------------------------所有PIM路由器
1.2 组管理协议(IGMP)
IGMP是IPv4引入的管理多播客户和它们之间关系的协议。IGMP被开发出来用于通知路由器网络上的一个机器对指定组的数据感兴趣。
为了多播正确的工作,两个多播节点之间的所有路由器必须支持IGMP协议。任何没有开启IGMP协议的路由器仅简单地丢弃接收到的多播数据。
另外,当终端加入到多播组时,它指定TTL参数,来指明终端的多播应用程序想要经过多少个路由器来发送和接收数据,如下代码所示。
int nTtl = 0; setsockopt(sock,IPPROTO_IP,IP_MULTICAST_TTL,(char*)&nTtl,sizeof(nTtl));
l 初始TTL为0的多播封包被限制在同一个主机
l 初始TTL为1的多播封包被限制在同一个子网
l 初始TTL为32的多播封包被限制在同一个站点
l 初始TTL为64的多播封包被限制在同一个地区
l 初始TTL为128的多播封包被限制在同一个大陆
l 初始TTL为255的多播封包没有范围限制
注意:许多多播路由器拒绝转发目的地址在224.0.0.0~224.0.0.255之间的任何多播数据报,不管它的TTL是多少。
每个多播传输仅从一个网络接口发出,即便是主机有多个多播接口。可以使用套接字选项IP_MULTICAST_IF改变默认发送数据接口,如下代码所示。
struct in_addr addr;
setsockopt(sock,IPPROTO_IP,IP_MULTICAST_IF,&addr,sizeof(addr));
其中addr是本地机器想要外出的接口。设置为地址INADDR_ANY可以恢复使用默认接口。
1.3带源地址的IP多播
带源地址的IP多播允许加入组时指定要接收哪些成员的数据。有两种方式:
1) “包含”方式,这种方式下,为套接字指定N个有效源地址,套接字仅接收来自这些源地址的数据;使用IP_ADD_SOURCE_MEMBERSHIP和IP_DROP_SOURCE_MEMBERSHIP。
2) “排除”方式,这种方式下,为套接字指定N个源地址,套接字将接收来自这些源地址之外的数据;使用IP_BLOCK_SOURCE(排除某个源地址)和IP_UNBLOCK_SOURCE(从排除集合中移除此源地址)以上两种方式输入参数都是ip_mreq_source结构。
下面是一个测试程序。
接收端:
#include <winsock2.h>
#include <ws2tcpip.h>
#pragma comment(lib,"ws2_32")
void initSock(){
WORD wVersionRequested;
WSADATA wsaData;
int err;
wVersionRequested = MAKEWORD( 2, 2 );
err = WSAStartup( wVersionRequested, &wsaData );
if ( err != 0 ) {
return;
}
if ( LOBYTE( wsaData.wVersion ) != 2 ||
HIBYTE( wsaData.wVersion ) != 2 ) {
WSACleanup( );
return;
}
}
int main(){
initSock();
SOCKET sock = socket(AF_INET,SOCK_DGRAM,0);
if(sock==INVALID_SOCKET){
return 0;
}
BOOL bReuse = TRUE;
setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,(char*)&bReuse,sizeof(BOOL));
sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_addr.S_un.S_addr = INADDR_ANY;
addr.sin_port = htons(4567);
::bind(sock,(sockaddr*)&addr,sizeof(addr));
ip_mreq mcast;
mcast.imr_interface.S_un.S_addr = INADDR_ANY;
mcast.imr_multiaddr.S_un.S_addr = inet_addr("234.5.15.8");
setsockopt(sock,IPPROTO_IP,IP_ADD_MEMBERSHIP,(char*)&mcast,sizeof(mcast));
char buf[100];
int len = sizeof(sockaddr);
while(true){
int ret = ::recvfrom(sock,buf,sizeof(buf),0,(sockaddr*)&addr,&len);
if(ret == SOCKET_ERROR)
break;
printf("%s\n",buf);
}
closesocket(sock);
WSACleanup();
return 0;
}
发送端:
#include <winsock2.h>
#include <ws2tcpip.h>
#pragma comment(lib,"ws2_32")
void initSock(){
WORD wVersionRequested;
WSADATA wsaData;
int err;
wVersionRequested = MAKEWORD( 2, 2 );
err = WSAStartup( wVersionRequested, &wsaData );
if ( err != 0 ) {
return;
}
if ( LOBYTE( wsaData.wVersion ) != 2 ||
HIBYTE( wsaData.wVersion ) != 2 ) {
WSACleanup( );
return;
}
}
int main(){
initSock();
SOCKET sock = socket(AF_INET,SOCK_DGRAM,0);
int nTtl = 0;
setsockopt(sock,IPPROTO_IP,IP_MULTICAST_TTL,(char*)&nTtl,sizeof(nTtl));
sockaddr_in addr;
addr.sin_addr.S_un.S_addr = inet_addr("234.5.15.8");
addr.sin_family = AF_INET;
addr.sin_port = htons(4567);
char buf[100] = {0};
int i = 0;
while(++i<=20){
char name[100] = {0};
gethostname(name,sizeof(name));
hostent* phost = gethostbyname(name);
sprintf(buf,"hello! I'm %s (%s)",name,inet_ntoa(*(in_addr*)(phost->h_addr_list[0])));
::sendto(sock,buf,sizeof(buf),0,(sockaddr*)&addr,sizeof(sockaddr));
}
closesocket(sock);
WSACleanup();
return 0;
}
运行结果: