我爱电脑技术论坛's Archiver

star2008 发表于 2008-4-22 19:08

用Visual C++实现局域网IP多播

在局域网中,管理员常常需要将某条信息发送给一组用户。如果使用一对一的发送方法,虽然是可行的,但是过于麻烦,也常会出现漏发、错发。为了更有效的解决这种组通信问题,出现了一种多播技术(也常称为组播通信),它是基于IP层的通信技术。为了帮助读者理解,下面将简要的介绍一下多播的概念。&KK|K~`

v(S1Nc&r,h(GB   众所周知,普通IP通信是在一个发送者和一个接收者之间进行的,我们常把它称为点对点的通信,但对于有些应用,这种点对点的通信模式不能有效地满足实际应用的需求。例如:一个数字电话会议系统由多个会场组成,当在其中一个会场的参会人发言时,要求其它会场都能即时的得到此发言的内容,这是一个典型的一对多的通信应用,通常把这种一对多的通信称为多播通信。采用多播通信技术,不仅可以实现一个发送者和多个接收者之间进行通信的功能,而且可以有效减轻网络通信的负担,避免资源的无谓浪费。o.ML f*w0o?9S
9h9d.e$zK
  广播也是一种实现一对多数据通信的模式,但广播与多播在实现方式上有所不同。广播是将数据从一个工作站发出,局域网内的其他所有工作站都能收到它。这一特征适用于无连接协议,因为LAN上的所有机器都可获得并处理广播消息。使用广播消息的不利之处是每台机器都必须对该消息进行处理。多播通信则不同,数据从一个工作站发出后,如果在其它LAN上的机器上面运行的进程表示对这些数据"有兴趣",多播数据才会制给它们。t+z6Q%E8X8Z*]u[
6n6lD4j#d7f7k:N+Vv
  本实例由Sender和Receiver两个程序组成,Sender用户从控制台上输入多播发送数据,Receiver端都要求加入同一个多播组,完成接收Sender发送的多播数据。B-rX R}-u9TG(d
!u*i$XT#[*ok
  一、实现方法
"rC k{ ]5OCb*m^
G/pRL Pd9ab0G   1、 协议支持
N H.u*jo:DTh Lb&X!k
E0BW6}BM   并不是所有的协议都支持多播通信,对Win32平台而言,仅两种可从WinSock内访问的协议(IP/ATM)才提供了对多播通信的支持。因通常通信应用都建立在TCP/IP协议之上的,所以本文只针对IP协议来探讨多播通信技术。
O{x$\,g8G 3eZ5h:[7n}OWj2l}
  支持多播通信的平台包括Windows CE 2.1、Windows 95、Windows 98、Windows NT 4、Windows 2000和WindowsXP。自2.1版开始,Windows CE才开始实现对IP多播的支持。本文实例建立在WindowsXP专业版平台上。
jl? {TF:[
~3B'XNIKt g.`   2、多播地址J.]){(v+L3?Q
*O"F,\iE1{2A
  IP采用D类地址来支持多播。每个D类地址代表一组主机。共有28位可用来标识小组。所以可以同时有多达25亿个小组。当一个进程向一个D类地址发送分组时,会尽最大的努力将它送给小组的所有成员,但不能保证全部送到。有些成员可能收不到这个分组。举个例子来说,假定五个节点都想通过I P多播,实现彼此间的通信,它们便可加入同一个组地址。全部加入之后,由一个节点发出的任何数据均会一模一样地复制一份,发给组内的每个成员,甚至包括始发数据的那个节点。D类I P地址范围在244.0.0.0到239.255.255.255之间。它分为两类:永久地址和临时地址。永久地址是为特殊用途而保留的。比如,244.0.0.0根本没有使用(也不能使用),244.0.0.1代表子网内的所有系统(主机),而244.0.0.2代表子网内的所有路由器。在RFC 1700文件中,提供了所有保留地址的一个详细清单。该文件是为特殊用途保留的所有资源的一个列表,大家可以找来作为参考。"Internet分配数字专家组"(I A N A)负责着这个列表的维护。在表1中,我们总结了目前标定为"保留"的一些地址。临时组地址在使用前必须先创建,一个进程可以要求其主机加入特定的组,它也能要求其主机脱离该组。当主机上的最后一个进程脱离某个组后,该组地址就不再在这台主机中出现。每个主机都要记录它的进程当前属于哪个组。 表1 部分永久地址说明
Y V.HU2P
f+m;`7fG!I;[U   地 址 说 明3y5h:O'eP#c

Ls0`&L w i+B%mIH 244.0.0.1 基本地址(保留)
!o _/[o4|Hm 244.0.0.1 子网上的所有系统#^0_Y6_T'TzG
244.0.0.2 子网上的所有路由器
H-C6F*d3G nQ D@ 244.0.0.5 子网上所有OSPF路由器 K.b p3F*l*m
244.0.0.6 子网上所有指定的OSPF路由器9Z7Hl*K"S j7I
244.0.0.9 RIP第2版本组地址/O(vBQY?G
244.0.1.1 网络时间协议
T7} {[A:oN 244.0.1.24 WINS服务器组地址
bC9d4C-V*mF   3、 多播路由器
(S7T*xE D9CUdS 8OTi!x6v7o3i
  多播由特殊的多播路由器来实现,多播路由器同时也可以是普通路由器。各个多播路由器每分钟发送一个硬件多播信息给子网上的主机(目的地址为244.0.0.1),要求它们报告其进程当前所属的是哪一组,各主机将它感兴趣的D类地址返回。这些询问和响应分组使用IGMP(Internet group management protocol),它大致类似于ICMP。它只有两种分组:询问和响应,都有一个简单的固定格式,其中有效载荷字段的第一个字段是一些控制信息,第二字段是一个D类地址,在RFC1112中有详细说明。
)^a I5J q n c p N[ d
  多播路由器的选择是通过生成树实现的,每个多播路由器采用修改过的距离矢量协议和其邻居交换信息,以便向每个路由器为每一组构造一个覆盖所有组员的生成树。在修剪生成树及删除无关路由器和网络时,用到了很多优化方法。 4@-]$w4b3P Ir~!_h

G6D,?2BE7NK'| |W   4.库支持
T"yQf,kx/cD*X3o
8m i^&k SM"H   WinSock提供了实现多播通信的API函数调用。针对IP多播,WinSock提供了两种不同的实现方法,具体取决于使用的是哪个版本的WinSock。第一种方法是WinSock1提供的,要求通过套接字选项来加入一个组;另一种方法是WinSock2提供的,它是引入一个新函数,专门负责多播组的加入,这个函数便是WSAJoinLeaf,它是基层协议是无关的。本文将通过一个多播通信的实例的实现过程,来讲叙多播实现的主要步骤。因为Window98以后版本都安装了Winsock2.0以上版本,所以本文实例在WinSock2.0平台上开发的,但在其中对WinSock1实现不同的地方加以说明。+l6I&R'l Y#qY
8^ TR8i~7F,q
  二、编程步骤
]L1?xN3Ox
-d-KLQ!A+Tro   1、启动Visual C++6.0,创建一个控制台项目工程MultiCase。在此项目工程中添加Sender和Receiver两个项目。&Wc8I[l:H
Receiver项目实现步骤:
!T }6y4|Z |X !u1y _4E(s*io:Qd/I
  (1)、创建一个SOCK_DGRAM类型的Socket。
6Xa v@'b%O~ /pa Br4?F n%p'}o
  (2)、将此Socket绑定到本地的一个端口上,为了接收服务器端发送的多播数据。$m-xH\&T

$p$PV _;h_a8t K   (3)、加入多播组。
+Ow?7V%_!zE/`
jbM&c%M&I@fY+i   ①、 WinSock2中引入一个WSAJoinLeaf,此函数原型如下:
v.O(gQ?4n
Oi,f mr3B%I!\P q SOCKET WSAJoinLeaf( SOCKET s, const struct sockaddr FAR *name, int namelen,
MmA(S5y;r2w2M LPWSABUF lpCallerData, LPWSABUF lpCalleeData, LPQOS lpSQOS, LPQOS lpGQOS, DWORD dwFlags );2DODf}C.Irsq

6X5kW4j9ed V   其中,第一个参数s代表一个套接字句柄,是自WSASocket返回的。传递进来的这个套接o ])n-b~7|2S0I
y5R,GQ _)P(t
  字必须使用恰当的多播标志进行创建;否则的话WSAJoinLeaf就会失败,并返回错误WSAEINVAL。第二个参数是SOCKADDR(套接字地址)结构,具体内容由当前采用的协议决定,对于IP协议来说,这个地址指定的是主机打算加入的那个多播组。第三个参数namelen(名字长度)是用于指定name参数的长度,以字节为单位。第四个参数lpCallerData(呼叫者数据)的作用是在会话建立之后,将一个数据缓冲区传输给自己通信的对方。第五个参数lpCalleeData(被叫者数据)用于初始化一个缓冲区,在会话建好之后,接收来自对方的数据。注意在当前的Windows平台上,lpCallerData和lpCalleeData这两个参数并未真正实现,所以均应设为NULL。LpSQOS和lpGQOS这两个参数是有关Qos(服务质量)的设置,通常也设为NULL,有关Qos内容请参阅MSDN或有关书籍。最后一个参数dwFlags指出该主机是发送数据、接收数据或收发兼并。该参数可选值分别是:JL_SENDER_ONLY、JL_RECEIVER_ONLY或者JL_BOTH。
&TN1T6ZO\hy f
@wHp&]suK0n   ②、在WinSock1平台上加入多播组需要调用setsockopt函数,同时设置IP_ADD_MEMBERSHIP选项,指定想加入的那个组的地址结构。具体实现代码将在下面代码注释列出。
-oV1D3A9`x5O\.WY
9y J+l pEJm-Q,H#p   (4)、接收多播数据。uq)`.XuX4J ytq

.[ \(z`;x*V]'ks   Sender实现步骤:
Tm:g1Bat DMk BY
  (1)、创建一个SOCK_DGRAM类型的Socket。
T})`-S`o^ A0_'n1? K W7@
  (2)、加入多播组。
t4l!U)PI6w)bx8| j
6up1~*j#Om#Sn#i   (3)、发送多播数据。
mH/m:i {DB"cR 3a@@.@#W~
  3、编译两个项目,在局域网中按如下步骤测试:
rB+a uR _X-bpy0X ux
  (1)、将Sender.exe拷贝到发送多播数据的PC上。
R)e1P.^P+dV ^K6DQ O
  (2)、将Receiver.exe拷贝到多个要求接收多播数据的PC上。{*{&I&eZv\*q

| v V!c#V+W3_ ? P8e   (3)、各自运行相应的程序。3Aw8V)h8r"qcGE \
 
?'f3C}\   (4)、在Sender PC上输入多播数据后,你就可以在Receiver PC上看到输入的多播数据。

star2008 发表于 2008-4-22 19:08

   /UZx,EU S+I;?#s
三、程序代码
y.vu+fej]*Y I*z V!VY)G6X+L4j Wf
!uP g F-ZDdM/zA9L

$e&OyQq9b2` rvjMZ~!md?0I
//////////////////////////////Receiver.c程序代码:
x? _5F nU #include <winsock2.h>
4_V Aavp;uO #include <ws2tcpip.h>
[!g#Q X(zh'R(Q #include <stdio.h>
Sf)H-o-j$K0Y #include <stdlib.h>
ir(xk/{3dv0i7u #define MCASTADDR "233.0.0.1" //本例使用的多播组地址。tl6c]g0}E'}
#define MCASTPORT 5150 //绑定的本地端口号。
dYP.k"w+R ^8W0G #define BUFSIZE 1024 //接收数据缓冲大小。 {\f5|9e5E!^0q@
int main( int argc,char ** argv)X*J)\ A4ql)i:LXw)Y
{
-j u*d`"u:~L ]c  WSADATA wsd;2eW7uP[+tw
 struct sockaddr_in local,remote,from;
F#l*dM]$l4Q  SOCKET sock,sockM;q2M#A#t~k
 TCHAR recVBuf[BUFSIZE];(_/k/w+}.Vg.m4G+l
 /*struct ip_mreq mcast; // Winsock1.0 */;X;Wh {5XL;T
Q-faj |*O
 int len = sizeof( struct sockaddr_in);RC$@Gp*S
 int ret;
a0Cs!A Dr9dj  //初始化WinSock2.2
c4X^G~D.L:f  if( WSAStartup( MAKEWORD(2,2),&wsd) != 0 )
e7HKrgY7Oc  {
_Zq,C })~EI`   printf("WSAStartup() failed");'\ y&A wBCd ^
  return -1;o[P*x ^#~:u'S5@cG
 }
kG"` P!WP,w.Zf n  /*i[1KGb)u
 创建一个SOCK_DGRAM类型的SOCKET
'|O~.g`gV:I9Q  其中,WSA_FLAG_MULTIPOINT_C_LEAF表示IP多播在控制面层上属于"无根"类型;&])Ifw7d;Y
 WSA_FLAG_MULTIPOINT_D_LEAF表示IP多播在数据面层上属于"无根",有关控制面层和 iW5?/yM"[\6d^
 数据面层有关概念请参阅MSDN说明。5Uky7q#[:q
 */
!l-P)@9w}NI  if((sock=WSASocket(AF_INET,SOCK_DGRAM,0,NULL,0, q)V rdK a9g9ef |
  WSA_FLAG_MULTIPOINT_C_LEAF|WSA_FLAG_MULTIPOINT_D_LEAF|
-XD+bf)U\#bE   WSA_FLAG_OVERLAPPED)) == INVALID_SOCKET)
+^fQN*E*P,XY Z  {g'd"u"fqWT
  printf("socket failed with:%d",WSAGetLastError());
(K v C;e:xo(q3_   WSACleanup();:I0AT%@%dFS
  return -1;
8x F2\6k'PZ  }
v[;ifl} V"jA*h  //将sock绑定到本机某端口上。
;LPl Y$tE  local.sin_family = AF_INET;
5BT$^N`1`;vx  local.sin_port = htons(MCASTPORT); y j/`X9A9u
 local.sin_addr.s_addr = INADDR_ANY;
fOq4c/C:c%L.\L  if( bind(sock,(struct sockaddr*)&local,sizeof(local)) == SOCKET_ERROR )
O%\/ZM MoJ  {
@]jN?Q:M6~   printf( "bind failed with:%d ",WSAGetLastError());
-z9] v.R$T\,{Ot$K9`   closesocket(sock);0H6x:H9z;O6Y ZD
  WSACleanup();OI u:c+d];z Z&S/e/r1h
  return -1;
|Kt8Q].W"U c  }.M?(D:g&F c\
 //加入多播组sb_"X[q~/t
 remote.sin_family = AF_INET;
eUv M@~ L!z  remote.sin_port = htons(MCASTPORT);
E)b7w Z^%oT4G  remote.sin_addr.s_addr = inet_addr( MCASTADDR );yC3^X"rd
 /* Winsock1.0 */
?d4cLf Kbj  /*
\ _9p9S-`g  mcast.imr_multiaddr.s_addr = inet_addr(MCASTADDR);mfhO7q
 mcast.imr_interface.s_addr = INADDR_ANY;*c2o(k@ ?
 if( setsockopt(sockM,IPPROTO_IP,IP_ADD_MEMBERSHIP,(char*)&mcast,sizeof(mcast)) == SOCKET_ERROR)&P^ ]LKWB
 {
J c:O E:m   printf("setsockopt(IP_ADD_MEMBERSHIP) failed:%d",WSAGetLastError());9E:n!z~"FDT
  closesocket(sockM); x;F:u9aT-q/a&XB
  WSACleanup();0h:n@9MAT5i
  return -1;
/n_hj6Qa&F;l#r;T6Y  }
zj!MY^_j;@2k~8l  */DL1c,g"W
 /* Winsock2.0*/1h(s5@o}
 if(( sockM = WSAJoinLeaf(sock,(SOCKADDR*)&remote,sizeof(remote),NULL,NULL,NULL,NULL,
#L2n6qe^*]{ JL_BOTH)) == INVALID_SOCKET)J%^ X+T e k ]pk K%Y
 {b*z(Q@9fP
  printf("WSAJoinLeaf() failed:%d",WSAGetLastError()); OW}7]g'pP@OR/y
  closesocket(sock);3JQ.nXnNg|5b
  WSACleanup();P;e/?8d7ST(D
  return -1;
u;Jg{,_ j O  }_a-?"|(Y[~
 //接收多播数据,当接收到的数据为"QUIT"时退出。
)Y#C$F6W]-E  while(1)2QBq|/A0`k
 {
'C+D J9w6g5^   if(( ret = recvfrom(sock,recvbuf,BUFSIZE,0,(struct sockaddr*)&from,&len)) == SOCKET_ERROR)RC#T!Ez#A_RN
  {
@Zhb%g!t#gFj.{    printf("recvfrom failed with:%d",WSAGetLastError());T T AJ/aC.f7vD Lh
   closesocket(sockM);
+EuhKn u2p    closesocket(sock);~-\3k0lA&f
   WSACleanup();tB%Ty2L'j
   return -1;
0iX ne o&EE4Q   }Mj3v(s F.F*z
  if( strcmp(recvbuf,"QUIT") == 0 ) break;
I9Zo[,Dx{{\$h   else { U"m*^im
   recvbuf[ret] = '\0';)d(U$k1z]:zb8{N&P3z
   printf("RECV:' %s ' FROM <%s> ",recvbuf,inet_ntoa(from.sin_addr));
N,JkC S| f]   }~j9UhGL
 }:?2P&|)z1E/J(S
2{3X'y'Q PO'G{n1V
 closesocket(sockM);
\ T!p8`foj  closesocket(sock);)k A l ^U%@ E
 WSACleanup();
ekZ8\Y2vg  return 0;k5d;O-vsk
}

star2008 发表于 2008-4-22 19:09

/////////////////////////////////////////////////////////////////////Sender.c程序代码e+{8WmX"d
#include <winsock2.h>Aum#f1bC+o
#include <ws2tcpip.h>
d] TD6tndO1Y #include <stdio.h>&xT!`t,[5A+G)K
#include <stdlib.h>X7Q(F${@0VO
#define MCASTADDR "233.0.0.1" //本例使用的多播组地址。)M:wi3H Z Aj
#define MCASTPORT 5150 //本地端口号。
FEa%v0T:y B #define BUFSIZE 1024 //发送数据缓冲大小。
"y!~0l t"A}mZ int main( int argc,char ** argv)
G%m-IF F {
l4m~%{4cDGG  WSADATA wsd;
f3\8V~x bI  struct sockaddr_in remote;
-cju:CS8d)z.V  SOCKET sock,sockM;9\2E_gNrwK'h.W ~[
 TCHAR sendbuf[BUFSIZE];6WJ@{\3n _u
 int len = sizeof( struct sockaddr_in);
"R K!R+{eE8DyZ  //初始化WinSock2.2f r7Plxa\
 if( WSAStartup( MAKEWORD(2,2),&wsd) != 0 )3])j!y@V z
 {~l+hv0R5x
  printf("WSAStartup() failed");
.g!X,w[:rM'B   return -1;
Cs(t'hy  }}}iI%J*_dQ J*Cc
 if((sock=WSASocket(AF_INET,SOCK_DGRAM,0,NULL,0,@2N,Rr$]
  WSA_FLAG_MULTIPOINT_C_LEAF|WSA_FLAG_MULTIPOINT_D_LEAF| zwnE0U z:jKk&C
  WSA_FLAG_OVERLAPPED)) == INVALID_SOCKET)
^(o{)E,UL%x H  {3\3A` Y:yX&Tc
  printf("socket failed with:%d",WSAGetLastError());P([:f8z't
  WSACleanup();
#z7f]]~T,Y(G;nx   return -1;
C`FXZmK  }
,q `o6S)y"b  //加入多播组AEr2f&Y;?*tH:~I9^
 remote.sin_family = AF_INET;
BNJ w {Z?~+w  remote.sin_port = htons(MCASTPORT);6uk,Jd)X5x
 remote.sin_addr.s_addr = inet_addr( MCASTADDR );
{ Y$C)i{8h8w1{ E  if(( sockM = WSAJoinLeaf(sock,(SOCKADDR*)&remote,?&c*\KLVF2s3F I
  sizeof(remote),NULL,NULL,NULL,NULL,
&]$F4OP8k'p   JL_BOTH)) == INVALID_SOCKET)
7^ s8H[3[3z  {
c+sDS ?;rE;V"P J   printf("WSAJoinLeaf() failed:%d",WSAGetLastError());
p r{"W.] m{.T   closesocket(sock);4mR@S,Qp9p"Wo:U*Q
  WSACleanup();!a3I3u3T0Q S;hp
  return -1;
|v(^&FfFc A  }C'FR)A.[T)wOs3p
1r u|/t\(Y&^L4U
 //发送多播数据,当用户在控制台输入"QUIT"时退出。wCX)|Pje9w
 while(1)AgX-{6^,Gj
 {
U \A0Y1yn   printf("SEND : ");
Jog MA+@s.b'B   scanf("%s",sendbuf);~P4r*[u#O8jQ d
  if( sendto(sockM,(char*)sendbuf,strlen(sendbuf),0,(struct sockaddr*)&remote,sizeof(remote))==SOCKET_ERROR)
0zWlOn[^   {%F gk7wt0Y[ Gg
   printf("sendto failed with: %d",WSAGetLastError());8I2Y-x%E*LM:RWt
   closesocket(sockM);
^} sF*e$U    closesocket(sock);
l6q2v/ac_4LJx'm    WSACleanup();lL k {H G
   return -1;yO C }E!Itn
  },A2mb&dE&XS
  if(strcmp(sendbuf,"QUIT")==0) break;$[a^f1f
  Sleep(500);
K.OedK2d|;s  }5E `!J.V @xJ
(j ah^eC(Z q6Q%n
 closesocket(sockM);Q0p$K9q*qm
 closesocket(sock);
^g9n9Qu-|Qz {  WSACleanup();{5h$d8~@Ct(M$|
 return 0;
;G$KS3LI!Al T }q#xQFz*A RE
le }y4S4df^Q$V
6k|M5z&W1n
1ZXn8j@cZ
S4G#[;s b$L1]m
  四、小结
#s @%] Tt-KNA 2? KE2Yw7w0N0m,Y
  本实例对IP多播通信进行了探讨,实例程序由Sender和Receiver两部分组成,Sender用户从控制台上输入多播发送数据,Receiver端都要求加入同一个多播组,完成接收Sender发送的多播数据。

页: [1]

Powered by Discuz! Archiver 6.1.0  © 2001-2007 Comsenz Inc.