在局域网中管理员常常需要将某条信息发送给一组用户如果使用一对一的发送方法虽然是可行的但是过于麻烦也常会出现漏发错发为了更有效的解决这种组通信问题出现了一种多播技术(也常称为组播通信)它是基于IP层的通信技术为了帮助读者理解下面将简要的介绍一下多播的概念
众所周知普通IP通信是在一个发送者和一个接收者之间进行的我们常把它称为点对点的通信但对于有些应用这种点对点的通信模式不能有效地满足实际应用的需求例如一个数字电话会议系统由多个会场组成当在其中一个会场的参会人发言时要求其它会场都能即时的得到此发言的内容这是一个典型的一对多的通信应用通常把这种一对多的通信称为多播通信采用多播通信技术不仅可以实现一个发送者和多个接收者之间进行通信的功能而且可以有效减轻网络通信的负担避免资源的无谓浪费
广播也是一种实现一对多数据通信的模式但广播与多播在实现方式上有所不同广播是将数据从一个工作站发出局域网内的其他所有工作站都能收到它这一特征适用于无连接协议因为LAN上的所有机器都可获得并处理广播消息使用广播消息的不利之处是每台机器都必须对该消息进行处理多播通信则不同数据从一个工作站发出后如果在其它LAN上的机器上面运行的进程表示对这些数据有兴趣多播数据才会制给它们
本实例由Sender和Receiver两个程序组成Sender用户从控制台上输入多播发送数据Receiver端都要求加入同一个多播组完成接收Sender发送的多播数据
一实现方法
协议支持
并不是所有的协议都支持多播通信对Win平台而言仅两种可从WinSock内访问的协议(IP/ATM)才提供了对多播通信的支持因通常通信应用都建立在TCP/IP协议之上的所以本文只针对IP协议来探讨多播通信技术
支持多播通信的平台包括Windows CE Windows Windows Windows NT Windows 和WindowsXP自版开始Windows CE才开始实现对IP多播的支持本文实例建立在WindowsXP专业版平台上
多播地址
IP采用D类地址来支持多播每个D类地址代表一组主机共有位可用来标识小组所以可以同时有多达亿个小组当一个进程向一个D类地址发送分组时会尽最大的努力将它送给小组的所有成员但不能保证全部送到有些成员可能收不到这个分组举个例子来说假定五个节点都想通过I P多播实现彼此间的通信它们便可加入同一个组地址全部加入之后由一个节点发出的任何数据均会一模一样地复制一份发给组内的每个成员甚至包括始发数据的那个节点D类I P地址范围在到之间它分为两类永久地址和临时地址永久地址是为特殊用途而保留的比如根本没有使用(也不能使用)代表子网内的所有系统(主机)而代表子网内的所有路由器在RFC 文件中提供了所有保留地址的一个详细清单该文件是为特殊用途保留的所有资源的一个列表大家可以找来作为参考Internet分配数字专家组(I A N A)负责着这个列表的维护在表中我们总结了目前标定为保留的一些地址临时组地址在使用前必须先创建一个进程可以要求其主机加入特定的组它也能要求其主机脱离该组当主机上的最后一个进程脱离某个组后该组地址就不再在这台主机中出现每个主机都要记录它的进程当前属于哪个组表部分永久地址说明
地 址说 明 基本地址(保留)子网上的所有系统子网上的所有路由器子网上所有OSPF路由器子网上所有指定的OSPF路由器RIP第版本组地址网络时间协议 WINS服务器组地址
多播路由器
多播由特殊的多播路由器来实现多播路由器同时也可以是普通路由器各个多播路由器每分钟发送一个硬件多播信息给子网上的主机(目的地址为)要求它们报告其进程当前所属的是哪一组各主机将它感兴趣的D类地址返回这些询问和响应分组使用IGMP(Internet group management protocol)它大致类似于ICMP它只有两种分组询问和响应都有一个简单的固定格式其中有效载荷字段的第一个字段是一些控制信息第二字段是一个D类地址在RFC中有详细说明
多播路由器的选择是通过生成树实现的每个多播路由器采用修改过的距离矢量协议和其邻居交换信息以便向每个路由器为每一组构造一个覆盖所有组员的生成树在修剪生成树及删除无关路由器和网络时用到了很多优化方法
库支持
WinSock提供了实现多播通信的API函数调用针对IP多播WinSock提供了两种不同的实现方法具体取决于使用的是哪个版本的WinSock第一种方法是WinSock提供的要求通过套接字选项来加入一个组另一种方法是WinSock提供的它是引入一个新函数专门负责多播组的加入这个函数便是WSAJoinLeaf它是基层协议是无关的本文将通过一个多播通信的实例的实现过程来讲叙多播实现的主要步骤因为Window以后版本都安装了Winsock以上版本所以本文实例在WinSock平台上开发的但在其中对WinSock实现不同的地方加以说明
二编程步骤
启动Visual C++创建一个控制台项目工程MultiCase在此项目工程中添加Sender和Receiver两个项目
Receiver项目实现步骤
()创建一个SOCK_DGRAM类型的Socket
()将此Socket绑定到本地的一个端口上为了接收服务器端发送的多播数据
()加入多播组
①WinSock中引入一个WSAJoinLeaf此函数原型如下
SOCKET WSAJoinLeaf( SOCKET s const struct sockaddr FAR *name int namelen
LPWSABUF lpCallerData LPWSABUF lpCalleeData LPQOS lpSQOS LPQOS lpGQOS DWORD dwFlags );
其中第一个参数s代表一个套接字句柄是自WSASocket返回的传递进来的这个套接字必须使用恰当的多播标志进行创建否则的话WSAJoinLeaf就会失败并返回错误WSAEINVAL第二个参数是SOCKADDR(套接字地址)结构具体内容由当前采用的协议决定对于IP协议来说这个地址指定的是主机打算加入的那个多播组第三个参数namelen(名字长度)是用于指定name参数的长度以字节为单位第四个参数lpCallerData(呼叫者数据)的作用是在会话建立之后将一个数据缓沖区传输给自己通信的对方第五个参数lpCalleeData(被叫者数据)用于初始化一个缓沖区在会话建好之后
接收来自对方的数据注意在当前的Windows平台上lpCallerData和lpCalleeData这两个参数并未真正实现所以均应设为NULLLpSQOS和lpGQOS这两个参数是有关Qos(服务质量)的设置通常也设为NULL有关Qos内容请参阅MSDN或有关书籍最后一个参数dwFlags指出该主机是发送数据接收数据或收发兼并该参数可选值分别是JL_SENDER_ONLYJL_RECEIVER_ONLY或者JL_BOTH
②在WinSock平台上加入多播组需要调用setsockopt函数同时设置IP_ADD_MEMBERSHIP选项指定想加入的那个组的地址结构具体实现代码将在下面代码注释列出
()接收多播数据
Sender实现步骤
()创建一个SOCK_DGRAM类型的Socket
()加入多播组
()发送多播数据
编译两个项目在局域网中按如下步骤测试
()将Senderexe拷贝到发送多播数据的pc上
()将Receiverexe拷贝到多个要求接收多播数据的pc上
()各自运行相应的程序
()在Sender PC上输入多播数据后你就可以在Receiver PC上看到输入的多播数据
三程序代码
Receiverc程序代码
#include <winsockh>
#include <wstcpiph>
#include <stdioh>
#include <stdlibh>
#define MCASTADDR //本例使用的多播组地址
#define MCASTPORT //绑定的本地端口号
#define BUFSIZE //接收数据缓沖大小
int main( int argcchar ** argv)
{
WSADATA wsd;
struct sockaddr_in localremotefrom;
SOCKET socksockM;
TCHAR recvbuf[BUFSIZE];
/*struct ip_mreq mcast; // Winsock */
int len = sizeof( struct sockaddr_in);
int ret;
//初始化WinSock
if( WSAStartup( MAKEWORD()&wsd) != )
{
printf(WSAStartup() failed\n);
return ;
}
/*
创建一个SOCK_DGRAM类型的SOCKET
其中WSA_FLAG_MULTIPOINT_C_LEAF表示IP多播在控制面层上属于无根类型;
WSA_FLAG_MULTIPOINT_D_LEAF表示IP多播在数据面层上属于无根有关控制面层和
数据面层有关概念请参阅MSDN说明
*/
if((sock=WSASocket(AF_INETSOCK_DGRAMNULL
WSA_FLAG_MULTIPOINT_C_LEAF|WSA_FLAG_MULTIPOINT_D_LEAF|
WSA_FLAG_OVERLAPPED)) == INVALID_SOCKET)
{
printf(socket failed with:%d\nWSAGetLastError());
WSACleanup();
return ;
}
//将sock绑定到本机某端口上
localsin_family = AF_INET;
localsin_port = htons(MCASTPORT);
localsin_addrs_addr = INADDR_ANY;
if( bind(sock(struct sockaddr*)&localsizeof(local)) == SOCKET_ERROR )
{
printf( bind failed with:%d \nWSAGetLastError());
closesocket(sock);
WSACleanup();
return ;
}
//加入多播组
remotesin_family = AF_INET;
remotesin_port = htons(MCASTPORT);
remotesin_addrs_addr = inet_addr( MCASTADDR );
/* Winsock */
/*
mcastimr_multiaddrs_addr = inet_addr(MCASTADDR);
mcastimr_interfaces_addr = INADDR_ANY;
if( setsockopt(sockMIPPROTO_IPIP_ADD_MEMBERSHIP
(char*)&mcastsizeof(mcast)) == SOCKET_ERROR)
{
printf(setsockopt(IP_ADD_MEMBERSHIP) failed:%d\nWSAGetLastError());
closesocket(sockM);
WSACleanup();
return ;
}
*/
/* Winsock*/
if(( sockM = WSAJoinLeaf(sock(SOCKADDR*)&remotesizeof(remote)
NULLNULLNULLNULL
JL_BOTH)) == INVALID_SOCKET)
{
printf(WSAJoinLeaf() failed:%d\nWSAGetLastError());
closesocket(sock);
WSACleanup();
return ;
}
//接收多播数据当接收到的数据为QUIT时退出
while()
{
if(( ret = recvfrom(sockrecvbufBUFSIZE
(struct sockaddr*)&from&len)) == SOCKET_ERROR)
{
printf(recvfrom failed with:%d\nWSAGetLastError());
closesocket(sockM);
closesocket(sock);
WSACleanup();
return ;
}
if( strcmp(recvbufQUIT) == ) break;
else {
recvbuf[ret] = \;
printf(RECV: %s FROM <%s> \nrecvbufinet_ntoa(fromsin_addr));
}
}
closesocket(sockM);
closesocket(sock);
WSACleanup();
return ;
}
Senderc程序
代码
#include <winsockh>
#include <wstcpiph>
#include <stdioh>
#include <stdlibh>
#define MCASTADDR //本例使用的多播组地址
#define MCASTPORT //本地端口号
#define BUFSIZE //发送数据缓沖大小
int main( int argcchar ** argv)
{
WSADATA wsd;
struct sockaddr_in remote;
SOCKET socksockM;
TCHAR sendbuf[BUFSIZE];
int len = sizeof( struct sockaddr_in);
//初始化WinSock
if( WSAStartup( MAKEWORD()&wsd) != )
{
printf(WSAStartup() failed\n);
return ;
}
if((sock=WSASocket(AF_INETSOCK_DGRAMNULL
WSA_FLAG_MULTIPOINT_C_LEAF|WSA_FLAG_MULTIPOINT_D_LEAF|
WSA_FLAG_OVERLAPPED)) == INVALID_SOCKET)
{
printf(socket failed with:%d\nWSAGetLastError());
WSACleanup();
return ;
}
//加入多播组
remotesin_family = AF_INET;
remotesin_port = htons(MCASTPORT);
remotesin_addrs_addr = inet_addr( MCASTADDR );
if(( sockM = WSAJoinLeaf(sock(SOCKADDR*)&remote
sizeof(remote)NULLNULLNULLNULL
JL_BOTH)) == INVALID_SOCKET)
{
printf(WSAJoinLeaf() failed:%d\nWSAGetLastError());
closesocket(sock);
WSACleanup();
return ;
}
//发送多播数据当用户在控制台输入QUIT时退出
while()
{
printf(SEND : );
scanf(%ssendbuf);
if( sendto(sockM(char*)sendbufstrlen(sendbuf)
(struct sockaddr*)&remotesizeof(remote))==SOCKET_ERROR)
{
printf(sendto failed with: %d\nWSAGetLastError());
closesocket(sockM);
closesocket(sock);
WSACleanup();
return ;
}
if(strcmp(sendbufQUIT)==) break;
Sleep();
}
closesocket(sockM);
closesocket(sock);
WSACleanup();
return ;
}
四小结
本实例对IP多播通信进行了探讨实例程序由Sender和Receiver两部分组成Sender用户从控制台上输入多播发送数据Receiver端都要求加入同一个多播组完成接收Sender发送的多播数据