- 論壇徽章:
- 0
|
初識組播(1)
我們知道在內(nèi)核中用結(jié)構(gòu)體struct net_device標(biāo)識一個網(wǎng)絡(luò)設(shè)備接口,該結(jié)構(gòu)體有一個成員指針ip_ptr,它是留給IPv4協(xié)議用于填充協(xié)議相關(guān)的一些數(shù)據(jù)的。IPv4協(xié)議的模塊將其指向一個結(jié)構(gòu)體struct in_device,該結(jié)構(gòu)體含有很多協(xié)議相關(guān)的數(shù)據(jù),比如配置在這個網(wǎng)絡(luò)設(shè)備接口上的所有的IPv4的地址,該網(wǎng)絡(luò)設(shè)備接口接受的組播地址等,下面是其完整的定義:
struct in_device
{
struct net_device *dev;
atomic_t refcnt;
int dead;
struct in_ifaddr *ifa_list; //IP地址列表
rwlock_t mc_list_lock;
struct ip_mc_list *mc_list; //IP組播過濾列表。
spinlock_t mc_tomb_lock; //下面都是組播相關(guān)數(shù)據(jù)。
struct ip_mc_list *mc_tomb;
unsigned long mr_v1_seen;
unsigned long mr_v2_seen;
unsigned long mr_maxdelay;
unsigned char mr_qrv;
unsigned char mr_gq_running;
unsigned char mr_ifc_count;
struct timer_list mr_gq_timer; /* general query timer */
struct timer_list mr_ifc_timer; /* interface change timer */
struct neigh_parms *arp_parms;
struct ipv4_devconf cnf;
struct rcu_head rcu_head;
};
我們暫時還無法完全理解這個結(jié)構(gòu)體,目前只需關(guān)注的是mc_list成員,這是關(guān)于組播的一個最為關(guān)鍵的數(shù)據(jù)結(jié)構(gòu)。mc_list是一個鏈表,鏈表的一個結(jié)點代表一個組播地址(也就是一個多播組的組號),代表這個網(wǎng)絡(luò)設(shè)備接口已經(jīng)加入了這個組播組,需要接收來自這個組的數(shù)據(jù)報。下面是該節(jié)點的結(jié)構(gòu)體定義:
struct ip_mc_list
{
struct in_device *interface;
unsigned long multiaddr;
struct ip_sf_list *sources;
struct ip_sf_list *tomb;
unsigned int sfmode;
unsigned long sfcount[2];
struct ip_mc_list *next;
struct timer_list timer;
int users;
atomic_t refcnt;
spinlock_t lock;
char tm_running;
char reporter;
char unsolicit_count;
char loaded;
unsigned char gsquery;
unsigned char crcount;
};
同樣,我們把其中的大部分成員留待以后理解。multiaddr就是組播地址,sources和tomb是關(guān)于組播源地址的一個列表,sfmode和sfcount是過濾參數(shù),也就是說,該網(wǎng)絡(luò)設(shè)備接口雖然加入了某個組播組,但對某些主機向該組發(fā)的數(shù)據(jù)報不接收,或者只接收某個主機發(fā)向該組的數(shù)據(jù)報,這就要對組播源進(jìn)行過濾。
關(guān)于D類地址的常識,這里不再介紹,可參考相關(guān)書籍。
下面介紹一個特殊的組播地址224.0.0.1,它標(biāo)識子網(wǎng)中的所有主機,同一個子網(wǎng)內(nèi)具有組播功能的主機都屬于這個組。我們的my_inet模塊在初始化時,myinetdev_event函數(shù)收到網(wǎng)絡(luò)設(shè)備接口啟動(NETDEV_UP)的消息后,調(diào)用myip_mc_up啟動組播功能。
啟動組播功能的第一件事便是把本機加入到這個特殊的組播組IGMP_ALL_HOSTS(即224.0.0.1),調(diào)用myip_mc_inc_group函數(shù)完成加入動作。因為我們的my_inet模塊是作為系統(tǒng)中的第二個IPv4模塊,在系統(tǒng)正常運行后被加載的,所以,網(wǎng)絡(luò)設(shè)備接口早已完成了加入該組播組的操作。my_inet模塊的加入動作是只是簡單地將成員users加1,然后調(diào)用myip_mc_add_src函數(shù)加入組播源過濾。
IGMP_ALL_HOSTS組的sources為NULL,sfmode為MCAST_EXCLUDE(過濾掉sources中列出的所有源),所以結(jié)果是不過濾任何組播源。myip_mc_add_src函數(shù)中,將sfcount[MCAST_EXCLUDE]的值加1,表示新增一個過濾機制。
初識組播(2)
上一篇我們講到,因為my_inet是系統(tǒng)中第二個加載的IPv4模塊,所以網(wǎng)絡(luò)設(shè)備接口早已完成了加入IGMP_ALL_HOST組的操作,my_inet只是簡單增加引用計數(shù)和源過濾計數(shù),下面我們來看看,第一個加載的IPv4模塊(即內(nèi)核原有的TCP/IP協(xié)議棧模塊)是如何把網(wǎng)絡(luò)設(shè)備接口加入IGMP_ALL_HOST組的。
在myip_mc_inc_group函數(shù)中,首先檢查in_device->mc_list列表中已加入的組播組,看本接口是否已經(jīng)加入了IGMP_ALL_HOST組,結(jié)果當(dāng)然是沒有。則,首先創(chuàng)建一個新的結(jié)構(gòu)體struct ip_mc_list *im,初始化其成員值,設(shè)成員multiaddr為組播地址224.0.0.1,sf_mode為MCAST_EXCLUDE,sfcount[MCAST_EXCLUDE]為1,sources為NULL,表示使用一個源過濾機制,該機制不過濾任何組播源。成員loaded為0,表示該組播組尚未被載入(稍后將看到載入的操作)。初始化完成后,將這個新的組播組加入到mc_list鏈表的表頭。
前面講到過,mc_tomb也是in_device的一個成員,也表示一個組播組列表,這個列表中的組應(yīng)該是不活躍的(當(dāng)前不在使用的,具體留待以后分析),新的組加入到mc_list成功后,還要到這個列表中查找,看是否也存在于這個列表中,如果存在,要刪除,因為該組當(dāng)前是活躍的。
最后,調(diào)用myigmp_group_added完成真正的加入組播組的操作,對于IGMP_ALL_HOST這個組來講,該函數(shù)做的事情相對比較少,它檢查loaded成員,發(fā)現(xiàn)為0,則調(diào)用myip_mc_filter_add,加入一個網(wǎng)絡(luò)設(shè)備級的組播地址。也就是說,代表網(wǎng)絡(luò)設(shè)備接口的結(jié)構(gòu)體struct net_device有一個成員mc_list,它是一個鏈表,每個結(jié)點代表一個組播組的mac地址。與in_device的mc_list中的組播IP地址對應(yīng)。loaded為0時,我們要做的事情就是把IP地址224.0.0.1映射成一個mac地址加到net_device的mc_list鏈表中去,然后把loaded置1,該成員的結(jié)點定義如下:
struct dev_mc_list
{
struct dev_mc_list *next;
__u8 dmi_addr[MAX_ADDR_LEN];
unsigned char dmi_addrlen;
int dmi_users;
int dmi_gusers;
};
dmi_addr是mac地址,dmi_addrlen是地址長度,dmi_users是引用計數(shù)。添加完成后,net_device的成員mc_count相應(yīng)的加1。
下面我們來看看組播IP地址是如何被映射成組播mac地址的。一個mac地址總共有6字節(jié),48位,被分成兩段:前3字節(jié)和后3字節(jié),前3字節(jié)用于標(biāo)識網(wǎng)卡的制造廠商,其中第40位(第一字節(jié)的最低位)用于標(biāo)識組播,所以在網(wǎng)卡的mac地址中必須置0,后3字節(jié)是廠商內(nèi)部使用的序列號。一個組播IP地址映射成mac地址的規(guī)則是:前三字節(jié)強制置01:00:5E,后3字節(jié)中,第23位置0,0-22位放入IP地址的0-23位。
加入一個組播組
網(wǎng)絡(luò)中的一臺主機如果希望能夠接收到來自網(wǎng)絡(luò)中其它主機發(fā)往某一個組播組的數(shù)據(jù)報,那么這么主機必須先加入該組播組,然后就可以從組地址接收數(shù)據(jù)包。在廣域網(wǎng)中,還涉及到路由器支持組播路由等,但本文希望以一個最為簡單的例子解釋清楚協(xié)議棧關(guān)于組播的一個最為簡單明了的工作過程,甚至,我們不希望涉及到IGMP包。
我們先從一個組播客戶端的應(yīng)用程序入手來解析組播的工作過程:
#include
#include
#include
#include
#include "my_inet.h"
#include
#define MAXBUF 256
#define PUERTO 5000
#define GRUPO "224.0.1.1"
int main(void)
{
int fd, n, r;
struct sockaddr_in srv, cli;
struct ip_mreq mreq;
char buf[MAXBUF];
memset( &srv, 0, sizeof(struct sockaddr_in) );
memset( &cli, 0, sizeof(struct sockaddr_in) );
memset( &mreq, 0, sizeof(struct ip_mreq) );
srv.sin_family = MY_AF_INET;
srv.sin_port = htons(PUERTO);
if( inet_aton(GRUPO, &srv.sin_addr ) if( bind(fd, (struct sockaddr *)&srv, sizeof(srv)) perror("bind");
return -1;
}
if (inet_aton(GRUPO, &mreq.imr_multiaddr)
{
perror("inet_aton");
return -1;
}
inet_aton( "172.16.48.2", &(mreq.imr_interface) );
if( setsockopt(fd, SOL_IP, IP_ADD_MEMBERSHIP, &mreq,sizeof(mreq)) )
{
perror("setsockopt");
return -1;
}
n = sizeof(cli);
while(1)
{
if( (r = recvfrom(fd, buf, MAXBUF, 0, (struct sockaddr *)&cli, (socklen_t*)&n)) {
perror("recvfrom");
}
else
{
buf[r] = 0;
fprintf(stdout, "Mensaje desde %s: %s", inet_ntoa(cli.sin_addr), buf);
}
}
}
這是一個非常簡單的組播客戶端,它指定從組播組224.0.1.1的5000端口讀數(shù)據(jù),并顯示在終端上,下面我們通過分析該程序來了解內(nèi)核的工作過程。
前面我們講過,bind操作首先檢查用戶指定的端口是否可用,然后為socket的一些成員設(shè)置正確的值,并添加到哈希表myudp_hash中。然后,協(xié)議棧每次收到UDP數(shù)據(jù),就會檢查該數(shù)據(jù)報的源和目的地址,還有源和目的端口,在myudp_hash中找到匹配的socket,把該數(shù)據(jù)報放入該socket的接收隊列,以備用戶讀取。在這個程序中,bind操作把socket綁定到地址224.0.0.1:5000上, 該操作產(chǎn)生的直接結(jié)果就是,對于socket本身,下列值受影響:
struct inet_sock{
.rcv_saddr = 224.0.0.1;
.saddr = 0.0.0.0;
.sport = 5000;
.daddr = 0.0.0.0;
.dport = 0;
}
這五個數(shù)據(jù)表示,該套接字在發(fā)送數(shù)據(jù)包時,本地使用端口5000,本地可以使用任意一個網(wǎng)絡(luò)設(shè)備接口,發(fā)往的目的地址不指定。在接收數(shù)據(jù)時,只接收發(fā)往IP地址224.0.0.1的端口為5000的數(shù)據(jù)。
程序中,緊接著bind有一個setsockopt操作,它的作用是將socket加入一個組播組,因為socket要接收組播地址224.0.0.1的數(shù)據(jù),它就必須加入該組播組。結(jié)構(gòu)體struct ip_mreq mreq是該操作的參數(shù),下面是其定義:
struct ip_mreq
{
struct in_addr imr_multiaddr; // 組播組的IP地址。
struct in_addr imr_interface; // 本地某一網(wǎng)絡(luò)設(shè)備接口的IP地址。
};
一臺主機上可能有多塊網(wǎng)卡,接入多個不同的子網(wǎng),imr_interface參數(shù)就是指定一個特定的設(shè)備接口,告訴協(xié)議棧只想在這個設(shè)備所在的子網(wǎng)中加入某個組播組。有了這兩個參數(shù),協(xié)議棧就能知道:在哪個網(wǎng)絡(luò)設(shè)備接口上加入哪個組播組。為了簡單起見,我們的程序中直接寫明了IP地址:在172.16.48.2所在的設(shè)備接口上加入組播組224.0.1.1。
這個操作是在網(wǎng)絡(luò)層上的一個選項,所以級別是SOL_IP,IP_ADD_MEMBERSHIP選項把用戶傳入的參數(shù)拷貝成了struct ip_mreqn結(jié)構(gòu)體:
struct ip_mreqn
{
struct in_addr imr_multiaddr;
struct in_addr imr_address;
int imr_ifindex;
};
多了一個輸入接口的索引,暫時被拷貝成零。
該操作最終引發(fā)內(nèi)核函數(shù)myip_mc_join_group執(zhí)行加入組播組的操作。首先檢查imr_multiaddr是否為合法的組播地址,然后根據(jù)imr_interface的值找到對應(yīng)的struct in_device結(jié)構(gòu)。接下來就要為socket加入到組播組了,在inet_sock的結(jié)構(gòu)體中有一個成員mc_list,它是一個結(jié)構(gòu)體struct ip_mc_socklist的鏈表,每一個節(jié)點代表socket當(dāng)前正加入的一個組播組,該鏈表是有上限限制的,缺省值為IP_MAX_MEMBERSHIPS(20),也就是說一個socket最多允許同時加入20個組播組。下面是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;
};
struct ip_sf_socklist
{
unsigned int sl_max;
unsigned int sl_count;
__u32 sl_addr[0];
};
除了multi成員,它還有一個源過濾機制。如果我們新添加的struct ip_mreqn已經(jīng)存在于這個鏈表中(表示socket早就加入這個組播組了),那么不做任何事情,否則,創(chuàng)建一個新的struct ip_mc_socklist:
struct ip_mc_socklist
{
.next = inet->mc_list; //新節(jié)點放到鏈表頭。
.multi = 傳入的參數(shù); //這是關(guān)鍵的組信息。
.sfmode = MCAST_EXCLUDE; //過濾掉sflist中的所有源。
.sflist = NULL; //沒有源需要過濾。
};
最后,調(diào)用myip_mc_inc_group函數(shù)在struct in_device和struct net_device的mc_list鏈表中都添上相應(yīng)的組播組節(jié)點,關(guān)于這部分的細(xì)節(jié)可以在前一篇文章《初識組播2》中找到。不再重復(fù)。
到此為止,我們完成了最為簡單的加入組播組的操作,對于同一子網(wǎng)內(nèi)的情況,socket已經(jīng)可以接收組播數(shù)據(jù)了,關(guān)于組播數(shù)據(jù)如何接收,下回分解。
接收組播數(shù)據(jù)報
前面我們講到如何加入到一個組播組中,當(dāng)一個客戶端完成了加入一個組播組的操作后,就可以從該組接收數(shù)據(jù)了。下面我們看看組播數(shù)據(jù)報接收的詳細(xì)流程。
通過加入組播組的操作后,網(wǎng)絡(luò)設(shè)備接口已經(jīng)知道要接收該組的數(shù)據(jù)報,所以組播數(shù)據(jù)會從網(wǎng)卡接收進(jìn)來,一直到達(dá)myip_rcv函數(shù),我們就從myip_rcv函數(shù)開始,跟蹤整個組播數(shù)據(jù)報的接收流程。
同樣,myip_rcv還是先檢查數(shù)據(jù)報的類型(是否為本機需要接收的包),ip首部是否正確,然后調(diào)用myip_rcv_finish。myip_rcv_finish對任何數(shù)據(jù)報都要先查找輸入路由,輸入路由查找函數(shù)是myip_route_input,當(dāng)該函數(shù)在路由緩存myrt_hash_table中找不到相應(yīng)的路由項時,判斷數(shù)據(jù)報的輸入地址,如果發(fā)現(xiàn)是組播地址,就不能簡單地查找FIB,而是要作特殊處理。
首先,調(diào)用myip_check_mc對這個組播數(shù)據(jù)報作檢查,從網(wǎng)絡(luò)設(shè)備接口的struct in_device中去匹配組播地址,如果匹配不到,表示這個不是我們希望接收的組播包,丟棄。匹配到了,則作下一步檢查,如果這本身就是一個IGMP包,則接收,否則,查看這個組播組在我們的struct in_device中設(shè)置的過濾機制,如果該數(shù)據(jù)報的源地址在我們的過濾名單中,則丟棄,否則接收。
如果檢查通過,準(zhǔn)備接收這個組播包,則調(diào)用myip_route_input_mc查找組播輸入路由,這是一個專門為組播設(shè)置的函數(shù),它第一步要檢查數(shù)據(jù)報源地址的有效性,即源地址不能是組播地址,不能是廣播地址,也不能是回環(huán)地址,同時,該數(shù)據(jù)報必須是一個因特網(wǎng)協(xié)議包(ETH_P_IP)。如果源地址為0,那么只有當(dāng)目的地址是224.0.0.0-224.0.0.255之間的值(只能在發(fā)送主機所在的一個子網(wǎng)內(nèi)的傳送,不會通過路由器轉(zhuǎn)發(fā)。)時,系統(tǒng)可以自己選定一個scope為RT_SCOPE_LINK的源地址,否則出錯。
當(dāng)驗證了源地址的有效性之后,我們建立路由項,即結(jié)構(gòu)體struct rtable。該路由項的rt_type值是RTN_MULTICAST,表示這一條組播路由。對于本地接收的組播包,我們設(shè)置接收函數(shù)為myip_local_deliver。
有了這個路由項,我們可以通過調(diào)用myip_local_deliver,繼續(xù)接收流程,這部分流程前面已有多次介紹,所以講得簡單一點,只注意組播特有的。同樣,到myip_local_deliver_finish后,首先要檢查是否有raw socket要接收這個組播包。然后根據(jù)IP首部里協(xié)議字段,調(diào)用相應(yīng)協(xié)議的接收函數(shù),我們這兒是一個UDP組播包,所以調(diào)用myudp_rcv。
myudp_rcv首先會對路由項的成員rt_flags作一個檢查,如果發(fā)現(xiàn)它有RTCF_BROADCAST或者RTCF_MULTICAST,就不會走常規(guī)的從myudp_hash中匹配源和目的地址,找到socket,把數(shù)據(jù)報放入接收隊列這么一個流程。而是調(diào)用函數(shù)myudp_v4_mcast_deliver,這是一個專用于接收UDP組播數(shù)據(jù)報的函數(shù),它首先根據(jù)目的端口確定在哈希表mydup_hash中的位置,然后遍歷找到的這個鏈表。與普通的UDP數(shù)據(jù)報接收相比,它多一個過濾檢查,即在套接字結(jié)構(gòu)體的成員mc_list中找到與該數(shù)據(jù)報所屬組對應(yīng)的ip_mc_socklist項,查看它的過濾配置,確認(rèn)該數(shù)據(jù)報的源地址是否在過濾列表中。如果不在,則把數(shù)據(jù)放到該socket的接收隊列中,完成組播數(shù)據(jù)報的接收。
關(guān)于組播的其它幾個選項
前面我們已經(jīng)講到了加入一個組播組的IP選項IP_ADD_MEMBERSHIP,關(guān)于組播的IP選項,除了這個,還有總共四個,它們分別是IP_DROP_MEMBERSHIP,IP_MULTICAST_IF,IP_MULTICAST_TTL,IP_MULTICAST_LOOP,下面分別一一介紹。
IP_DROP_MEMBERSHIP表示退出一個組播組,該選項最終會調(diào)用內(nèi)核函數(shù)myip_mc_leave_group。該函數(shù)首先拿到結(jié)構(gòu)體struct in_device,取走要離開的組的源過濾機制,即從in_device->mc_list中找到對應(yīng)的組struct ip_mc_list,將其成員sfcount[sfmode]減一,然后從其成員sources中取走相應(yīng)的過濾源。然后將in_device->mc_list中該組所在的節(jié)點的引用計數(shù)減一,如果引用計數(shù)已經(jīng)減為零了,則清struct net_device和struct in_device中該組的記錄。最后,套接字結(jié)構(gòu)體struct inet_sock的成員mc_list中有關(guān)該組的節(jié)點也被刪除。至此,完成離開一個組播組的操作,該選項的參數(shù)是結(jié)構(gòu)體struct ip_mreq,同IP_ADD_MEMBERSHIP。
IP_MULTICAST_IF是一個用于確定提交組播報文的接口,它的參數(shù)也是struct ip_mreq,通過該參數(shù)指定發(fā)送組播報文所使用的本地IP地址和本地網(wǎng)絡(luò)設(shè)備接口的索引號,用于發(fā)送組播數(shù)據(jù)報,這兩個值確定后放在套接字的結(jié)構(gòu)體struct inet_sock的成員mc_addr和mc_index中,以備發(fā)送組播數(shù)據(jù)報時查詢。
IP_MULTICAST_TTL指定提交的組播報文的TTL,有效的TTL在0到255之間,該選項提供的參數(shù)會被賦給套接字結(jié)構(gòu)體struct inet_sock的成員mc_ttl。以備發(fā)送組播數(shù)據(jù)報時查詢。
IP_MULTICAST_LOOP使組播報文環(huán)路有效或無效,如果環(huán)路有效,則在發(fā)送組播報文的時候,會給環(huán)回接口也發(fā)一份。該值存放在套接字的結(jié)構(gòu)體struct inet_sock的成員mc_loop中。
以上IP_MULTICAST_IF,IP_MULTICAST_TTL和IP_MULTICAST_LOOP三項都是跟組播報文發(fā)送相關(guān)的選項,在接下來的發(fā)送組播數(shù)據(jù)報的分析中會再次提到。
發(fā)送組播數(shù)據(jù)報(1)
我們還是以發(fā)送UDP的組播數(shù)據(jù)為例。其實發(fā)送一個UDP的組播數(shù)據(jù)報跟發(fā)送一個單播UDP數(shù)據(jù)報的差別并不大。
首先是在myudp_sendmsg函數(shù)中,如果發(fā)送接口的源地址沒有確定,并且目的地址是組播地址的話,則源地址使用inet_sock->mc_addr。而發(fā)送接口的源地址首先是通過inet_sock->saddr來確定的,如果發(fā)現(xiàn)inet_sock->saddr為零,才會采用inet_sock->mc_addr的值。
通過前面的文章,我們可以了解到bind系統(tǒng)調(diào)用的作用就是為一個本地套接口指定發(fā)送源地址和接收地址(即把一個本地套接口綁定在一個本地網(wǎng)絡(luò)設(shè)備接口上)。而組播選項IP_MULTICAST_IF用于指定組播數(shù)據(jù)報的發(fā)送接口,兩者的功能似乎有些重復(fù)。bind影響的是inet_sock的成員rcv_saddr, saddr, sport,分別表示接收地址(輸入數(shù)據(jù)報首部中指定該地址為目的地址的,將被接收),發(fā)送源地址(本地某個網(wǎng)絡(luò)設(shè)備接口的地址),發(fā)送和接收的端口。對于單播的情況,顯然rcv_saddr==saddr,因為一般來講 ,一個應(yīng)用程序總是使用一個網(wǎng)絡(luò)設(shè)備接口進(jìn)行數(shù)據(jù)的收發(fā)的。但如果應(yīng)用程序非要把一個組播地址和端口綁定到一個本地套接口上,則bind系統(tǒng)調(diào)用會讓rcv_addr=組播地址,sport=端口,而saddr等于0,但協(xié)議棧發(fā)送組播數(shù)據(jù)報必須要有一個本地網(wǎng)絡(luò)設(shè)備接口,沒有saddr,協(xié)議棧就不知道通過那個設(shè)備發(fā)送數(shù)據(jù)報,這個任務(wù)就留給了IP_MULTICAST_IF選項,它為inet_sock的成員mc_addr和mc_index賦值,指定本地接口用于發(fā)送組播數(shù)據(jù)報。
由上可得,如果我們的應(yīng)用希望通過本地一個網(wǎng)絡(luò)設(shè)備接口向網(wǎng)絡(luò)發(fā)送組播數(shù)據(jù)報,而不關(guān)心接收該組的數(shù)據(jù)報(可能來自其它主機,在啟動環(huán)路的情況下,也可能是來自自己),我們可以簡單地通過bind把這個發(fā)送套接字綁定到一個本地接口,然后再向組播地址發(fā)送數(shù)據(jù)報即可,但這樣的話,感覺就像是自己站在組外(不屬于這個組)向組內(nèi)發(fā)送數(shù)據(jù)報,源代碼如下:
#include
#include
#include
#include
#include "my_inet.h"
#include
#define MAXBUF 256
#define PUERTO 5000
#define GRUPO "224.0.1.1"
int main(void)
{
int fd;
struct sockaddr_in srv,local;
char buf[MAXBUF];
memset( &srv, 0, sizeof(srv) );
srv.sin_family = MY_AF_INET;
srv.sin_port = htons(PUERTO);
local.sin_family = AF_INET;
local.sin_port = htons(16000);
inet_aton("172.16.48.2", &(local.sin_addr) );
if( inet_aton(GRUPO, &srv.sin_addr) 因為bind系統(tǒng)調(diào)用把inet_sock的成員rcv_addr也置成了本地網(wǎng)絡(luò)設(shè)備接口的地址172.16.48.2,所以這個程序只能發(fā)送數(shù)據(jù)報,不能夠接收到來自組224.0.1.1的數(shù)據(jù)報。如果想要發(fā)送者也能接收,應(yīng)該這樣改程序:
#include
#include
#include
#include
#include
#include "my_inet.h"
#define MAXBUF 256
#define PUERTO 5000
#define GROUP "224.0.1.1"
int main(void)
{
int fd;
struct sockaddr_in srv,local;
struct in_addr if_req;
char buf[MAXBUF];
srv.sin_family = MY_AF_INET;
srv.sin_port = htons(PUERTO);
inet_aton(GROUP, &srv.sin_addr);
local.sin_family =MY_ AF_INET;
local.sin_port = htons(16000);
inet_aton(GROUP, &(local.sin_addr) );
if( (fd = socket( AF_INET, SOCK_DGRAM, IPPROTO_UDP) ) if( setsockopt( fd, SOL_IP, IP_MULTICAST_IF, &if_req, sizeof(struct in_addr) ) perror("setsockopt:");
return -1;
}
while( fgets(buf, MAXBUF, stdin) ){
if( sendto(fd, buf, strlen(buf), 0, (struct sockaddr *)&srv, sizeof(srv)) 這回,本地套接口被綁定在了一個組播地址上了,這樣,這個應(yīng)用程序不僅能夠發(fā)送組播數(shù)據(jù),也能夠接受同組中發(fā)往16000端口的數(shù)據(jù)報了(最好再加個IP_ADD_MEMBERSHIP選項的操作),但bind組播地址時,只會設(shè)定接收地址為該組播地址,不會設(shè)定發(fā)送源地址,所以,必須使用IP_MULTICAST_IF接口指定一個發(fā)送接口(程序中指定了172.16.48.2,即eth0接口)。
這兩個程序在現(xiàn)在的my_inet模塊中均能夠正常工作,但是它們調(diào)用的實際發(fā)送代碼是UDP單播的代碼,這基本能正常工作,但是單播的代碼少了很多對組播的特殊處理,比如組播路由驗證,環(huán)路發(fā)送等。
在下一篇,我們將為模塊添加組播數(shù)據(jù)報發(fā)送的代碼,并給出分析。
注:嚴(yán)格來講,上述兩個程序都是有問題的,程序1的套接口會收到發(fā)往本機172.16.48.2接口的16000端口的數(shù)據(jù),并阻塞在套接口的接收隊列中,程序2會收到發(fā)往組224.0.1.1的16000端口的數(shù)據(jù),并阻塞在套接口的接收隊列中。
發(fā)送組播數(shù)據(jù)報(2)
前面一篇文章中提到的兩個示例程序,它們雖然對外發(fā)送了組播數(shù)據(jù)報,但它們實際上調(diào)用的是協(xié)議棧中的單播發(fā)送的代碼。一般情況下,它們不會有什么問題,但是它們不是標(biāo)準(zhǔn)的組播程序,下面我們看看協(xié)議棧究竟是如何發(fā)送組播數(shù)據(jù)報的。
我們還是以發(fā)送UDP的組播數(shù)據(jù)報為例。前面已經(jīng)講過,IP選項IP_MULTICAST_IF確定組播發(fā)送的接口,在通過系統(tǒng)調(diào)用設(shè)置該選項時,參數(shù)只需要一個本地網(wǎng)絡(luò)接口的IP地址即可,myudp_sendmsg函數(shù)在發(fā)送組播數(shù)據(jù)報時,會以該選項設(shè)定的IP地址作為輸出路由查詢的源地址。
對于一個輸出組播數(shù)據(jù)報,協(xié)議棧也要做檢查,檢查該組播發(fā)送的接口是否也加入了同一個組播組(即檢查net_device->in_device->mc_list鏈表,查看是否存在跟輸出組播數(shù)據(jù)報目的地址相同的組),如果檢查結(jié)果確實加入了同一個組(本機可能有其它進(jìn)程在同一網(wǎng)絡(luò)設(shè)備口上,在該組中接收數(shù)據(jù)報),則把組播輸出函數(shù)指定為myip_mc_output,該函數(shù)與普通的IP數(shù)據(jù)報輸出函數(shù)相比,多了一個判斷,如果啟用了組播環(huán)路,則先向loopback接口發(fā)送一個組播數(shù)據(jù)報,確保本機需要接收該組中的數(shù)據(jù)的進(jìn)程能收到數(shù)據(jù)。組播環(huán)路缺省是打開的,可以通過IP選項IP_MULTICAST_LOOP進(jìn)行設(shè)置。
組播數(shù)據(jù)報的TTL的缺省值是1,這在很多情況下,顯然是不適用的,我們必須能夠修改它,IP選項IP_MULTICAST_TTL可用來修改這個TTL值,該選項把它的參數(shù)值賦給套接字結(jié)構(gòu)體的成員mc_ttl。協(xié)議棧在為待發(fā)送數(shù)據(jù)報構(gòu)建IP首部時,發(fā)現(xiàn)該數(shù)據(jù)的目的地址是一個組播地址時,就會把mc_ttl的值填入IP首部的ttl域。
下面是一個組播客戶端和一個組播服務(wù)端程序,讓它們運行在同一臺主機上,試著修改一些參數(shù),你就能得到各種不同的行為。我在調(diào)試中發(fā)現(xiàn)一個問題,就是現(xiàn)有的Linux TCP/IP協(xié)議棧代碼在往組播環(huán)路發(fā)數(shù)據(jù)報時,本機接收進(jìn)程會收到兩個數(shù)據(jù)包,也就是本示例中,服務(wù)端開啟了環(huán)路以后,發(fā)一個數(shù)據(jù)報,客戶端會收到兩個,很有趣的問題,暫時沒想通,歡迎大家探討。
服務(wù)端:
#include
#include
#include
#include
#include
#include "my_inet.h"
#define MAXBUF 256
#define PUERTO 5000
#define GROUP "224.0.1.1"
int main(void)
{
int fd, mc_loop = 1;
struct sockaddr_in srv,local;
struct in_addr if_req;
char buf[MAXBUF];
srv.sin_family = MY_AF_INET;
srv.sin_port = htons(PUERTO);
inet_aton(GROUP, &srv.sin_addr);
if( (fd = socket( MY_AF_INET, SOCK_DGRAM, MY_IPPROTO_UDP) ) 客戶端程序:
#include
#include
#include
#include
#include "my_inet.h"
#include
#define MAXBUF 256
#define PUERTO 5000
#define GROUP "224.0.1.1"
int main(void)
{
int fd, n, r;
struct sockaddr_in srv, cli;
struct ip_mreq mreq;
char buf[MAXBUF];
srv.sin_family = MY_AF_INET;
srv.sin_port = htons(PUERTO);
inet_aton(GROUP, &srv.sin_addr );
if( (fd = socket( MY_AF_INET, SOCK_DGRAM, MY_IPPROTO_UDP) )
struct ip_mreqn
{
struct in_addr imr_multiaddr; /* IP多點傳送組地址 */
struct in_addr imr_address; /* 本地接口的IP地址 */
int imr_ifindex; /* 接口索引 */
};
imr_multiaddr 包含應(yīng)用程序希望加入或者退出的多點廣播組的地址. 它必須是一個有效的多點廣播地址. imr_address 指的是系統(tǒng)用來加入多點廣播組的本地接口地址;如果它與 INADDR_ANY 一致,那么由系統(tǒng)選擇一個合適的接口. imr_ifindex 指的是要加入/脫離 imr_multiaddr 組的接口索引,或者設(shè)為0表示任何接口.
由于兼容性的緣故,老的 ip_mreq 接口仍然被支持.它與 ip_mreqn 只有一個地方不同,就是沒有包括 imr_ifindex 字段.這只在作為一個 setsockopt(2) 時才有效.
IP_DROP_MEMBERSHIP
脫離一個多點廣播組.參數(shù)為 ip_mreqn 或者 ip_mreq 結(jié)構(gòu),這與 IP_ADD_MEMBERSHIP 類似. T P IP_MULTICAST_IF 為多點廣播套接字設(shè)置本地設(shè)備.參數(shù)為 ip_mreqn 或者 ip_mreq 結(jié)構(gòu),它與 IP_ADD_MEMBERSHIP 類似.
當(dāng)傳遞一個無效的套接字選項時,返回 ENOPROTOOPT .
本文來自ChinaUnix博客,如果查看原文請點:http://blog.chinaunix.net/u1/35065/showart_2154912.html |
|