组播技术指的是单个发送者对应多个接收者的一种网络通信。组播技术中,通过向多个接收方传送单信息流方式,可以减少具有多个接收方同时收听或查看相同资源情况下的网络通信流量。
在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);
}
}