一、多播介紹
什么是多播?
單播用於兩個主機之間的端對端通信,廣播用於一個主機對整個局域網上所有主機上的數據通信。單播和廣播是兩個極端,要么對一個主機進行通信,要么對整個局域網上的主機進行通信。實際情況下,經常需要對一組特定的主機進行通信,而不是整個局域網上的所有主機,這就是多播的用途。多播,也稱為“組播”,將局域網中同一業務類型主機進行了邏輯上的分組,進行數據收發的時候其數據僅僅在同一分組中進行,其他的主機沒有加入此分組不能收發對應的數據。
多播的地址是特定的,D類地址用於多播。D類IP地址就是多播IP地址,即224.0.0.0至239.255.255.255之間的IP地址,並被划分為局部連接多播地址、預留多播地址和管理權限多播地址3類:
局部多播地址:在224.0.0.0~224.0.0.255之間,這是為路由協議和其他用途保留的地址,路由器並不轉發屬於此范圍的IP包。
預留多播地址:在224.0.1.0~238.255.255.255之間,可用於全球范圍(如Internet)或網絡協議。
管理權限多播地址:在239.0.0.0~239.255.255.255之間,可供組織內部使用,類似於私有IP地址,不能用於Internet,可限制多播范圍。
屬於永久組的地址:
224.0.0.1 --- 所有組播主機
224.0.0.2 --- 所有組播路由器
224.0.0.4 --- DRMRP路由器
224.0.0.5 --- 所有OSPF的路由器
224.0.0.6 --- OSPF指派路由器
224.0.0.9 --- RPIv2路由器
224.0.0.10 --- EIGRP路由器
224.0.0.13 --- PIM路由器
224.0.0.22 --- IGMPv3
224.0.0.25 --- RGMP
224.0.1.1 --- NTP網絡時間協議
多播主機
多播主機分為三個級別:
0級:主機不能發送或接收I P多播。
這種主機應該自動丟棄它收到的具有D類目的地址的分組。
1級:主機能發送但不能接收I P多播。
在向某個IP多播組發送數據報之前,並不要求主機加入該組。多播數據報的發送方式與單播一樣,除了多播數據報的目的地址是IP多播組之外。網絡驅動器必須能夠識別出這個地址,把在本地網絡上多播數據報。
2級:主機能發送和接收I P多播。
為了接收IP多播,主機必須能夠加入或離開多播組,而且必須支持IGMP,能夠在至少一個接口上交換組成員信息。多接口主機必須支持在它的接口的一個子網上的多播Net/3符合2級主機要求,可以完成多播路由器的工作。與單播IP選路一樣,我們假定所描述的系統是一個多播路由器,並加上了Net/3多播選路的程序。
組播的優勢
組播協議的優勢在於當需要將大量相同的數據傳輸到不通主機時,
1.能節省發送數據的主機的系統資源和帶寬;
2.組播是有選擇地復制給又要求的主機;
3. 組播可以穿越公網廣泛傳播,而廣播則只能在局域網或專門的廣播網內部傳播;
4. 組播能節省網絡主干的帶寬;
組播的缺點:
與單播相比,組播沒有補包機制,因為組播采用的是UDP的傳輸方式,並且不是針對一個接受者,所以無法有針對的進行補包。所以直接用組播協議傳輸的數據是不可靠的。
根據接收者對組播源處理方式的不同,組播模型分為以下兩大類:
ASM模型:即任意源組播模型。在ASM模型中,任一發送者都可作為組播源向某組播組地址發送組播信息,接收者通過加入由該組播組地址標識的組播組以獲得發往該組播組的組播信息。在ASM模型中,接收者無法預先知道組播源的位置,但可以在任意時間加入或離開組播組。
SSM模型:即指定信源組播模型。在現實生活中,用戶可能只對某些組播源發送的組播信息感興趣,而不願接收其它源發送的信息。SSM模型為用戶提供了一種能夠在客戶端指定組播源的傳輸服務。
在ASM模型下,接收者無法選擇組播源,只能被動地接收所有組播源的信息,而SSM模型的提出則為指定源組播提供了解決方案。
二、內核配置
查看linux系統是否支持多播和廣播,使用命令ifconfig:
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1 #說明該網卡支持
如果不支持則進行相應的配置,使得Linux支持多播IP,在默認狀態下,大多Linux發行版本關閉的對多播IP的支持。
為了在Linux系統使用多播套接口,需要從新配置和編譯Linux內核。下面看一下配置步驟:
make menuconfig
選擇網絡選項
選中IP:Enable Multicasting IP一項
保存配置
重新編譯內核
三、IGMP
IGMP是IPv4引入的管理多播客戶和它們之間關系的協議。IGMP被開發出來用於通知路由器網絡上的一個機器對指定組的數據感興趣。
IGMP運行於主機和與主機直連的路由器之間,其實現的功能是雙向的:
一方面,主機通過IGMP通知路由器希望接收某個特定組播組的信息;
一方面,路由器通過IGMP周期性地查詢局域網內的組播組成員是否處於活動狀態,實現所連網段組成員關系的收集與維護。通過IGMP,在路由器中記錄的信息是某個組播組是否在本地有組成員,而不是組播組與主機之間的對應關系。
為了多播正確的工作,兩個多播節點之間的所有路由器必須支持IGMP協議。任何沒有開啟IGMP協議的路由器僅簡單地丟棄接收到的多播數據。
另外,當終端加入到多播組時,它指定TTL參數,來指明終端的多播應用程序想要經過多少個路由器來發送和接收數據,如下代碼所示。
int nTtl = 0; setsockopt(sock,IPPROTO_IP,IP_MULTICAST_TTL,(char*)&nTtl,sizeof(nTtl));
初始TTL為0的多播封包被限制在同一個主機
初始TTL為1的多播封包被限制在同一個子網
初始TTL為32的多播封包被限制在同一個站點
初始TTL為64的多播封包被限制在同一個地區
初始TTL為128的多播封包被限制在同一個大陸
初始TTL為255的多播封包沒有范圍限制
注意:許多多播路由器拒絕轉發目的地址在224.0.0.0~224.0.0.255之間的任何多播數據報,不管它的TTL是多少。
IGMPv3(RFC 3376)中增加的主要功能是成員可以指定接收或拒絕來自某些組播源的報文,以實現對SSM模型的支持。
四、多播編程
linux多播編程步驟:
1>建立一個socket;
2>設置多播的參數,例如超時時間TTL,本地回環許可LOOP等
3>加入多播組
4>發送和接收數據
5>從多播組離開
多播程序設計使用setsockopt()函數和getsockopt()函數來實現,組播的選項是IP層的。
getsockopt()/setsockopt()的選項 |
含 義 |
IP_MULTICAST_TTL |
設置多播組數據的TTL值 |
IP_ADD_MEMBERSHIP |
在指定接口上加入組播組 |
IP_DROP_MEMBERSHIP |
退出組播組 |
IP_MULTICAST_IF |
獲取默認接口或設置接口 |
IP_MULTICAST_LOOP |
禁止組播數據回送 |
1.選項IP_MULTICASE_TTL
選項IP_MULTICAST_TTL允許設置超時TTL,范圍為0~255之間的任何值,例如:
unsigned char ttl=255; setsockopt(s,IPPROTO_IP,IP_MULTICAST_TTL,&ttl,sizeof(ttl));
2.選項IP_MULTICAST_IF
選項IP_MULTICAST_IF用於設置組播的默認默認網絡接口,會從給定的網絡接口發送,另一個網絡接口會忽略此數據。例如:
struct in_addr addr; setsockopt(s,IPPROTO_IP,IP_MULTICAST_IF,&addr,sizeof(addr));
參數addr是希望多播輸出接口的IP地址,使用INADDR_ANY地址回送到默認接口。
默認情況下,當本機發送組播數據到某個網絡接口時,在IP層,數據會回送到本地的回環接口,選項IP_MULTICAST_LOOP用於控制數據是否回送到本地的回環接口。例如:
unsigned char loop; setsockopt(s,IPPROTO_IP,IP_MULTICAST_LOOP,&loop,sizeof(loop)); //參數loop設置為0禁止回送,設置為1允許回送。
3.選項IP_ADD_MEMBERSHIP和IP_DROP_MEMBERSHIP(ASM模型)
加入或者退出一個多播組,通過選項IP_ADD_MEMBERSHIP和IP_DROP_MEMBER- SHIP,對一個結構struct ip_mreq類型的變量進行控制,struct ip_mreq原型如下:
struct ip_mreq { struct in_addr imn_multiaddr; /*加入或者退出的廣播組IP地址*/ struct in_addr imr_interface; /*加入或者退出的網絡接口IP地址*/ };
選項IP_ADD_MEMBERSHIP用於加入某個多播組,之后就可以向這個多播組發送數據或者從多播組接收數據。此選項的值為mreq結構,成員imn_multiaddr是需要加入的多播組IP地址,成員imr_interface是本機需要加入廣播組的網絡接口IP地址。例如:
struct ip_mreq mreq; setsockopt(s,IPPROTO_IP,IP_ADD_MEMBERSHIP,&mreq,sizeof(mreq));
4. 帶源地址的IP多播(SSM模型)
帶源地址的IP多播允許加入組時指定要接收哪些成員的數據。有兩種方式:
1> “包含”方式,這種方式下,為套接字指定N個有效源地址,套接字僅接收來自這些源地址的數據;使用IP_ADD_SOURCE_MEMBERSHIP和IP_DROP_SOURCE_MEMBERSHIP。
2> “排除”方式,這種方式下,為套接字指定N個源地址,套接字將接收來自這些源地址之外的數據;使用IP_BLOCK_SOURCE(排除某個源地址)和IP_UNBLOCK_SOURCE(從排除集合中移除此源地址)以上兩種方式輸入參數都是ip_mreq_source結構。
五、多播編程實例
服務器端:
下面是一個多播服務器的例子。多播服務器的程序設計很簡單,建立一個數據包套接字,選定多播的IP地址和端口,直接向此多播地址發送數據就可以了。多播服務器的程序設計,不需要服務器加入多播組,可以直接向某個多播組發送數據。
下面的例子持續向多播IP地址"224.0.0.100"的8888端口發送數據"BROADCAST TEST DATA",每發送一次間隔5s。
/* *broadcast_server.c - 多播服務程序 */ #define MCAST_PORT 8888; #define MCAST_ADDR "224.0.0.100"/ /*一個局部連接多播地址,路由器不進行轉發*/ #define MCAST_DATA "BROADCAST TEST DATA" /*多播發送的數據*/ #define MCAST_INTERVAL 5 /*發送間隔時間*/ int main(int argc, char*argv) { int s; struct sockaddr_in mcast_addr; s = socket(AF_INET, SOCK_DGRAM, 0); /*建立套接字*/ if (s == -1) { perror("socket()"); return -1; } memset(&mcast_addr, 0, sizeof(mcast_addr));/*初始化IP多播地址為0*/
mcast_addr.sin_family = AF_INET; /*設置協議族類行為AF*/ mcast_addr.sin_addr.s_addr = inet_addr(MCAST_ADDR);/*設置多播IP地址*/ mcast_addr.sin_port = htons(MCAST_PORT); /*設置多播端口*/ /*向多播地址發送數據*/ while(1) { int n = sendto(s, /*套接字描述符*/ MCAST_DATA, /*數據*/ sizeof(MCAST_DATA), /*長度*/ 0, (struct sockaddr*)&mcast_addr, sizeof(mcast_addr)) ; if( n < 0) { perror("sendto()"); return -2; }
sleep(MCAST_INTERVAL); /*等待一段時間*/ } return 0; }
客戶端:
多播組的IP地址為224.0.0.100,端口為8888,當客戶端接收到多播的數據后將打印出來。
客戶端只有在加入多播組后才能接受多播組的數據,因此多播客戶端在接收多播組的數據之前需要先加入多播組,當接收完畢后要退出多播組。
/* *broadcast_client.c - 多播的客戶端 */ #define MCAST_PORT 8888; #define MCAST_ADDR "224.0.0.100" /*一個局部連接多播地址,路由器不進行轉發*/ #define MCAST_INTERVAL 5 /*發送間隔時間*/ #define BUFF_SIZE 256 /*接收緩沖區大小*/ int main(int argc, char*argv[]) { int s; /*套接字文件描述符*/ struct sockaddr_in local_addr; /*本地地址*/ int err = -1; s = socket(AF_INET, SOCK_DGRAM, 0); /*建立套接字*/ if (s == -1) { perror("socket()"); return -1; } /*初始化地址*/ memset(&local_addr, 0, sizeof(local_addr)); local_addr.sin_family = AF_INET; local_addr.sin_addr.s_addr = htonl(INADDR_ANY); local_addr.sin_port = htons(MCAST_PORT); /*綁定socket*/ err = bind(s,(struct sockaddr*)&local_addr, sizeof(local_addr)) ; if(err < 0) { perror("bind()"); return -2; } /*設置回環許可*/ int loop = 1; err = setsockopt(s,IPPROTO_IP, IP_MULTICAST_LOOP,&loop, sizeof(loop)); if(err < 0) { perror("setsockopt():IP_MULTICAST_LOOP"); return -3; } struct ip_mreq mreq;
/*加入多播組*/ mreq.imr_multiaddr.s_addr = inet_addr(MCAST_ADDR); /*多播地址*/ mreq.imr_interface.s_addr = htonl(INADDR_ANY); /*網絡接口為默認*/ /*將本機加入多播組*/ err = setsockopt(s, IPPROTO_IP, IP_ADD_MEMBERSHIP,&mreq, sizeof(mreq));//ASM模型 if (err < 0) { perror("setsockopt():IP_ADD_MEMBERSHIP"); return -4; } int times = 0; int addr_len = 0; char buff[BUFF_SIZE]; int n = 0; /*循環接收多播組的消息,5次后退出*/ for(times = 0;times<5;times++) { addr_len = sizeof(local_addr); memset(buff, 0, BUFF_SIZE); /*清空接收緩沖區*/ /*接收數據*/ n = recvfrom(s, buff, BUFF_SIZE, 0,(struct sockaddr*)&local_addr,&addr_len); if( n== -1) { perror("recvfrom()"); } /*打印信息*/ printf("Recv %dst message from server:%s\n", times, buff); sleep(MCAST_INTERVAL); } /*退出多播組*/ err = setsockopt(s, IPPROTO_IP, IP_DROP_MEMBERSHIP,&mreq, sizeof(mreq)); close(s); return 0; }