天天看點

多點傳播的實作和需要注意的問題

       前段時間研究了一小段時間的網絡多點傳播問題,自己很有感觸,把自己的經曆寫出來,希望有需要的可以少走一些彎路。(轉發請注明出處)

         先說一下原理,我覺得這個還是需要說一下的。

         網絡中存在三種傳輸概念,單點傳播,多點傳播,廣播,單點傳播和廣播大家可能都很了解,單點傳播,連接配接的建立是一對一的,廣播則是向一個網絡内所有使用者發送。

         我們這裡隻說多點傳播,多點傳播的好處我就不說了,節省帶寬什麼的。

        其實我個人覺得,單點傳播多點傳播都可以看錯是某種意義上的廣播,單點傳播可以了解為網絡隻有一個使用者,多點傳播則可以了解為是受限制的一組廣播使用者(指定的一組使用者)。

網絡中存在五種IP位址,A,B,C,D,E類

需要明白的一點事,IP位址分為兩部分,IP=類别+網絡号+主機号

           類别号     網絡号       主機号

A類,   0             1~126          0~255 0~255 1~254

B類,10              128~191     0~255 0~2551~254

C類,110            192~223     0~255 0~255 1~254

D類,1110         224~239      0~255 0~255 1~254

E類,1111          240~255 0~255 0~255 1~254

多點傳播的實作和需要注意的問題

排版不是我想要的,還是看圖吧。

其中,對于A類位址來說,10.0.0.0 ~ 10.255.255.255為私有位址,127.0.0.0~127.255.255.255為回環位址,主機ID全0辨別一個網絡,主機ID全1表示廣播位址,B類位址:172.16.0.0 ~ 172.31.255.255為私有位址,主機ID全0辨別一個網絡,主機ID全1表示廣播位址,C類位址:192.168.0.0 ~ 192.168.255.255為私有位址(這個應該很熟悉吧),主機ID全0辨別一個網絡,主機ID全1表示廣播位址。

          多點傳播對應的MAC位址:01-00-5e-xx-xx-xx

映射關系:

多點傳播的實作和需要注意的問題

         多點傳播隻會由感興趣的端口接收,他是怎麼知道這些端口的呢?這裡就要說D類位址了。這裡一定要了解一個概念:多點傳播組。多點傳播的資料是定向的發給一個多點傳播組的,這樣凡是多點傳播組内的成員就會收到資料,有人問了,網絡上有那麼多多點傳播組,是怎麼知道要發給哪個組。這裡就是D類位址了,D類位址充當了多點傳播組的辨別,記住,僅僅是辨別。可以了解為,多點傳播組的目的位址,多點傳播組的ID。所有的主機可以選擇加入多點傳播組,也就是被标記為一個該多點傳播組的一個ID。如何加入多點傳播組是技術問題,我們後面講。

          那麼接下來的問題是,分布在全球的這麼多台主機,如果美國的一台主機加入了這個多點傳播組,英國的一個主機也加入了同樣一個多點傳播組,而我源頭是中國這邊的主機,那麼他怎麼發過去呢?

          需要考慮的問題,資料包如何到達子網路由器(主機-路由器之間的組成員關系協定),資料包如何在公網内路由(路由器-路由器之間的多點傳播路由協定)(轉發規則),資料如何被目的子網路由器接受并轉發。

           首先,這個資料包要能到達你所在的子網的路由器,這一步如何實作的?答案,是IGMP協定。

IGMP(Internet Group Management Protocol),看名字就可以看出是網際網路多點傳播管理協定。是主機與路由器之間唯一的信令協定。目前有三個版本,V1,V2,V3(不同之處主要是V1,V2,V2是主動離開多點傳播組,V1則是不會主動離開).通過用wireshark軟體抓包測試,你會發現,目前網絡上大部分是V2版本的多點傳播協定包。主機向本地路由器發送一個IGMP,加入相對應的多點傳播(多點傳播位址端口,自己定義)。這樣主機是可以加入多點傳播了,但是到來的多點傳播資料包是如何知道資料包要發給誰呢?還是IGMP!當發現有資料包來的時候,本地路由器向本地子網内的主機發送一個查詢封包(IGMP),加入了多點傳播組的主機則會發送一個回複給路由器(IGMP包),那後面就會轉發此資料包了。如果主機要離開多點傳播組怎麼辦呢?也是IGMP!主機隻需要向路由器發送一個離開的消息(IGMP包)給路由器就可以了。

           資料發送到路由器後,路由器根據什麼将資料包轉發到其他路由器呢(公網内路由器的轉發)?答案是:域内多點傳播路由協定及域間多點傳播路由協定。其實個人感覺不需要區分這個域間和域内路由,我們隻需要關心,資料包可以在網際網路上自由轉發就可以了。這裡需要知道的是兩個域内路由協定,PIM-SM,PIM-DM,DVMRP(主要差別是密集模式和稀疏模式差別)。路由器間的轉發需要的是這幾個路由協定,原理在網上一搜一大堆,我就不講了。這幾個協定主要在轉發,鄰居發現什麼的有些差別,比如說剪枝政策。

         多點傳播的轉發利用了一個叫逆向路徑轉發政策(RPF),RPF協定決定是否轉發次資料包以及丢棄掉。

        路由器檢查到達多點傳播包的源位址,如果資訊包是在可傳回源站點的接口上到達,則RPF檢查成功,資訊包被轉發如果RPF檢查失敗,丢棄資訊包。

         大家這個時候可能對這個有些概念,多點傳播說的也很神乎,比如很省帶寬,畢竟是一發多,隻需要發送一份,但是可以很多都接受。比單點傳播好用多了,那你可能會問,那多點傳播的應用應該很廣了?答案是肯定的,多點傳播的應用很多,比如多媒體會議,聯網遊戲等。但是有個問題目前确實緻命的!應用的條件: 路由器沒有開啟這個功能!!!

          大緻可以說一下多點傳播路由的過程,資料包從源端口出發,經路由器轉發(這個應該是所有路由器都會經過,可是所有哦),然後到達有目的多點傳播的成員則轉發給他。這個時候你可能會發現一個問題,如果多點傳播大規模應用的話,那網絡上這種資料包會非常多,畢竟誰都可以建立多點傳播組,然後發送,路由器是要進行轉發的。

          我自己本來也是想實作一個多點傳播功能的類似于視訊會議的應用的,但是測試的時候發現,資料包就是沖不出去内網,隻能在區域網路内轉(區域網路内可以收到)。後來發現是路由器雖然有這個功能,但是預設都給關閉了。記得當時查這個資料的時候,在一個路由器管理者配置的一個BBS,上面一個人發帖,有人回答說:管理者如果開啟這個功能,那隻能說有病。足可以看出,目前路由器對多點傳播支援的尴尬處境。

         是以這裡隻是提醒一下做這個的朋友,路由器對這個支援不是很好,如果要做的話多考慮一下。可能我了解不對,如果有做出來這個的,希望能提供觀點哈。

多點傳播的實作和需要注意的問題

       附一個簡單的多點傳播程式,同一子網下運作無誤,隻需要打開這個用戶端就可以。兩個程序,一個負責發,一個負責接收。

#include <iostream>
#include <winsock2.h> //注意這裡的include檔案順序
#include <Ws2tcpip.h>
#include <process.h> //_beginthread要求

#pragma comment(lib, "ws2_32.lib")

using namespace std;

const char* MULTICAST_IP = "230.1.1.99"; //多點傳播組位址
const int MULTICAST_PORT = 2002; //多點傳播組端口

const int BUFFER_SIZE = 1024;

void do_send(void* arg); //讀取使用者輸入并發送到多點傳播組線程函數
void do_read(void* arg); //讀物多點傳播組資料函數

int main()
{
	//這個結構被用來存儲被WSAStartup函數調用後傳回的Windows Sockets資料。
	//它包含Winsock.dll執行的資料。
		WSAData wsaData;

		/*
		使用Socket的程式在使用Socket之前必須調用WSAStartup函數。該函數的第一個參數指明程式請求使用的Socket版本,其中高位位元組指明副版本、低位位元組指明主版本;作業系統利用第二個參數傳回請求的Socket的版本資訊。
		加載Windows套接字動态連結庫
		*/
		if( WSAStartup(MAKEWORD(2,2), &wsaData) != 0 )
		{
		cout <<"Error in WSAStartup"<<endl;
		return 0;
		}

		SOCKET server;
		//原始的方式
		/*
		第一個參數指定應用程式使用的通信協定的協定族,對于TCP/IP協定族,該參數置AF_INET;
		第二個參數指定要建立的套接字類型,流套接字類型為SOCK_STREAM、資料報套接字類型為SOCK_DGRAM、
		原始套接字SOCK_RAW(WinSock接口并不适用某種特定的協定去封裝它,而是由程式自行處理資料包以及協定首部);
		另一種方式WSASocket
		*/
		server = socket(AF_INET, SOCK_DGRAM, 0); //建立一個UDP套接口
		cout<<"create socket: "<<server<<endl;

		int ret ;

		const int on = 1; //允許程式的多個執行個體運作在同一台機器上
		/*
		調用setsockopt()函數為套接字設定SO_REUSEADDR選項,以允許套接字綁紮到一個已在使用的位址上。設定套接字的選項
		*/
		ret = setsockopt(server, SOL_SOCKET, SO_REUSEADDR, (char *)&on, sizeof(on));
		if( ret == SOCKET_ERROR )
		{
		WSACleanup();
		
		cout<<"Error in setsockopt(SO_REUSEADDR): "<<WSAGetLastError()<<endl;
		return 0;
		}
		
		const int routenum = 10;

		//ret = setsockopt(server,IPPROTO_IP,IP_MULTICAST_TTL,\
		
		ret = setsockopt(server,IPPROTO_IP,IP_MULTICAST_TTL,\
		(char*)&routenum,sizeof(routenum));
		if( ret == SOCKET_ERROR )
		{
		WSACleanup();

		cout<<"Error in setsockopt(IP_MULTICAST_TTL): "<<WSAGetLastError()<<endl;
		return 0;
		}

		const int loopback = 0; //禁止回饋
		//使多點傳播封包環路有效或無效
		ret = setsockopt(server,IPPROTO_IP,IP_MULTICAST_LOOP,\
		(char*)&loopback,sizeof(loopback));
		if( ret == SOCKET_ERROR )
		{
		WSACleanup();

		cout<<"Error in setsockopt(IP_MULTICAST_LOOP): "<<WSAGetLastError()<<endl;
		return 0;
		}
		//位址資訊,local設定為多點傳播組端口
		sockaddr_in local;
		memset(&local, 0, sizeof(local));
		local.sin_family = AF_INET;
		local.sin_port = htons(MULTICAST_PORT);
		//INADDR_ANY為0.0.0.0
		local.sin_addr.S_un.S_addr = INADDR_ANY;
		
		ret = bind(server, (sockaddr*)(&local), sizeof(local));

		if( ret == SOCKET_ERROR )
		{
		WSACleanup();

		cout<<"Error in bind: "<<WSAGetLastError()<<endl;
		return 0;
		}
		//多點傳播組結構
		ip_mreq mreq;
		memset(&mreq, 0, sizeof(mreq));
		//本機位址
		mreq.imr_interface.S_un.S_addr = INADDR_ANY;
		//點分十進制位址轉化為IP位址
		mreq.imr_multiaddr.S_un.S_addr = inet_addr(MULTICAST_IP);

		//加入一個多點傳播組
		ret = setsockopt(server,IPPROTO_IP,IP_ADD_MEMBERSHIP,\
		(char*)&mreq,sizeof(mreq));
		if( ret == SOCKET_ERROR )
		{
		WSACleanup();

		cout<<"Error in setsockopt(IP_ADD_MEMBERSHIP): "<<WSAGetLastError()<<endl;
		return 0;
		}

		//建立了兩個線程,一個讀使用者輸入并發送,一個讀多點傳播組資料
		HANDLE hHandle[2];
		hHandle[0] = (HANDLE)_beginthread(do_send,0,(void*)server);
		hHandle[1] = (HANDLE)_beginthread(do_read,0,(void*)server);

		//如果使用者輸入結束,程式就終止了
		WaitForSingleObject(hHandle[0], INFINITE);

		WSACleanup();

		return 0;
}

void do_send(void* arg)
		{
		SOCKET server = (SOCKET)arg;

		char sendline[BUFFER_SIZE+1];

		sockaddr_in remote;
		memset(&remote, 0, sizeof(remote));
		remote.sin_addr.s_addr = inet_addr ( MULTICAST_IP );
		remote.sin_family = AF_INET ;
		remote.sin_port = htons(MULTICAST_PORT);

		for(;;) //讀取使用者輸入知道使用者輸入"end"
		{
		cin.getline(sendline, BUFFER_SIZE);

		if(strncmp(sendline,"end",3)==0)
			break;

		//發送使用者輸入的資料到多點傳播組
		sendto(server, sendline, strlen(sendline), 0, (sockaddr*)(&remote), sizeof(remote)); 
		}

		cout<<"do_send end..."<<endl;
}

void do_read(void* arg)
{
		SOCKET server = (SOCKET)arg;

		char buf[BUFFER_SIZE+1];
		int ret;

		sockaddr_in client;
		int clientLen;

		for(;;) //一直讀取知道主線程終止
		{
		clientLen = sizeof(client);
		memset(&client, 0, clientLen);

		ret = recvfrom(server, buf, BUFFER_SIZE, 0, (sockaddr*)(&clientLen), &clientLen);
		if ( ret == 0) //do_read在使用者直接回車發送了一個空字元串
		{
		continue;
		}
		else if( ret == SOCKET_ERROR )
		{
		if( WSAGetLastError() == WSAEINTR ) //主線程終止recvfrom傳回的錯
		break;

		cout<<"Error in recvfrom: "<<WSAGetLastError()<<endl;
		break ;
		}
		buf[ret] = '\0';
		cout<<"received: "<<buf<<endl;
		}

		cout<<"do_read end..."<<endl;
}
           

       結貼。

繼續閱讀