ip多播(也稱多址廣播或組播)技術(shù),是一種答應(yīng)一臺或多臺主機(多播源)發(fā)送單一數(shù)據(jù)包到多臺主機(一次的,同時的)的TCP/IP網(wǎng)絡(luò)技術(shù)。多播作為一點對多點的通信,是節(jié)省網(wǎng)絡(luò)帶寬的有效方法之一。在網(wǎng)絡(luò)音頻/視頻廣播的應(yīng)用中,當(dāng)需要將一個節(jié)點的信號傳送到多個節(jié)點時,無論是采用重復(fù)點對點通信方式,還是采用廣播方式,都會嚴(yán)重浪費網(wǎng)絡(luò)帶寬,只有多播才是最好的選擇。多播能使一個或多個多播源只把數(shù)據(jù)包發(fā)送給特定的多播組,而只有加入該多播組的主機才能接收到數(shù)據(jù)包。目前,IP多播技術(shù)被廣泛應(yīng)用在網(wǎng)絡(luò)音頻/視頻廣播、AOD/VOD、網(wǎng)絡(luò)視頻會議、多媒體遠(yuǎn)程教育、“push”技術(shù)(如股票行情等)和虛擬現(xiàn)實游戲等方面。
一、IP多播技術(shù)簡介
1.IP多播地址和多播組
IP多播通信必須依靠于IP多播地址,在IPv4中它是一個D類IP地址,范圍從224.0.0.0到239.255.255.255,并被劃分為局部鏈接多播地址、預(yù)留多播地址和治理權(quán)限多播地址三類。其中,局部鏈接多播地址范圍在224.0.0.0~224.0.0.255,這是為路由協(xié)議和其它用途保留的地址,路由器并不轉(zhuǎn)發(fā)屬于此范圍的IP包;預(yù)留多播地址為224.0.1.0~238.255.255.255,可用于全球范圍(如Internet)或網(wǎng)絡(luò)協(xié)議;治理權(quán)限多播地址為239.0.0.0~239.255.255.255,可供組織內(nèi)部使用,類似于私有IP地址,不能用于Internet,可限制多播范圍。
使用同一個IP多播地址接收多播數(shù)據(jù)包的所有主機構(gòu)成了一個主機組,也稱為多播組。一個多播組的成員是隨時變動的,一臺主機可以隨時加入或離開多播組,多播組成員的數(shù)目和所在的地理位置也不受限制,一臺主機也可以屬于幾個多播組。此外,不屬于某一個多播組的主機也可以向該多播組發(fā)送數(shù)據(jù)包。
2.IP多播技術(shù)的硬件支持
要實現(xiàn)IP多播通信,要求介于多播源和接收者之間的路由器、集線器、交換機以及主機均需支持IP多播。目前,IP多播技術(shù)已得到硬件、軟件廠商的廣泛支持。
(1)主機
支持IP多播通信的平臺包括Windows CE 2.1、Windows 95、Windows 98、Windows NT 4和windows 2000等,運行這些操作系統(tǒng)的主機都可以進(jìn)行IP多播通信。此外,新生產(chǎn)的網(wǎng)卡也幾乎都提供了對IP多播的支持。
(2)集線器和交換機
目前大多數(shù)集線器、交換機只是簡單地把多播數(shù)據(jù)當(dāng)成廣播來發(fā)送接收,但一些中、高檔交換機提供了對IP多播的支持。例如,在3COM SuperStack 3 Swith 3300交換機上可啟用802.1p或IGMP多播過濾功能,只為已偵測到IGMP數(shù)據(jù)包的端口轉(zhuǎn)發(fā)多播數(shù)據(jù)包。
(3)路由器
多播通信要求多播源節(jié)點和目的節(jié)點之間的所有路由器必須提供對Internet組治理協(xié)議(IGMP)、多播路由協(xié)議(如PIM、DVMRP等)的支持。
當(dāng)一臺主機欲加入某個多播組時,會發(fā)出“主機成員報告”的IGMP消息通知多播路由器。當(dāng)多播路由器接收到發(fā)給那個多播組的數(shù)據(jù)時,便會將其轉(zhuǎn)發(fā)給所有的多播主機。多播路由器還會周期性地發(fā)出“主機成員查詢”的IGMP消息,向子網(wǎng)查詢多播主機,若發(fā)現(xiàn)某個多播組已沒有任何成員,則停止轉(zhuǎn)發(fā)該多播組的數(shù)據(jù)。此外,當(dāng)支持IGMP v2的主機(如Windows 98/2000計算機)退出某個多播組時,還會向路由器發(fā)送一條“離開組”的IGMP消息,以通知路由器停止轉(zhuǎn)發(fā)該多播組的數(shù)據(jù)。但只有當(dāng)子網(wǎng)上所有主機都退出某個多播組時,路由器才會停止向該子網(wǎng)轉(zhuǎn)發(fā)該多播組的數(shù)據(jù)。
使用多播路由協(xié)議,路由器可建立起從多播源節(jié)點到所有目的節(jié)點的多播路由表,從而實現(xiàn)在子網(wǎng)間轉(zhuǎn)發(fā)多播數(shù)據(jù)包。例如,PIM(協(xié)議獨立多播)就是一種多播路由協(xié)議,它有兩種類型:稀疏模式(sparse-mode)和密集模式(dense-mode)。以Cisco 2621路由器為例,啟用IP多播轉(zhuǎn)發(fā)功能的基本設(shè)置如下:
c2621(config)# ip multicast-routing 啟動IP多播,使路由器成為一個多播路由器
c2621(config)# int f0/0 配置快速以太網(wǎng)端口0
c2621(config-if)# ip pim dense-mode(或sparse-mode)啟動PIM,同時激活I(lǐng)GMP協(xié)議
c2621(config-if)# int f0/1 配置快速以太網(wǎng)端口1
c2621(config-if)# ip pim dense-mode(或sparse-mode)
二、IP多播應(yīng)用的編程方法
在實際應(yīng)用中,編程人員通常需要自己編制底層網(wǎng)絡(luò)應(yīng)用程序來實現(xiàn)網(wǎng)上的底層通信,如具體實現(xiàn)IP多播通信的功能。編制底層網(wǎng)絡(luò)應(yīng)用程序通常要借助于網(wǎng)絡(luò)數(shù)據(jù)通信編程接口,而在不同的操作系統(tǒng)中所提供的網(wǎng)絡(luò)編程接口是有所不同的,如在Microsoft Windows環(huán)境下的網(wǎng)絡(luò)編程接口就是Windows套接字(Windows Socket,簡稱Winsock)。
Winsock提供了包括TCP/IP、IPX等多種通信協(xié)議下的編程接口。不同的Windows版本支持不同的Winsock版本,其中Windows 95等早期版本本身只支持Winsock1.1(16位)下的編程(可以通過安裝相關(guān)的軟件包使其支持Winsock2.0),而Windows98、Windows NT4.0、Windows 2000則直接支持Winsock2.0(32位)。Winsock2.0是Winsock1.1的擴展,除兼容Winsock1.1 API外,還定義了一套可支持IP多播的與協(xié)議無關(guān)的API。
使用Winsock 2.0實現(xiàn)IP多播的一般步驟如下:
1.初始化Winsock資源
在使用Winsock之前,必須調(diào)用WSAStartup()函數(shù)初始化Windows Sockets DLL。它答應(yīng)應(yīng)用程序或DLL指定Windows Sockets API要求的版本。
2.創(chuàng)建套接字
調(diào)用WSASocket()函數(shù)可以創(chuàng)建一個使用UDP協(xié)議的套接字,它是加入多播組的初始化套接字,并且以后數(shù)據(jù)的發(fā)送和接收都在該套接字上進(jìn)行。針對IP多播通信,可將參數(shù)dwFlags設(shè)置為WSA_FLAG_MULTIPOINT_C_LEAF、WSA_FLAG_MULTIPOINT_D_LEAF和WSA_FLAG_OVERLAPPED的位和,指明IP多播通信在控制層面和數(shù)據(jù)層面都是“無根的”,只存在葉節(jié)點,它們可以任意加入一個多播組,而且從一個葉節(jié)點發(fā)送的數(shù)據(jù)會傳送到每一個葉節(jié)點(包括它自己);創(chuàng)建的套接字具有重疊屬性。
3.設(shè)置套接字的選項
調(diào)用setsockopt()函數(shù)為套接字設(shè)置SO_REUSEADDR選項,以答應(yīng)套接字綁扎到一個已在使用的地址上。
4.綁定套接字
調(diào)用bind()函數(shù)綁定套接字,從而將創(chuàng)建好的套接字與本地地址和本地端口聯(lián)系起來。對于多播通信來說,發(fā)送和接收數(shù)據(jù)通常采用同一個端口。
5.設(shè)置多播套接字的模式
WSAIoctl()函數(shù)的命令碼SIO_MULTICAST_LOOP用來答應(yīng)或禁止多播通信時發(fā)送出去的通信流量是否也能夠在同一個套接字上被接收(即多播返回)。值得注重的是,在Windows 95/98/NT 4中,默認(rèn)是答應(yīng)多播返回,但不能設(shè)置禁止,否則會出錯;只有在Windows 2000以上版本中,才能設(shè)置答應(yīng)/禁止多播返回。
WSAIoctl()函數(shù)的命令碼SIO_MULTICAST_SCOPE用來設(shè)置多播傳播的范圍,即生存時間TTL。每當(dāng)多播路由器轉(zhuǎn)發(fā)多播數(shù)據(jù)包時,數(shù)據(jù)包中的TTL值都會被減1,若數(shù)據(jù)包的TTL減少到0,則路由器將拋棄該數(shù)據(jù)包。TTL的值是多少,多播數(shù)據(jù)便最多能經(jīng)過多少個多播路由器。例如,TTL值為0,則多播只能在本地主機的多個套接字間傳播,而不能傳播到“網(wǎng)線”上;TTL值為1(默認(rèn)值),則多播數(shù)據(jù)碰到第一個路由器,便會被它“無情”地丟棄,不答應(yīng)傳出本地網(wǎng)絡(luò)之外,即只有同一個網(wǎng)絡(luò)內(nèi)的多播組成員才能收到多播數(shù)據(jù)。
6.加入一個多播組
調(diào)用WSAJoinLeaf()函數(shù)可加入一個多播組并指定角色(發(fā)送者/接收者)。調(diào)用時,參數(shù)dwFlags可指定套接字作為發(fā)送者(JL_SENDER_ONLY)、接收者(JL_RECEIVER_ONLY)或身兼兩者(JL_BOTH)。調(diào)用成功后會返回一個多播套接字,調(diào)用closesocket()函數(shù)關(guān)閉該套接字就離開了多播組,此時可以調(diào)用WSAJoinLeaf()函數(shù)再次加入多播組。注重,對多播組數(shù)據(jù)的接收和發(fā)送不能在該套接字上完成。
7.向多播組發(fā)送數(shù)據(jù)
調(diào)用sendto()函數(shù),可在指定的UDP套接字上向指定的多播組發(fā)送多播數(shù)據(jù)。調(diào)用時,參數(shù)to應(yīng)指向多播組的IP地址。值得注重的是,若一個應(yīng)用程序只是打算給多播組發(fā)送數(shù)據(jù),便不必加入一個多播組。
8.等待事件
調(diào)用WSAAsyncSelect()函數(shù),使套接字置于非阻塞模式,這時應(yīng)用程序就可在該套接字上接收以Windows消息為基礎(chǔ)的網(wǎng)絡(luò)事件通知。例如,若參數(shù)lEvent值為FD_READ,則應(yīng)用程序可在套接字上接收到“數(shù)據(jù)正等待被讀入”的通知。
9.從多播組接收數(shù)據(jù)
調(diào)用recvfrom函數(shù),可在指定的UDP套接字上讀取輸入數(shù)據(jù)。多播通信中數(shù)據(jù)的發(fā)送與接收一般采用同一個端口,因此其發(fā)送套接字和接收套接字是一樣的。
10.關(guān)閉套接字,釋放Winsock資源。
在多播通信結(jié)束后,先調(diào)用closesocket()函數(shù)關(guān)閉多播套接字和UDP套接字,然后調(diào)用WSACleanup()函數(shù)結(jié)束對Windows Sockets DLL的使用。
三、應(yīng)用實例
為了說明IP多播技術(shù)的應(yīng)用方法,本人在Visual C++.NET環(huán)境下設(shè)計了一個簡單的基于Windows Socket 2的IP多播應(yīng)用程序,通過該例子讀者可以把握IP多播應(yīng)用程序設(shè)計的一般方法。該程序的具體設(shè)計方法如下:
1.在Visual Studio.NET中建立一個基于對話框的MFC項目CMulticastSocket。注重在“高級功能”設(shè)置中不要選擇“Windows套接字”,這是因為MFC只支持Windows Socket 1而不支持Windows Socket 2。為了能使用Winsock 2 API編程,在程序中應(yīng)包含“winsock2.h”頭文件,并在項目中加入ws2_32.lib的靜態(tài)庫,該靜態(tài)庫應(yīng)設(shè)置在項目屬性的“鏈接器”/“輸入”/“附加依靠項”中。
2.在對話框(類名CCMulticastSocketDlg)資源中,設(shè)置它的Caption為“WinSock 2多播應(yīng)用程序”,并添加以下控件:
靜態(tài)文本:Caption為“接收到的信息:”;
編輯框:ID為IDC_RECEIVE_EDIT,Read Only、Auto Vscroll、Vertical Scroll和Multiline屬性值都為True
靜態(tài)文本:Caption為“發(fā)送的信息:”
編輯框:ID為IDC_SEND_EDIT
第一個按鈕:Caption為“加入多播組(&J)”,ID為IDC_JOIN_BUTTON
第二個按鈕:Caption為“多播發(fā)送(&S)”,ID為IDC_SEND_BUTTON
第三個按鈕:Caption為“離開多播組(&L)”,ID為IDC_LEAVE_BUTTON
第四個按鈕:Caption為“退出(&Q)”,ID為IDC_QUIT_BUTTON
為兩個編輯框分別添加相關(guān)聯(lián)的CString類型的變量m_SendMessage和m_ReceiveMessage;為四個按鈕添加相應(yīng)的消息處理函數(shù);為對話框添加定時器消息(用于定時顯示接收到的消息)及其消息處理函數(shù)。
3.添加一個新的對話框資源,設(shè)置它的Caption為“加入多播組”,保留默認(rèn)的兩個按鈕控件,同時添加添加以下控件:
靜態(tài)文本:Caption為“IP多播組地址:”
編輯框:ID為IDC_IPADDRESS_EDIT
靜態(tài)文本:Caption為“IP多播端口:”
編輯框:ID為IDC_PORT_EDIT
靜態(tài)文本:Caption為“生存時間:”
編輯框:ID為IDC_TTL_EDIT
復(fù)選框:Caption為“多播返回:”,ID為IDC_LOOPBACK_CHECK,Left Text屬性值為True。
為該對話框添加新的類CJoinGroupDlg,它的基類為CDialog,然后為該對話框中的三個編輯框分別添加相
關(guān)聯(lián)的變量,即CSting m_IPAddress、UINT m_nPort、UINT m_nTTL;為復(fù)選框添加相關(guān)聯(lián)的BOOL類型的變量m_Loopback。
4.在CMulticastSocketDlg.h文件的前面添加CJoinGroupDlg的頭文件:#include “JoinGroupDlg.h”,并在CCMulticastSocketDlg類中添加了一個CJoinGroupDlg類實例對象m_JoinDlg。
5.為了能在對話框中接收網(wǎng)絡(luò)事件通知,應(yīng)增加一個用戶自定義的消息及消息處理函數(shù),具體實現(xiàn)方法如下:在CMulticastSocketDlg.h文件的前面自定義消息:#define WM_SOCK_MSG(WM_USER+166),并在afx_msg塊中說明消息處理函數(shù):afx_msg LRESULT OnSocketMsg (WPARAM wParam,LPARAM lParam);在CMulticastSocketDlg.cpp文件中的消息映射塊中,使用ON_MESSAGE (WM_SOCK_MSG,OnSocketMsg)宏指令將消息映射到消息處理函數(shù)中,并具體實現(xiàn)消息處理函數(shù):LRESULT CCMulticastSocketDlg:: OnSocketMsg(WPARAM wParam,LPARAM lParam){…}。
該程序的主要代碼可參見程序清單,相關(guān)函數(shù)的具體說明可參看Microsoft MSDN幫助系統(tǒng)。為了節(jié)省篇幅,程序中省略了部分自動生成的和用于錯誤處理的代碼。
程序清單:
// CMulticastSocketDlg.cpp : 實現(xiàn)文件
#include "stdafx.h"
#include "winsock2.h"
#include "CMulticastSocket.h"
#include "CMulticastSocketDlg.h"
……
DWord cbRet;
SOCKET Sock,SockM; file://UDP套接字,多播套接字
BOOL bFlag,bJoin;
SOCKADDR_IN local,Remote,From; file://分別指向本地、多播組和數(shù)據(jù)來源的IP地址與端口
int Fromlen;
char ReceiveBuf[32000]; file://接收緩沖區(qū)
BOOL bDataReceived;
……
BEGIN_MESSAGE_MAP(CCMulticastSocketDlg, CDialog)
……
ON_BN_CLICKED(IDC_JOIN_BUTTON, OnBnClickedJoinButton)
ON_BN_CLICKED(IDC_LEAVE_BUTTON, OnBnClickedLeaveButton)
ON_BN_CLICKED(IDC_QUIT_BUTTON, OnBnClickedQuitButton)
ON_BN_CLICKED(IDC_SEND_BUTTON, OnBnClickedSendButton)
ON_WM_TIMER()
ON_MESSAGE(WM_SOCK_MSG,OnSocketMsg)
END_MESSAGE_MAP()
BOOL CCMulticastSocketDlg::OnInitDialog()
{
CDialog::OnInitDialog();
……
SetTimer(1,100,NULL); file://設(shè)置定時器
Fromlen=sizeof(From);
bDataReceived=TRUE;
bJoin=FALSE;
return TRUE; // 除非設(shè)置了控件的焦點,否則返回 TRUE
}
……
void CCMulticastSocketDlg::OnBnClickedJoinButton() file://加入多播組
{
if(m_JoinDlg.DoModal()==IDOK)
{
WORD wVersionRequested;
WSADATA wsaData;
int 北京中慶;
wVersionRequested = MAKEWORD(2,2);
北京中慶 = WSAStartup(wVersionRequested, &wsaData); file://初始化WinSock2資源
if(北京中慶!= 0){
AfxMessageBox("不能加載Windows套接字動態(tài)鏈接庫,MB_OK");
return;
}
if (LOBYTE(wsaData.wVersion) !=2 HIBYTE(wsaData.wVersion) !=2){
AfxMessageBox("WinSock DLL不支持2.0版本,MB_OK");
WSACleanup( );
return;
}
file://創(chuàng)建一個套接字
Sock=WSASocket(AF_INET,SOCK_DGRAM,IPPROTO_UDP,
(LPWSAPROTOCOL_INFO)NULL,0,WSA_FLAG_OVERLAPPED
WSA_FLAG_MULTIPOINT_C_LEAFWSA_FLAG_MULTIPOINT_D_LEAF);
bFlag=TRUE; file://設(shè)置套接字選項,使套接字為可重用端口地址
setsockopt(Sock,SOL_SOCKET,SO_REUSEADDR,(char*)&bFlag,sizeof(bFlag));
file://將套接字綁定到用戶指定端口及默認(rèn)的接口
memset(&local,0,sizeof(local));
local.sin_family=AF_INET;
local.sin_port=htons((USHORT)m_JoinDlg.m_nPort);
local.sin_addr.s_addr=htonl(INADDR_ANY);
bind(Sock,(strUCt sockaddr FAR *)&local,sizeof(local));
file://設(shè)置多播數(shù)據(jù)報傳播范圍(生存時間TTL)
WSAIoctl(Sock,SIO_MULTICAST_SCOPE,&m_JoinDlg.m_nTTL,sizeof(int),
NULL,0,&cbRet,NULL,NULL);
file://設(shè)置多播返回(LOOKBACK)
BOOL nLoopBack=m_JoinDlg.m_Loopback;
WSAIoctl(Sock,SIO_MULTIPOINT_LOOPBACK,&nLoopBack,sizeof(nLoopBack),
NULL,0,&cbRet,NULL,NULL);
memset(&Remote,0,sizeof(Remote));
Remote.sin_family=AF_INET;
Remote.sin_addr.s_addr=inet_addr(m_JoinDlg.m_IPAddress);
Remote.sin_port=htons(m_JoinDlg.m_nPort);
file://加入到指定的多播組,并指定為既作為發(fā)送者又作為接收者(JL_BOTH)
SockM=WSAJoinLeaf(Sock,(sockaddr*)&Remote,sizeof(Remote),
NULL,NULL,NULL,NULL,JL_BOTH);
WSAAsyncSelect(Sock,m_hWnd,WM_SOCK_MSG,FD_READ); file://注冊網(wǎng)絡(luò)消息及其網(wǎng)絡(luò)事件
bJoin=TRUE;
}
}
void CCMulticastSocketDlg::OnBnClickedSendButton() file://多播發(fā)送
{
if(bJoin){
UpdateData(TRUE);
const char* strMessage=LPCTSTR(m_SendMessage);
int nSize=m_SendMessage.GetLength()+1;
sendto(Sock,strMessage,nSize,0,(sockaddr*)&Remote,sizeof(Remote));
}
else
AfxMessageBox("請先加入多播組!");
m_SendMessage="";
UpdateData(FALSE);
}
void CCMulticastSocketDlg::OnBnClickedLeaveButton()
{ file://離開多播組
closesocket(SockM);
closesocket(Sock);
WSACleanup();
m_SendMessage="";
m_ReceiveMessage="";
bDataReceived=TRUE;
bJoin=FALSE;
UpdateData(FALSE);
}
void CCMulticastSocketDlg::OnBnClickedQuitButton()
{ file://退出
DestroyWindow();
}
void CCMulticastSocketDlg::OnTimer(UINT nIDEvent) file://定時器處理函數(shù),實現(xiàn)接收信息的定時更新
{
if(bDataReceived)
{
m_ReceiveMessage+=ReceiveBuf;
m_ReceiveMessage+="/r/n";
bDataReceived=FALSE;
UpdateData(FALSE);
}
CDialog::OnTimer(nIDEvent);
}
LRESULT CCMulticastSocketDlg::OnSocketMsg(WPARAM wParam,LPARAM lParam)
{
file://檢索網(wǎng)絡(luò)事件
switch(WSAGETSELECTEVENT(lParam)){
case FD_READ:
recvfrom(Sock,ReceiveBuf,32000,0,(sockaddr *)&From,&Fromlen);
bDataReceived=TRUE; file://設(shè)置已接收到一條信息標(biāo)志
break;
}
return TRUE;
}
新聞熱點
疑難解答
圖片精選