組播技術指的是單個發送者對應多個接收者的一種網絡通信。組播技術中,通過向多個接收方傳送單信息流方式,可以減少具有多個接收方同時收聽或查看相同資源情況下的網絡通信流量。
在struct net_device這個網絡設備接口標識的結構體中,有一個mc_list成員,這個成員是組播關鍵的數據結構之一。mc_list是一個鏈表,鏈表的一個結點代表一個組播地址,代表這個網絡設備接口已經加入了組播中。該結點的結構體定義如下:
struc 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;
};
my_inet模塊在初始化時,myinetdev_event函數收到網絡設備接口啓動的消息後,調用myip_mc_up啓動組播功能。
啓動組播功能的第一件事就是把本機加入到組播組中(IGMP_ALL_HOSTS),調用哦myip_mc_inc_group函數完成加入。
如何把網絡設備接口加入IGMP_ALL_HOST組?
首先檢查in_device->mc_list列表中已加入的組播組,確定這個接口是否已經加入了IGMP_ALL_HOST組。如果沒有加入,就先創建一個新的結構體struct ip_mc_list *im,初始化其成員值,將multiaddr設爲組播地址(224網段到239網段任意ip地址都可以),sf_mode[MCAST_EXCLUDE]設爲1,sources的值設爲NULL,表示使用一個源過濾機制,該機制不過濾任何組播源。將loaded的值0,表示該組播組尚未被載入,初始化完成後,將這個新的組播組加入到mc_list鏈表的表頭。
另一個結構體:
struct dev_mc_list
{
struct dev_mc_list *next;
__u8 dmi_addr[MAX_ADDR_LEN]; //mac地址
unsigned char dmi_addrlen;//地址長度
int dmi_users; //應用計數
int dmi_gusers;
};
組播ip地址如何被映射成組播mac地址?
一個mac地址總共有6字節,48位,被分成兩段:前3字節和後3字節,前3字節用於標識網卡的製造商,其中第40位(第一個字節的最低位)用於標識組播,所以在網卡的mac地址中必須置0,後3字節是廠商內部使用的序列號,一個組播ip地址映射成mac地址的規則是:前3字節強制置01:00:5E,後3字節中,第23位置0,0-22位放入ip地址的0-23位。
struct ip_mreq
{
struct in_addr imr_multiaddr; //組播組的ip地址
struct in_addr imr_interface; //本地某一網絡設備接口的ip地址
};
setsockopt()函數將socket加入一個組播組。
一臺主機上可能有多塊網卡,接入多個不同的子網,imr_interface參數就是指定一個特定的設備接口,告訴協議棧只想在這個設備所在的子網中加入某個組播組,有這兩個參數,協議棧就能知道在哪個網絡設備接口上加入哪個組播組。
IP_ADD_MEMBERSHIP選項把用戶傳入的參數拷貝成struct ip_mreqn結構體:
struct ip_mreqn
{
struct in_addr imr_multiaddr;
struct in_addr imr_address;
int imr_ifindex;
};
組播編程例子(主要用來接收組播信息):
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <string.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define PORT 4444
#define GRIP "225.3.3.3"
#define MAXSIZE 1024
int main()
{
int sockfd,len,recvbytes;
struct sockaddr_in serv, cli;
struct ip_mreq mreq;
char buf[MAXSIZE];
FILE *fp;
memset(&serv, 0, sizeof(struct sockaddr_in));
memset(&cli, 0, sizeof(struct sockaddr_in));
memset(&mreq, 0, sizeof(struct ip_mreq));
serv.sin_family = AF_INET;
serv.sin_port = htons(PORT);
if(inet_aton(GRIP, &serv.sin_addr) < 0){
perror("inet_aton");
exit(-1);
}
if((sockfd = socket(AF_INET,SOCK_DGRAM,0)) < 0)
{
perror("socket error");
exit(-1);
}
if(bind(sockfd, (struct sockaddr *)&serv, sizeof(serv)) < 0)
{
perror("bind error");
exit(-1);
}
if(inet_aton(GRIP,&mreq.imr_multiaddr) < 0)
{
perror("inet_aton error");
exit(-1);
}
inet_aton("172.16.6.63", &(mreq.imr_interface));
if(setsockopt(sockfd, SOL_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) < 0)
{
perror("setsocketopt() error");
exit(-1);
}
len = sizeof(cli);
if((fp = fopen("test.ts","w")) < 0)
{
perror("can not file\n");
exit(-1);
}
while(1)
{
memset(buf, 0, 1024);
recvbytes = recvfrom(sockfd, buf, MAXSIZE, 0, (struct sockaddr *)&cli,&len);
if(recvbytes == 0)
{
printf("receive finish!\n");
exit(-1);
}
printf("receive data !\n");
fprintf(fp,"%s",buf);
sleep(1);
}
}