我们还是以发送UDP的组播数据为例。其实发送一个UDP的组播数据报跟发送一个单播UDP数据报的差别并不大。
首先是在myudp_sendmsg函数中,如果发送接口的源地址没有确定,并且目的地址是组播地址的话,则源地址使用 inet_sock->mc_addr。而发送接口的源地址首先是通过inet_sock->saddr来确定的,如果发现 inet_sock->saddr为零,才会采用inet_sock->mc_addr的值。
通过前面的文章,我们可以了解到bind系统调用的作用就是为一个本地套接口指定发送源地址和接收地址(即把一个本地套接口绑定在一个本地网络设备接口 上)。而组播选项IP_MULTICAST_IF用于指定组播数据报的发送接口,两者的功能似乎有些重复。bind影响的是inet_sock的成员 rcv_saddr, saddr, sport,分别表示接收地址(输入数据报首部中指定该地址为目的地址的,将被接收),发送源地址(本地某个网络设备接口的地址),发送和接收的端口。对 于单播的情况,显然rcv_saddr==saddr,因为一般来讲 ,一个应用程序总是使用一个网络设备接口进行数据的收发的。但如果应用程序非要把一个组播地址和端口绑定到一个本地套接口上,则bind系统调用会让 rcv_addr=组播地址,sport=端口,而saddr等于0,但协议栈发送组播数据报必须要有一个本地网络设备接口,没有saddr,协议栈就不 知道通过那个设备发送数据报,这个任务就留给了IP_MULTICAST_IF选项,它为inet_sock的成员mc_addr和mc_index赋 值,指定本地接口用于发送组播数据报。
由上可得,如果我们的应用希望通过本地一个网络设备接口向网络发送组播数据报,而不关心接收该组的数据报(可能来自其它主机,在启动环路的情况下,也可能 是来自自己),我们可以简单地通过bind把这个发送套接字绑定到一个本地接口,然后再向组播地址发送数据报即可,但这样的话,感觉就像是自己站在组外 (不属于这个组)向组内发送数据报,源代码如下:
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include "my_inet.h"
#include <linux/in.h>
#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) < 0 ){
perror("inet_aton");
return -1;
}
if( (fd = socket( MY_AF_INET, SOCK_DGRAM, MY_IPPROTO_UDP) ) < 0 ){
perror("socket");
return -1;
}
if( bind( fd, (struct sockaddr *)&local, sizeof(local) ) < 0 ){
perror("bind:");
return -1;
}
while( fgets(buf, MAXBUF, stdin) ){
if( sendto(fd, buf, strlen(buf), 0, (struct sockaddr *)&srv, sizeof(srv)) < 0 ){
perror("recvfrom");
}else{
fprintf(stdout, "Enviado a %s: %s", GRUPO, buf);
}
}
}
因为bind系统调用把inet_sock的成员rcv_addr也置成了本地网络设备接口的地址172.16.48.2,所以这个程序只能发送数据报,不能够接收到来自组224.0.1.1的数据报。如果想要发送者也能接收,应该这样改程序:
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <string.h>
#include <arpa/inet.h>
#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) ) < 0 ){
perror("socket");
return -1;
}
if( bind( fd, (struct sockaddr *)&local, sizeof(local) ) < 0 ){
perror("bind:");
return -1;
}
inet_aton("172.16.48.2", &(if_req) );
if( setsockopt( fd, SOL_IP, IP_MULTICAST_IF, &if_req, sizeof(struct in_addr) ) < 0 ){
perror("setsockopt:");
return -1;
}
while( fgets(buf, MAXBUF, stdin) ){
if( sendto(fd, buf, strlen(buf), 0, (struct sockaddr *)&srv, sizeof(srv)) < 0 ){
perror("sendto");
}else{
fprintf(stdout, "Enviado a %s: %s", GROUP, buf);
}
}
}
这回,本地套接口被绑定在了一个组播地址上了,这样,这个应用程序不仅能够发送组播数据,也能够接受同组中发往16000端口的数据报了(最好再加个 IP_ADD_MEMBERSHIP选项的操作),但bind组播地址时,只会设定接收地址为该组播地址,不会设定发送源地址,所以,必须使用 IP_MULTICAST_IF接口指定一个发送接口(程序中指定了172.16.48.2,即eth0接口)。
这两个程序在现在的my_inet模块中均能够正常工作,但是它们调用的实际发送代码是UDP单播的代码,这基本能正常工作,但是单播的代码少了很多对组播的特殊处理,比如组播路由验证,环路发送等。
在下一篇,我们将为模块添加组播数据报发送的代码,并给出分析。
注:严格来讲,上述两个程序都是有问题的,程序1的套接口会收到发往本机172.16.48.2接口的16000端口的数据,并阻塞在套接口的接收队列中,程序2会收到发往组224.0.1.1的16000端口的数据,并阻塞在套接口的接收队列中。