亚洲av成人无遮挡网站在线观看,少妇性bbb搡bbb爽爽爽,亚洲av日韩精品久久久久久,兔费看少妇性l交大片免费,无码少妇一区二区三区

  免費注冊 查看新帖 |

Chinaunix

  平臺 論壇 博客 文庫
最近訪問板塊 發(fā)新帖
查看: 2733 | 回復(fù): 0
打印 上一主題 下一主題

組播編程 [復(fù)制鏈接]

論壇徽章:
0
跳轉(zhuǎn)到指定樓層
1 [收藏(0)] [報告]
發(fā)表于 2010-01-20 15:12 |只看該作者 |倒序瀏覽

初識組播(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
您需要登錄后才可以回帖 登錄 | 注冊

本版積分規(guī)則 發(fā)表回復(fù)

  

北京盛拓優(yōu)訊信息技術(shù)有限公司. 版權(quán)所有 京ICP備16024965號-6 北京市公安局海淀分局網(wǎng)監(jiān)中心備案編號:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年舉報專區(qū)
中國互聯(lián)網(wǎng)協(xié)會會員  聯(lián)系我們:huangweiwei@itpub.net
感謝所有關(guān)心和支持過ChinaUnix的朋友們 轉(zhuǎn)載本站內(nèi)容請注明原作者名及出處

清除 Cookies - ChinaUnix - Archiver - WAP - TOP