單 播用於兩個主機之間的端對端通信,廣播用於一個主機對整個局域網上所有主機上的數據通信。單播和廣播是兩個極端,要麼對一個主機進行通信,要麼對整個局域 網上的主機進行通信。實際情況下,經常需要對一組特定的主機進行通信,而不是整個局域網上的所有主機,這就是多播的用途。
11.3.1 多播的概念
多播,也稱爲“組播”,將網絡中同一業務類型主機進行了邏輯上的分組,進行數據收發的時候其數據僅僅在同一分組中進行,其他的主機沒有加入此分組不能收發對應的 數據。
在 廣域網上廣播的時候,其中的交換機和路由器只向需要獲取數據的主機複製並轉發數據。主機可以向路由器請求加入或退出某個組,網絡中的路由器和交換機有選擇 地複製並傳輸數據,將數據僅僅傳輸給組內的主機。多播的這種功能,可以一次將數據發送到多個主機,又能保證不影響其他不需要(未加入組)的主機的其他通 信。
相對於傳統的一對一的單播,多播具有如下的優點:
q 具有同種業務的主機加入同一數據流,共享同一通道,節省了帶寬和服務器的優點,具有廣播的優點而又沒有廣播所需要的帶寬。
q 服務器的總帶寬不受客戶端帶寬的限制。由於組播協議由接收者的需求來確定是否進行數據流的轉發,所以服務器端的帶寬是常量,與客戶端的數量無關。
q 與單播一樣,多播是允許在廣域網即Internet上進行傳輸的,而廣播僅僅在同一局域網上才能進行。
組播的缺點:
q 多播與單播相比沒有糾錯機制,當發生錯誤的時候難以彌補,但是可以在應用層來實現此種功能。
q 多播的網絡支持存在缺陷,需要路由器及網絡協議棧的支持。
多播的應用主要有網上視頻、網上會議等。
11.3.2 廣域網的多播
多播的地址是特定的,D類地址用於多播。D類IP地址就是多播IP地址,即224.0.0.0至239.255.255.255之間的IP地址,並被劃分爲局部連接多播地址、預留多播地址和管理權限多播地址3類:
q 局部多播地址:在224.0.0.0~224.0.0.255之間,這是爲路由協議和其他用途保留的地址,路由器並不轉發屬於此範圍的IP包。
q 預留多播地址:在224.0.1.0~238.255.255.255之間,可用於全球範圍(如Internet)或網絡協議。
q 管理權限多播地址:在239.0.0.0~239.255.255.255之間,可供組織內部使用,類似於私有IP地址,不能用於Internet,可限制多播範圍。
11.3.3 多播的編程
多播的程序設計使用setsockopt()函數和getsockopt()函數來實現,組播的選項是IP層的,其選項值和含義參見11.5所示。
表11.5 多播相關的選項
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
加入或者退出一個組播組,通過選項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));
使用IP_ADD_MEMBERSHIP選項每次只能加入一個網絡接口的IP地址到多播組,但並不是一個多播組僅允許一個主機IP地址加入,可以多次調用IP_ADD_MEMBERSHIP選項來實現多個IP地址加入同一個廣播組,或者同一個IP地址加入多個廣播組。當imr_
interface爲INADDR_ANY時,選擇的是默認組播接口。
4.選項IP_DROP_MEMBERSHIP
選項IP_DROP_MEMBERSHIP用於從一個廣播組中退出。例如:
struct ip_mreq mreq;
setsockopt(s,IPPROTP_IP,IP_DROP_MEMBERSHIP,&mreq,sizeof(sreq));
其中mreq包含了在IP_ADD_MEMBERSHIP中相同的值。
5.多播程序設計的框架
要進行多播的編程,需要遵從一定的編程框架,其基本順序如圖11.6所示。
多播程序框架主要包含套接字初始化、設置多播超時時間、加入多播組、發送數據、接收數據以及從多播組中離開幾個方面。其步驟如下:
(1)建立一個socket。
(2)然後設置多播的參數,例如超時時間TTL、本地迴環許可LOOP等。
(3)加入多播組。
(4)發送和接收數據。
(5)從多播組離開。
11.3.4 內核中的多播
Linux內核中的多播是利用結構struct ip_mc_socklist來將多播的各個方面連接起來的,其示意圖如圖11.7所示。
圖
11.7 多播的內核結構
struct inet_sock {
...
__u8 mc_ttl; /*多播TTL*/
...
__u8 ...
mc_loop:1; /*多播迴環設置*/
int mc_index; /*多播設備序號*/
__be32 mc_addr; /*多播地址*/
struct ip_mc_socklist *mc_list; /*多播羣數組*/
...
};
q 結構成員mc_ttl用於控制多播的TTL;
q 結構成員mc_loop表示是否迴環有效,用於控制多播數據的本地發送;
q 結構成員mc_index用於表示網絡設備的序號;
q 結構成員mc_addr用於保存多播的地址;
q 結構成員mc_list用於保存多播的羣組。
1.結構ip_mc_socklist
結構成員mc_list的原型爲struct
ip_mc_socklist,定義如下:
struct ip_mc_socklist
{
struct ip_mc_socklist *next;
struct ip_mreqn multi;
unsigned int sfmode; /*MCAST_{INCLUDE,EXCLUDE}*/
struct ip_sf_socklist *sflist;
};
q 成員參數next指向鏈表的下一個節點。
q 成員參數multi表示組信息,即在哪一個本地接口上,加入到哪一個多播組。
q 成員參數sfmode是過濾模式,取值爲 MCAST_INCLUDE或MCAST_EXCLUDE,分別表示只接收sflist所列出的那些源的多播數據報,和不接收sflist所列出的那些源的多播數據報。
q 成員參數sflist是源列表。
2.結構ip_mreqn
multi成員的原型爲結構struct ip_mreqn,定義如下:
struct ip_mreqn
{
struct in_addr imr_multiaddr; /*多播組的IP地址*/
struct in_addr imr_address; /*本地址網絡接口的IP地址*/
int imr_ifindex; /*網絡接口序號*/
};
該結構體的兩個成員分別用於指定所加入的多播組的組IP地址,和所要加入組的那個本地接口的IP地址。該命令字沒有源過濾的功能,它相當於實現IGMPv1的多播加入服務接口。
3.結構ip_sf_socklist
成員sflist的原型爲結構struct
ip_sf_socklist,定義如下:
struct ip_sf_socklist
{
unsigned int sl_max; /*當前sl_addr數組的最大可容納量*/
unsigned int sl_count; /*源地址列表中源地址的數量*/
__u32 sl_addr[0]; /*源地址列表*/
};
q 成員參數sl_addr表示是源地址列表;
q 成員參數sl_count表示是源地址列表中源地址的數量;
q 成員參數sl_max表示是當前sl_addr數組的最大可容納量(不確定)。
4.選項IP_ADD_MEMBERSHIP
選項IP_ADD_MEMBERSHIP用於把一個本地的IP地址加入到一個多播組,在內核中其處理過程如圖11.8所示,在應用層調用函數setsockopt()函數的選項IP_ADD_MEMBE-
RSHIP後,內核的處理過程如下,主要調用了函數ip_mc_join_group()。
圖
11.8 選項IP_ADD_MEMBERSHIP的內核處理過程
(1)將用戶數據複製如內核。
(2)判斷廣播IP地址是否合法。
(3)查找IP地址對應的網絡接口。
(4)查找多播列表中是否已經存在多播地址。
(5)將此多播地址加入列表。
(6)返回處理值。
5.選項IP_DROP_MEMBERSHIP
選項IP_DROP_MEMBERSHIP用於把一個本地的IP地址從一個多播組中取出,在內核中其處理過程如圖11.9所示,在應用層調用setsockopt()函數的選項IP_DROP_
MEMBERSHIP後,內核的處理過程如下,主要調用了函數ip_mc_leave_group()。
圖
11.9 選項IP_DROP_MEMBERSHIP的內核處理過程
(1)將用戶數據複製入內核。
(2)查找IP地址對應的網絡接口。
(3)查找多播列表中是否已經存在多播地址。
(4)將此多播地址從源地址中取出。
(5)將此地址結構從多播列表中取出。
(6)返回處理值。
11.3.5 一個多播例子的服務器端
下面是一個多播服務器的例子。多播服務器的程序設計很簡單,建立一個數據包套接字,選定多播的IP地址和端口,直接向此多播地址發送數據就可以了。多播服務器的程序設計,不需要服務器加入多播組,可以直接向某個多播組發送數據。
下面的例子持續向多播IP地址"224.0.0.88"的8888端口發送數據"BROADCAST
TEST DATA",每發送一次間隔5s。
/*
*broadcast_server.c - 多播服務程序
*/
#define MCAST_PORT 8888;
#define MCAST_ADDR "224.0.0.88"/ /*一個局部連接多播地址,路由器不進行轉發*/
#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;
}
11.3.6 一個多播例子的客戶端
多播組的IP地址爲224.0.0.88,端口爲8888,當客戶端接收到多播的數據後將打印 出來。
客戶端只有在加入多播組後才能接受多播組的數據,因此多播客戶端在接收多播組的數據之前需要先加入多播組,當接收完畢後要退出多播組。
/*
*broadcast_client.c - 多播的客戶端
*/
#define MCAST_PORT 8888;
#define MCAST_ADDR "224.0.0.88" /*一個局部連接多播地址,路由器不進行轉發*/
#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));
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;
}
|