在局域網中管理員常常需要將某條信息發送給一組用戶如果使用一對一的發送方法雖然是可行的但是過於麻煩也常會出現漏發錯發為了更有效的解決這種組通信問題出現了一種多播技術(也常稱為組播通信)它是基於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發送的多播數據
From:http://tw.wingwit.com/Article/program/net/201311/11975.html