程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> VC >> 關於VC++ >> 用Visual C++實現局域網IP多播

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

編輯:關於VC++

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

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

廣播也是一種實現一對多數據通信的模式,但廣播與多播在實 現方式上有所不同。廣播是將數據從一個工作站發出,局域網內的其他所有工作站都能收到 它。這一特征適用於無連接協議,因為LAN上的所有機器都可獲得並處理廣播消息。使用廣播 消息的不利之處是每台機器都必須對該消息進行處理。多播通信則不同,數據從一個工作站 發出後,如果在其它LAN上的機器上面運行的進程表示對這些數據"有興趣",多播 數據才會制給它們。

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

一、實現方法

1、 協議支持

並不是所有的協議都支持多播通信, 對Win32平台而言,僅兩種可從WinSock內訪問的協議(IP/ATM)才提供了對多播通信的支持 。因通常通信應用都建立在TCP/IP協議之上的,所以本文只針對IP協議來探討多播通信技術 。

支持多播通信的平台包括Windows CE 2.1、Windows 95、Windows 98、Windows NT 4、Windows 2000和WindowsXP。自2.1版開始,Windows CE才開始實現對IP多播的支持。本文 實例建立在WindowsXP專業版平台上。

2、多播地址

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

地 址 說 明

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服務器組地址

3、 多播路由器

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

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

4.庫支持

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

二、編程步驟

1、啟動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選項,指 定想加入的那個組的地址結構。具體實現代碼將在下面代碼注釋列出。

(4)、接收多 播數據。

Sender實現步驟:

(1)、創建一個SOCK_DGRAM類型的Socket。

(2)、加入多播組。

(3)、發送多播數據。

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

(1)、將Sender.exe拷貝到發送多播數據的PC上。

(2)、將Receiver.exe拷貝到多個要求接收多播數據的PC上。

(3)、各自運行相 應的程序。

(4)、在Sender PC上輸入多播數據後,你就可以在Receiver PC上看到 輸入的多播數據。

三、程序代碼

//////////////////////////////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;
}

四、小結

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

  1. 上一頁:
  2. 下一頁:
欄目導航
Copyright © 程式師世界 All Rights Reserved