Visual C++實現局域網IP多播

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////編程

2018.9.25補充

winSock1.1版的發送端程序,代碼參考:https://pan.baidu.com/s/1s-cXVVNZjIfCR4J0xkz0Uw服務器

 

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////網絡

在局域網中,管理員經常須要將某條信息發送給一組用戶。若是使用一對一的發送方法,雖然是可行的,可是過於麻煩,也常會出現漏發、錯發。爲了更有效的解決這種組通訊問題,出現了一種多播技術(也常稱爲組播通訊),它是基於IP層的通訊技術。爲了幫助讀者理解,下面將簡要的介紹一下多播的概念。socket

衆所周知,普通IP通訊是在一個發送者和一個接收者之間進行的,咱們常把它稱爲點對點的通訊,但對於有些應用,這種點對點的通訊模式不能有效地知足實際應用的需求。例如:一個數字電話會議系統由多個會場組成,當在其中一個會場的參會人發言時,要求其它會場都能即時的獲得此發言的內容,這是一個典型的一對多的通訊應用,一般把這種一對多的通訊稱爲多播通訊。採用多播通訊技術,不只能夠實現一個發送者和多個接收者之間進行通訊的功能,並且能夠有效減輕網絡通訊的負擔,避免資源的無謂浪費。tcp

廣播也是一種實現一對多數據通訊的模式,但廣播與多播在實現方式上有所不一樣。廣播是將數據從一個工做站發出,局域網內的其餘全部工做站都能收到它。這一特徵適用於無鏈接協議,由於LAN上的全部機器均可得到並處理廣播消息。使用廣播消息的不利之處是每臺機器都必須對該消息進行處理。多播通訊則不一樣,數據從一個工做站發出後,若是在其它LAN上的機器上面運行的進程表示對這些數據「有興趣」,多播數據纔會制給它們。函數

本實例由SenderReceiver兩個程序組成,Sender用戶從控制檯上輸入多播發送數據,Receiver端都要求加入同一個多播組,完成接收Sender發送的多播數據。測試

1、實現方法

一、協議支持

並非全部的協議都支持多播通訊,對Win32平臺而言,僅兩種可從WinSock內訪問的協議(IP/ATM)才提供了對多播通訊的支持。因一般通訊應用都創建在TCP/IP協議之上的,因此本文只針對IP協議來探討多播通訊技術。優化

支持多播通訊的平臺包括Windows CE 2.一、Windows 9五、Windows 9八、Windows NT 四、Windows 2000和WindowsXP。自2.1版開始,Windows CE纔開始實現對IP多播的支持。本文實例創建在WindowsXP專業版平臺上。spa

二、多播地址

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部分永久地址說明:code

地 址 說 明
244.0.0.1 基本地址(保留)
244.0.0.1 子網上的全部系統
244.0.0.2 子網上的全部路由器
244.0.0.5 子網上全部OSPF路由器
244.0.0.6 子網上全部指定的OSPF路由器
244.0.0.9 RIP第2版本組地址
244.0.1.1 網絡時間協議
244.0.1.24 WINS服務器組地址

三、多播路由器

多播由特殊的多播路由器來實現,多播路由器同時也能夠是普通路由器。各個多播路由器每分鐘發送一個硬件多播信息給子網上的主機(目的地址爲244.0.0.1),要求它們報告其進程當前所屬的是哪一組,各主機將它感興趣的D類地址返回。這些詢問和響應分組使用IGMP(Internet group management protocol),它大體相似於ICMP。它只有兩種分組:詢問和響應,都有一個簡單的固定格式,其中有效載荷字段的第一個字段是一些控制信息,第二字段是一個D類地址,在RFC1112中有詳細說明。

多播路由器的選擇是經過生成樹實現的,每一個多播路由器採用修改過的距離矢量協議和其鄰居交換信息,以便向每一個路由器爲每一組構造一個覆蓋全部組員的生成樹。在修剪生成樹及刪除無關路由器和網絡時,用到了不少優化方法。

四、庫支持

WinSock提供了實現多播通訊的API函數調用。針對IP多播,WinSock提供了兩種不一樣的實現方法,具體取決於使用的是哪一個版本的WinSock。第一種方法是WinSock1提供的,要求經過套接字選項來加入一個組;另外一種方法是WinSock2提供的,它是引入一個新函數,專門負責多播組的加入,這個函數即是WSAJoinLeaf,它是基層協議是無關的。本文將經過一個多播通訊的實例的實現過程,來說敘多播實現的主要步驟。由於Window98之後版本都安裝了Winsock2.0以上版本,因此本文實例在WinSock2.0平臺上開發的,但在其中對WinSock1實現不一樣的地方加以說明。

2、編程步驟

一、啓動Visual C++6.0,建立一個控制檯項目工程MultiCase。在此項目工程中添加Sender和Receiver兩個項目。Receiver項目實現步驟:

(1)、建立一個SOCK_DGRAM類型的Socket。
(2)、將此Socket綁定到本地的一個端口上,爲了接收服務器端發送的多播數據。
(3)、加入多播組。

①、WinSock2中引入一個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這兩個參數並未真正實現,因此均應設爲NULL。LpSQOS和lpGQOS這兩個參數是有關Qos(服務質量)的設置,一般也設爲NULL,有關Qos內容請參閱MSDN或有關書籍。最後一個參數dwFlags指出該主機是發送數據、接收數據或收發兼併。該參數可選值分別是:JL_SENDER_ONLY、JL_RECEIVER_ONLY或者JL_BOTH。

②、在WinSock1平臺上加入多播組須要調用setsockopt函數,同時設置IP_ADD_MEMBERSHIP選項,指定想加入的那個組的地址結構。具體實現代碼將在下面代碼註釋列出。

#p#

(4)、接收多播數據。

Sender實現步驟:

(1)、建立一個SOCK_DGRAM類型的Socket。
(2)、加入多播組。
(3)、發送多播數據。

三、編譯兩個項目,在局域網中按以下步驟測試:

(1)、將Sender.exe拷貝到發送多播數據的PC上。
(2)、將Receiver.exe拷貝到多個要求接收多播數據的PC上。
(3)、各自運行相應的程序。
(4)、在Sender PC上輸入多播數據後,你就能夠在Receiver PC上看到輸入的多播數據。

3、程序代碼

Receiver.c程序代碼

#include <winsock2.h>
#include <ws2tcpip.h> #include <stdio.h> #include <stdlib.h> #define MCASTADDR "233.0.0.1" //本例使用的多播組地址。 #define MCASTPORT 5150 //綁定的本地端口號。 #define BUFSIZE 1024 //接收數據緩衝大小。 int main( int argc,char ** argv) {  WSADATA wsd;  struct sockaddr_in local,remote,from;  SOCKET sock,sockM;  TCHAR recvbuf[BUFSIZE];  /*struct ip_mreq mcast; // Winsock1.0 */  int len = sizeof( struct sockaddr_in);  int ret;  //初始化WinSock2.2  if( WSAStartup( MAKEWORD(2,2),&wsd) != 0 )  {   printf("WSAStartup() failed\n");   return -1;  }  /*  建立一個SOCK_DGRAM類型的SOCKET  其中,WSA_FLAG_MULTIPOINT_C_LEAF表示IP多播在控制面層上屬於"無根"類型;  WSA_FLAG_MULTIPOINT_D_LEAF表示IP多播在數據面層上屬於"無根",有關控制面層和  數據面層有關概念請參閱MSDN說明。  */  if((sock=WSASocket(AF_INET,SOCK_DGRAM,0,NULL,0,   WSA_FLAG_MULTIPOINT_C_LEAF|WSA_FLAG_MULTIPOINT_D_LEAF|   WSA_FLAG_OVERLAPPED)) == INVALID_SOCKET)  {   printf("socket failed with:%d\n",WSAGetLastError());   WSACleanup();   return -1;  }  //將sock綁定到本機某端口上。  local.sin_family = AF_INET;  local.sin_port = htons(MCASTPORT);  local.sin_addr.s_addr = INADDR_ANY;  if( bind(sock,(struct sockaddr*)&local,sizeof(local)) == SOCKET_ERROR )  {   printf( "bind failed with:%d \n",WSAGetLastError());   closesocket(sock);   WSACleanup();   return -1;  }  //加入多播組  remote.sin_family = AF_INET;  remote.sin_port = htons(MCASTPORT);  remote.sin_addr.s_addr = inet_addr( MCASTADDR );  /* Winsock1.0 */  /*  mcast.imr_multiaddr.s_addr = inet_addr(MCASTADDR);  mcast.imr_interface.s_addr = INADDR_ANY;  if( setsockopt(sockM,IPPROTO_IP,IP_ADD_MEMBERSHIP,(char*)&mcast, sizeof(mcast)) == SOCKET_ERROR)  {   printf("setsockopt(IP_ADD_MEMBERSHIP) failed:%d\n",WSAGetLastError());   closesocket(sockM);   WSACleanup();   return -1;  }  */  /* Winsock2.0*/  if(( sockM = WSAJoinLeaf(sock,(SOCKADDR*)&remote,sizeof(remote), NULL,NULL,NULL,NULL, JL_BOTH)) == INVALID_SOCKET)  {   printf("WSAJoinLeaf() failed:%d\n",WSAGetLastError());   closesocket(sock);   WSACleanup();   return -1;  }  //接收多播數據,當接收到的數據爲"QUIT"時退出。  while(1)  {   if(( ret = recvfrom(sock,recvbuf,BUFSIZE,0, (struct sockaddr*)&from,&len)) == SOCKET_ERROR)   {    printf("recvfrom failed with:%d\n",WSAGetLastError());    closesocket(sockM);    closesocket(sock);    WSACleanup();    return -1;   }   if( strcmp(recvbuf,"QUIT") == 0 ) break;   else {    recvbuf[ret] = '\0';    printf("RECV:' %s ' FROM <%s> \n",recvbuf,inet_ntoa(from.sin_addr));   }  }  closesocket(sockM);  closesocket(sock);  WSACleanup();  return 0; }

Sender.c程序代碼

#include <winsock2.h>
#include <ws2tcpip.h> #include <stdio.h> #include <stdlib.h> #define MCASTADDR "233.0.0.1" //本例使用的多播組地址。 #define MCASTPORT 5150 //本地端口號。 #define BUFSIZE 1024 //發送數據緩衝大小。 int main( int argc,char ** argv) {  WSADATA wsd;  struct sockaddr_in remote;  SOCKET sock,sockM;  TCHAR sendbuf[BUFSIZE];  int len = sizeof( struct sockaddr_in);  //初始化WinSock2.2  if( WSAStartup( MAKEWORD(2,2),&wsd) != 0 )  {   printf("WSAStartup() failed\n");   return -1;  }  if((sock=WSASocket(AF_INET,SOCK_DGRAM,0,NULL,0,   WSA_FLAG_MULTIPOINT_C_LEAF|WSA_FLAG_MULTIPOINT_D_LEAF|   WSA_FLAG_OVERLAPPED)) == INVALID_SOCKET)  {   printf("socket failed with:%d\n",WSAGetLastError());   WSACleanup();   return -1;  }  //加入多播組  remote.sin_family = AF_INET;  remote.sin_port = htons(MCASTPORT);  remote.sin_addr.s_addr = inet_addr( MCASTADDR );  if(( sockM = WSAJoinLeaf(sock,(SOCKADDR*)&remote,   sizeof(remote),NULL,NULL,NULL,NULL,   JL_BOTH)) == INVALID_SOCKET)  {   printf("WSAJoinLeaf() failed:%d\n",WSAGetLastError());   closesocket(sock);   WSACleanup();   return -1;  }  //發送多播數據,當用戶在控制檯輸入"QUIT"時退出。  while(1)  {   printf("SEND : ");   scanf("%s",sendbuf);   if( sendto(sockM,(char*)sendbuf,strlen(sendbuf),0,(struct sockaddr*) &remote,sizeof(remote))==SOCKET_ERROR)   {    printf("sendto failed with: %d\n",WSAGetLastError());    closesocket(sockM);    closesocket(sock);    WSACleanup();    return -1;   }   if(strcmp(sendbuf,"QUIT")==0) break;   Sleep(500);  }  closesocket(sockM);  closesocket(sock);  WSACleanup();  return 0; }

4、小結

本實例對IP多播通訊進行了探討,實例程序由Sender和Receiver兩部分組成,Sender用戶從控制檯上輸入多播發送數據,Receiver端都要求加入同一個多播組,完成接收Sender發送的多播數據。

相關文章
相關標籤/搜索