linux udp 單播組播廣播實現


 

http://blog.csdn.net/zuokong/article/details/7548008

多播廣播是用於建立分步式系統:例如網絡游戲、ICQ聊天構建、遠程視頻會議系統的重要工具。使用多播廣播的程序和UDP向單個介紹方發送信息的程序相似。區別在於多播廣播程序使用特殊的多播IP地址。

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)

  例如本地計算機的的IP地址是:127.0.0.1二它的多播地址是: 224.0.0.1。這是由RCF 1390定義的。為發送IP多播數據,發送者需要確定一個合適的多播地址,這個地址代表一個組。IPv4多播地址采用DIP地址確定多播的組。在Internet中,多播地址范圍是從224.0.0.0234.255.255.255。其中比較重要的地址有:

224.0.0.1 網段中所有支持多播的主機

224.0.0.2 網段中所有支持多播的路由器

224.0.0.4 網段中所有的DVMRP路由器

224.0.0.5 所有的OSPF路由器

224.0.0.6 所有的OSPF指派路由器

224.0.0.9 所有RIPv2路由器

IPv6地址空間中有1/256的地址空間分配給多播地址。一個FF11111111)值標識該地址是多播地址。標識段高三位始終設置為0並保留。第四位T標識設置為0時表示一個永久分配的多播地址。T標識設置為1時,表示非永久分配的多播地址,這種地址作為一個臨時的多播地址。

  在默認狀態下,大多Linux發行版本關閉的對多播IP的支持。為了在Linux系統使用多播套接口,需要從新配置和編譯Linux內核。下面看一下配置步驟:

1.cd /usr/src/linux

2.make menuconfig

3.選擇網絡選項

4.選中IPEnable Multicasting IP一項

5.保存並從menuconfig 退出

6.運行:make depmake cleanmake bzlmage

7.cpvmlinuzvdimLz_good

8.cparch/i386/boot/zImagevmlinzz

9.cdetc

10.編輯liloconf,加入針對/vmlinuz_good的內核新選項

11.運行li1o

Linux內核編譯后,以超級用戶身份運行命令:router add –net 224.0.0.0 netmask 224.0.0.0 dev lo

  核實命令是否加入系統,運行命令:

#route –eKernel IP      routing table

Destination     gatewary   Genmask       Flags  MSS       Window irtt Iface

10.0.0.0        *         255.255.255.0  U          0     0      0        eth0

127.0.0.0       *         255.0.0.0      U     0          0      0    lo

BASE_ADDRESS>MC *         240.0.0.0      U     0          0      0     lo

Default         10.0.0.1   0.0.0.0       UG    0          0      0     eth0

  其中出現多播地址: 224.0.0.1。就表示配置成功了

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

多播的概念

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

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

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

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

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

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

組播的缺點:

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

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

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

廣域網的多播

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

多播的編程

多播的程序設計使用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允許回送。

選項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時,選擇的是默認組播接口。

選項IP_DROP_MEMBERSHIP

選項IP_DROP_MEMBERSHIP用於從一個廣播組中退出。例如:

struct ip_mreq mreq;

setsockopt(s,IPPROTP_IP,IP_DROP_MEMBERSHIP,&mreq,sizeof(sreq));

其中mreq包含了在IP_ADD_MEMBERSHIP中相同的值。

多播程序設計的框架

要進行多播的編程,需要遵從一定的編程框架,其基本順序如圖11.6所示。

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

(1)建立一個socket。

(2)然后設置多播的參數,例如超時時間TTL、本地回環許可LOOP等。

(3)加入多播組。

(4)發送和接收數據。

(5)從多播組離開。

內核中的多播

多播的內核結構

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

 選項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()。

 

 選項IP_DROP_MEMBERSHIP的內核處理過程

(1)將用戶數據復制入內核。

(2)查找IP地址對應的網絡接口。

(3)查找多播列表中是否已經存在多播地址。

(4)將此多播地址從源地址中取出。

(5)將此地址結構從多播列表中取出。

(6)返回處理值。

一個多播例子的服務器端

下面是一個多播服務器的例子。多播服務器的程序設計很簡單,建立一個數據包套接字,選定多播的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;

}

 

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

廣播的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)。

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

廣播與單播的比較

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

例如在一個以太網上有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數據報,發送的目的IP為192.168.1.151,端口為 80,目的MAC地址為00:00:00:00:00:02。此數據經過UDP層、IP層,到達數據鏈路層,數據在整個以太網上傳播,在此層中其他主機會 判斷目的MAC地址。主機C的MAC地址為00:00:00:00:00:03,與目的MAC地址00:00:00:00:00:02不匹配,數據鏈路層 不會進行處理,直接丟棄此數據。

 

單播的以太網示意圖

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

廣播的示意圖如圖11.4所示,主機A向整個網絡發送廣播數據,發送的目的IP為192.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層,到達接收數據的應用程序。

 

廣播的示例

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

1.廣播例子簡介

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

                                                                                                    

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 第49行select偵聽是否有數據到來。

q 第50行查看select的返回值。

q 第52行select發生錯誤。

q 第55行select超時。

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 第38~42行設置可廣播地址,因為默認情況下是不可廣播的。

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

q 第50~55行發送服務器請求到整個局域網上。

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

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

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

q 第65行查看select的返回值。

q 第67行select發生錯誤。

q 第70行select超時。

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

q 第65行接收數據。

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM