1. 程式人生 > >多播--概念和程式設計

多播--概念和程式設計

 

11.3  多播

單播用於兩個主機之間的端對端通訊,廣播用於一個主機對整個區域網上所有主機上的資料通訊。單播和廣播是兩個極端,要麼對一個主機進行通訊,要麼對整個區域網上的主機進行通訊。實際情況下,經常需要對一組特定的主機進行通訊,而不是整個區域網上的所有主機,這就是多播的用途。

11.3.1  多播的概念

多播,也稱為"組播",將網路中同一業務型別主機進行了邏輯上的分組,進行資料收發的時候其資料僅僅在同一分組中進行,其他的主機沒有加入此分組不能收發對應的        資料。

在廣域網上廣播的時候,其中的交換機和路由器只向需要獲取資料的主機複製並轉發資料。主機可以向路由器請求加入或退出某個組,網路中的路由器和交換機有選擇地複製並傳輸資料,將資料僅僅傳輸給組內的主機。多播的這種功能,可以一次將資料傳送到多個主機,又能保證不影響其他不需要(未加入組)的主機的其他通訊。

相對於傳統的一對一的單播,多播具有如下的優點:

具有同種業務的主機加入同一資料流,共享同一通道,節省了頻寬和伺服器的優點,具有廣播的優點而又沒有廣播所需要的頻寬。

伺服器的總頻寬不受客戶端頻寬的限制。由於組播協議由接收者的需求來確定是否進行資料流的轉發,所以伺服器端的頻寬是常量,與客戶端的數量無關。

與單播一樣,多播是允許在廣域網即Internet上進行傳輸的,而廣播僅僅在同一區域網上才能進行。

組播的缺點:

多播與單播相比沒有糾錯機制,當發生錯誤的時候難以彌補,但是可以在應用層來實現此種功能。

多播的網路支援存在缺陷,需要路由器及網路協議棧的支援。

多播的應用主要有網上視訊、網上會議等。

11.3.2  廣域網的多播

多播的地址是特定的,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,可限制多播範圍。

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所示。

(點選檢視大圖)圖11.6  多播的程式設計流程

多播程式框架主要包含套接字初始化、設定多播超時時間、加入多播組、傳送資料、接收資料以及從多播組中離開幾個方面。其步驟如下:

(1)建立一個socket。

(2)然後設定多播的引數,例如超時時間TTL、本地迴環許可LOOP等。

(3)加入多播組。

(4)傳送和接收資料。

(5)從多播組離開。

11.3.4  核心中的多播

Linux核心中的多播是利用結構struct ip_mc_socklist來將多播的各個方面連線起來的,其示意圖如圖11.7所示。

圖11.7  多播的核心結構
  1. struct inet_sock { 
  2.     ... 
  3.     __u8 mc_ttl; /*多播TTL*/ 
  4.     ... 
  5.     __u8 ... 
  6.                         mc_loop:1; /*多播迴環設定*/ 
  7.     int mc_index; /*多播裝置序號*/ 
  8.     __be32 mc_addr; /*多播地址*/ 
  9.     struct ip_mc_socklist *mc_list; /*多播群陣列*/ 
  10.     ... 
  11. }

結構成員mc_ttl用於控制多播的TTL;

結構成員mc_loop表示是否迴環有效,用於控制多播資料的本地傳送;

結構成員mc_index用於表示網路裝置的序號;

結構成員mc_addr用於儲存多播的地址;

結構成員mc_list用於儲存多播的群組。

1.結構ip_mc_socklist

結構成員mc_list的原型為struct ip_mc_socklist,定義如下:

  1. struct ip_mc_socklist 
  2.     struct ip_mc_socklist *next; 
  3.     struct ip_mreqn multi; 
  4.     unsigned int sfmode; /*MCAST_{INCLUDE,EXCLUDE}*/ 
  5.     struct ip_sf_socklist *sflist; 
  6. }

 

成員引數next指向連結串列的下一個節點。

成員引數multi表示組資訊,即在哪一個本地介面上,加入到哪一個多播組。

成員引數sfmode是過濾模式,取值為 MCAST_INCLUDE或MCAST_EXCLUDE,分別表示只接收sflist所列出的那些源的多播資料報,和不接收sflist所列出的那些源的多播資料報。

成員引數sflist是源列表。

2.結構ip_mreqn

multi成員的原型為結構struct ip_mreqn,定義如下:

  1. struct ip_mreqn 
  2.     struct in_addr imr_multiaddr; /*多播組的IP地址*/ 
  3.     struct in_addr imr_address; /*本地址網路介面的IP地址*/ 
  4.     int imr_ifindex; /*網路介面序號*/ 
  5. }

 

該結構體的兩個成員分別用於指定所加入的多播組的組IP地址,和所要加入組的那個本地介面的IP地址。該命令字沒有源過濾的功能,它相當於實現IGMPv1的多播加入服務介面。

3.結構ip_sf_socklist

成員sflist的原型為結構struct ip_sf_socklist,定義如下:

  1. struct ip_sf_socklist 
  2.     unsigned int sl_max; /*當前sl_addr陣列的最大可容納量*/ 
  3.     unsigned int sl_count; /*源地址列表中源地址的數量*/ 
  4.     __u32 sl_addr[0]; /*源地址列表*/ 
  5. }

 

成員引數sl_addr表示是源地址列表;

成員引數sl_count表示是源地址列表中源地址的數量;

成員引數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; 
}