linux udp 單播 組播 廣播實現

轉載 http://blog.sina.com.cn/s/blog_4fc7368a0100okbq.html


1、組播和廣播需要在局域網內才能實現,另外得查看linux系統是否支持多播和廣播:ifconfig

     UP BROADCAST MULTICAST MTU:1500  躍點數:1
     說明該網卡支持
2、發送多播包的主機需要設置網關,否則運行sendto()會出現"network is unreachable",網卡可以隨便設置,但是一定要設。還要添加路由240.0.0.0,即:
      route add -net 224.0.0.0 netmask 240.0.0.0 dev eth0
      route add default gw "192.168.40.1 " dev eth0
3 、出現:“setsockopt:No such device”。的提示,說明多播IP設置出現問題,系統所需要的uint32_t格式的網絡地址的開頭不是1110,檢驗通不過。解決辦法:在把地址字 符串"*.*.*.*"轉化爲uint32_t時採用htonl(inet_network(“*.*.*.*”))或者inet_aton函 數,inet_aton(GRUPO, &srv.sin_addr)

另外有文章:http://unix-cd.com/unixcd12/article_5577.html

11.3 多 播
 

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

11.3.1  多播的概念

多播,也稱爲“組播”,將網絡中同一業務類型主機進行了邏輯上的分組,進行數據收發的時候其數據僅僅在同一分組中進行,其他的主機沒有加入此分組不能收發對應的        數據。

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

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

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

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

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

組播的缺點:

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

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

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

11.3.2  廣域網的多播

多播的地址是特定的,D類地址用於多播。DIP地址就是多播IP地址,即224.0.0.0239.255.255.255之間的IP地址,並被劃分爲局部連接多播地址、預留多播地址和管理權限多播地址3類:

q  局部多播地址:在224.0.0.0224.0.0.255之間,這是爲路由協議和其他用途保留的地址,路由器並不轉發屬於此範圍的IP包。

q  預留多播地址:在224.0.1.0238.255.255.255之間,可用於全球範圍(如Internet)或網絡協議。

q  管理權限多播地址:在239.0.0.0239.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,範圍爲0255之間的任何值,例如:

 

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_MEMBERSHIPIP_DROP_MEMBERSHIP

加入或者退出一個組播組,通過選項IP_ADD_MEMBERSHIPIP_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));

 

linux <wbr>udp <wbr>單播 <wbr>組播 <wbr>廣播實現

 

 

 

 

使用IP_ADD_MEMBERSHIP選項每次只能加入一個網絡接口的IP地址到多播組,但並不是一個多播組僅允許一個主機IP地址加入,可以多次調用IP_ADD_MEMBERSHIP選項來實現多個IP地址加入同一個廣播組,或者同一個IP地址加入多個廣播組。當imr_ interfaceINADDR_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所示。

 

linux <wbr>udp <wbr>單播 <wbr>組播 <wbr>廣播實現

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_INCLUDEMCAST_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()

 

linux <wbr>udp <wbr>單播 <wbr>組播 <wbr>廣播實現

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()

 

linux <wbr>udp <wbr>單播 <wbr>組播 <wbr>廣播實現

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;

}


11.2 廣 播
 

前面介紹的TCP/IP知識都是基於單播,即一對一的方式,本節介紹一對多的廣播方式。廣播是由一個主機發向一個網絡上所有主機的操作方式。例如在一個局域網內進行廣播,同一子網內的所有主機都可以收到此廣播發送的數據。

11.2.1  廣播的IP地址

要使用廣播,需要了解IPv4特定的廣播地址。IP地址分爲左邊的網絡ID部分以及右邊的主機ID部分。廣播地址所用的IP地址將表示主機ID的位全部設置爲1。網卡正確配置以後,可以用下面的命令來顯示所選用接口的廣播地址。

 

# ifconfig eth0

eth0 Link encap:Ethernet HWaddr 00:A0:4B:06:F4:8D

      inet addr:192.168.0.1 Bcast:192.168.0.255 Mask:255.255.255.0

      UP BROADCAST RUNNING PROMISC MULTICAST MTU:1500 Metric:1

      RX packets:1955 errors:0 dropped:0 overruns:0 frame:31

      TX packets:1064 errors:0 dropped:0 overruns:0 carrier:0

      collisions:0 txqueuelen:100

      Interrupt:9 Baseaddress:0xe400

 

第二行輸出信息說明eth0網絡接口的廣播地址爲192.168.0.255。這個廣播IP地址的前3個字節爲網絡ID,即192.168.0。這個地址的主機ID部分爲255,值255是表示主機ID全爲1的十進制數。

廣播地址255.255.255.255是一種特殊的廣播地址,這種格式的廣播地址是向全世界進行廣播,但是卻有更多的限制。一般情況下,這種廣播類型不會被路由器路由,而一個更爲特殊的廣播地址,例如192.168.0.255也許會被路由,這取決於路由器的配置。

通用的廣播地址在不同的環境中的含義不同。例如,IP地址255.255.255.255,一些UNIX系統將其解釋爲在主機的所有網絡接口上進行廣播,而有的UNIX內核只會選擇其中的一個接口進行廣播。當一個主機有多個網卡時,這就會成爲一個問題。

如果必須向每個網絡接口廣播,程序在廣播之前應執行下面的步驟。

1)確定下一個或第一個接口名字。

2)確定接口的廣播地址。

3)使用這個廣播地址進行廣播。

4)對於系統中其餘的活動網絡接口重複執行步驟(1)~步驟(3)。

在執行完這些步驟以後,就可以認爲已經對每一個接口進行廣播。

11.2.2  廣播與單播的比較

廣播和單播的處理過程是不同的,單播的數據只是收發數據的特定主機進行處理,而廣播的數據整個局域網都進行處理。

例如在一個以太網上有3個主機,主機的配置如表11.4所示。

11.4  某局域網中主機的配置情況

    

A

B

C

IP地址

192.168.1.150

192.168.1.151

192.168.1.158

MAC地址

00:00:00:00:00:01

00:00:00:00:00:02

00:00:00:00:00:03

單播的示意圖如圖11.3所示,主機A向主機B發送UDP數據報,發送的目的IP192.168.1.151,端口爲80,目的MAC地址爲00:00:00:00:00:02。此數據經過UDP層、IP層,到達數據鏈路層,數據在整個以太網上傳播,在此層中其他主機會判斷目的MAC地址。主機CMAC地址爲00:00:00:00:00:03,與目的MAC地址00:00:00:00:00:02不匹配,數據鏈路層不會進行處理,直接丟棄此數據。

 

linux <wbr>udp <wbr>單播 <wbr>組播 <wbr>廣播實現

11.3  單播的以太網示意圖

主機BMAC地址爲00:00:00:00:00:02,與目的MAC地址00:00:00:00:00:02一致,此數據會經過IP層、UDP層,到達接收數據的應用程序。

廣播的示意圖如圖11.4所示,主機A向整個網絡發送廣播數據,發送的目的IP192.168.1.255,端口爲80,目的MAC地址爲FF:FF:FF:FF:FF:FF。此數據經過UDP層、IP層,到達數據鏈路層,數據在整個以太網上傳播,在此層中其他主機會判斷目的MAC地址。由於目的MAC地址爲FF:FF:FF:FF:FF:FF,主機C和主機B會忽略MAC地址的比較(當然,如果協議棧不支持廣播,則仍然比較MAC地址),處理接收到的數據。

主機B和主機C的處理過程一致,此數據會經過IP層、UDP層,到達接收數據的應用程序。

 

linux <wbr>udp <wbr>單播 <wbr>組播 <wbr>廣播實現

11.4  廣播的以太網示意圖

11.2.3  廣播的示例

本節中是一個服務器地址發現的代碼,假設服務器爲A,客戶端爲B。客戶端在某個局域網啓動的時候,不知道本局域網內是否有適合的服務器存在,它會使用廣播在本局域網內發送特定協議的請求,如果有服務器響應了這種請求,則使用響應請求的IP地址進行連接,這是一種服務器/客戶端自動發現的常用方法。

1.廣播例子簡介

如圖11.5所示爲使用廣播的方法發現局域網上服務器的IP地址。服務器在局域網上偵聽,當有數據到來的時候,判斷數據是否有關鍵字IP_FOUND,當存在此關鍵字的時候,發送IP_FOUND_ACK到客戶端。客戶端判斷是否有服務器的響應IP_FOUND請求,並判斷響應字符串是否包含IP_FOUND_ACK來確定局域網上是否存在服務器,如果有服務器的響應,則根據recvfrom()函數的from變量可以獲得服務器的IP地址。

 

linux <wbr>udp <wbr>單播 <wbr>組播 <wbr>廣播實現

11.5  利用廣播進行服務器IP地址的發現

2.廣播的服務器端代碼

服務器的代碼如下,服務器等待客戶端向某個端口發送數據,如果數據的格式正確,則服務器會向客戶端發送響應數據。

 

01 

02      #define IP_FOUND "IP_FOUND"                  /*IP發現命令*/

03      #define IP_FOUND_ACK "IP_FOUND_ACK"     /*IP發現應答命令*/

04      void    HandleIPFound(void*arg)

05      {

06      #define BUFFER_LEN 32

07          int ret = -1;

08          SOCKET sock = -1;

09          struct sockaddr_in local_addr;          /*本地地址*/

10          struct sockaddr_in from_addr;           /*客戶端地址*/

11      int from_len;

12          int count = -1;

13          fd_set readfd;

14          char buff[BUFFER_LEN];

15          struct timeval timeout;

16          timeout.tv_sec = 2;                     /*超時時間2s*/

17          timeout.tv_usec = 0;

18     

19          DBGPRINT("==>HandleIPFound\n");

20         

21           sock = socket(AF_INET, SOCK_DGRAM, 0);  /*建立數據報套接字*/

22          if( sock < 0 )

23          {

24              DBGPRINT("HandleIPFound: socket init error\n");

25              return;

26          }

27         

28          /*數據清零*/

29          memset((void*)&local_addr, 0, sizeof(struct sockaddr_in));
                                                        /
*清空內存內容*/

30          local_addr.sin_family = AF_INET;            /*協議族*/

31           local_addr.sin_addr.s_addr = htonl(INADDR_ANY);/*本地地址*/

32          local_addr.sin_port = htons(MCAST_PORT);        /*偵聽端口*/

33          /*綁定*/

34          ret = bind(sock, (struct sockaddr*)&local_addr, sizeof(local_
            addr));

35          if(ret != 0)

36          {

37              DBGPRINT("HandleIPFound:bind error\n");

38              return;

39          }

40     

41           /*主處理過程*/

42          while(1)

43          {

44              /*文件描述符集合清零*/

45              FD_ZERO(&readfd);

46              /*將套接字文件描述符加入讀集合*/

47              FD_SET(sock, &readfd);

48              /*select偵聽是否有數據到來*/

49              ret = selectsocket(sock+1, &readfd, NULL, NULL, &timeout);

50              switch(ret)

51               {

52                  case -1:

53                  /*發生錯誤*/

54                      break;

55                  case 0:

56                      /*超時*/

57                      //超時所要執行的代碼

58                     

59                      break;

60                  default:

61                   /*有數據到來*/

62                      if( FD_ISSET( sock, &readfd ) )

63                      {

64                              /*接收數據*/

65                          count = recvfrom( sock, buff, BUFFER_LEN, 0,
                            ( struct sockaddr
*) &from_addr, &from_len );

66                          DBGPRINT( "Recv msg is %s\n", buff );

67                          if( strstr( buff, IP_FOUND ) )
                                /
*判斷是否吻合*/

68                          {

69                              /*將應答數據複製進去*/

70                              memcpy(buff, IP_FOUND_ACK,strlen(IP_
                                FOUND_ACK)+1);

71                               /*發送給客戶端*/

72                              count = sendto( sock, buff, strlen( buff ),
                                0, ( struct sockaddr
*) &from_addr, from_
                                len );

73                          }

74                      }

75              }

76          }

77          PRINT("<==HandleIPFound\n");

78

79          return;

80      }

 

服務器端分爲如下步驟:

q  16行和第17行定義了服務器等待的超時時間,爲2s

q  29行將地址結構清零。

q  30行定義地址協議族爲AF_INET

q  31行設置IP地址爲任意本地地址。

q  32行設置偵聽的端口。

q  34行將本地的地址綁定到一個套接字文件描述符上。

q  42行開始爲主處理過程,使用select函數,按照2s的超時時間偵聽是否有數據到來。

q  45行文件描述符集合清零。

q  47行將套接字文件描述符加入讀集合。

q  49select偵聽是否有數據到來。

q  50行查看select的返回值。

q  52select發生錯誤。

q  55select超時。

q  60行有可讀的數據到來。

q  65行接收數據。

q  67行查看接收到的數據是否匹配。

q  70行復制響應數據。

q  72行發送響應數據到客戶端。

3.廣播的客戶端代碼

廣播的客戶端函數代碼如下,客戶端向服務器端發送命令IP_FOUND,並等待服務器端的回覆,如果有服務器回覆,則向服務器發送IP_FOUND_ACK,否則發送10遍後退出。

 

01      #define IP_FOUND "IP_FOUND"                  /*IP發現命令*/

02      #define IP_FOUND_ACK "IP_FOUND_ACK"     /*IP發現應答命令*/

03      #define IFNAME "eth0"

04      void    IPFound(void*arg)

05      {

06      #define BUFFER_LEN 32

07          int ret = -1;

08          SOCKET sock = -1;

09          int so_broadcast = 1;

10          struct ifreq ifr;          

11          struct sockaddr_in broadcast_addr;      /*本地地址*/

12          struct sockaddr_in from_addr;           /*服務器端地址*/

13          int from_len;

14          int count = -1;

15          fd_set readfd;

16          char buff[BUFFER_LEN];

17          struct timeval timeout;

18          timeout.tv_sec = 2;                 /*超時時間2s*/

19          timeout.tv_usec = 0;

20     

21         

22           sock = socket(AF_INET, SOCK_DGRAM, 0);/*建立數據報套接字*/

23          if( sock < 0 )

24          {

25              DBGPRINT("HandleIPFound: socket init error\n");

26              return;

27          }

28          /*將需要使用的網絡接口字符串名字複製到結構中*/

29          strcpy(ifr.ifr_name,IFNAME,strlen(IFNAME));

30          /*發送命令,獲取網絡接口的廣播地址*/

31          if(ioctl(sock,SIOCGIFBRDADDR,&ifr) == -1)

32              perror("ioctl error"),exit(1);

33          /*將獲得的廣播地址複製給變量broadcast_addr*/

34          memcpy(&broadcast_addr, &ifr.ifr_broadaddr, sizeof(struct
            sockaddr_in ));

35          broadcast_addr.sin_port = htons(MCAST_PORT);/*設置廣播端口*/

36         

37          /*設置套接字文件描述符sock爲可以進行廣播操作*/

38          ret = setsockopt(sock,

39                  SOL_SOCKET,

40                  SO_BROADCAST,

41              &so_broadcast,

42              sizeof so_broadcast);

43             

44           /*主處理過程*/

45          int times = 10;

46          int i = 0;

47          for(i=0;i<times;i++)

48          {

49              /*廣播發送服務器地址請求*/

50              ret = sendto(sock,

51                          IP_FOUND,

52                          strlen(IP_FOUND),

53                          0,

54                          (struct sockaddr*)&broadcast_addr,

55                          sizeof(broadcast_addr));

56              if(ret == -1){

57                  continue;  

58              }

59              /*文件描述符集合清零*/

60              FD_ZERO(&readfd);

61              /*將套接字文件描述符加入讀集合*/

62              FD_SET(sock, &readfd);

63              /*select偵聽是否有數據到來*/

64              ret = selectsocket(sock+1, &readfd, NULL, NULL, &timeout);

65              switch(ret)

66               {

67                  case -1:

68                      /*發生錯誤*/

69                      break;

70                  case 0:

71                      /*超時*/

72                      //超時所要執行的代碼

73                     

74                      break;

75                  default:

76                   /*有數據到來*/

77                      if( FD_ISSET( sock, &readfd ) )

78                      {

79                          /*接收數據*/

80                          count = recvfrom( sock, buff, BUFFER_LEN, 0,
                            ( struct sockaddr
*) &from_addr, &from_len );

81                          DBGPRINT( "Recv msg is %s\n", buff );

82                          if(strstr(buff, IP_FOUND_ACK))/*判斷是否吻合*/

83                          {

84                              printf("found server, IP is %s\n",inet_ntoa
                                (from_addr.sin_addr));

85                          }

86                      break;/*成功獲得服務器地址,退出*/

07                      }

08              }

09          }  

90          return;

91      }

 

客戶端分爲如下步驟:

q  18行和第19行定義了服務器等待的超時時間,爲2s

q  22行建立數據報套接字。

q  29行復制網絡接口名稱。

q  31行獲得與網絡接口名稱對應的廣播地址。

q  34行和第35行設置廣播的地址和端口。

q  3842行設置可廣播地址,因爲默認情況下是不可廣播的。

q  47行開始爲主處理過程,發送多次廣播數據,查看網絡上是否有服務器存在。

q  5055行發送服務器請求到整個局域網上。

q  60行文件描述符集合清零。

q  62行將套接字文件描述符加入讀集合。

q  64select偵聽是否有數據到來。

q  65行查看select的返回值。

q  67select發生錯誤。

q  70select超時。

q  75行有可讀的數據到來。

q  65行接收數據。

q  80行查看接收到的數據是否匹配。

發佈了21 篇原創文章 · 獲贊 4 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章