前面一篇文章中提到的两个示例程序,它们虽然对外发送了组播数据报,但它们实际上调用的是协议栈中的单播发送的代码。一般情况下,它们不会有什么问题,但是它们不是标准的组播程序,下面我们看看协议栈究竟是如何发送组播数据报的。
我们还是以发送UDP的组播数据报为例。前面已经讲过,IP选项IP_MULTICAST_IF确定组播发送的接口,在通过系统调用设置该选项时,参数只
需要一个本地网络接口的IP地址即可,myudp_sendmsg函数在发送组播数据报时,会以该选项设定的IP地址作为输出路由查询的源地址。
对于一个输出组播数据报,协议栈也要做检查,检查该组播发送的接口是否也加入了同一个组播组(即检查
net_device->in_device->mc_list链表,查看是否存在跟输出组播数据报目的地址相同的组),如果检查结果确实加
入了同一个组(本机可能有其它进程在同一网络设备口上,在该组中接收数据报),则把组播输出函数指定为myip_mc_output,该函数与普通的IP
数据报输出函数相比,多了一个判断,如果启用了组播环路,则先向loopback接口发送一个组播数据报,确保本机需要接收该组中的数据的进程能收到数
据。组播环路缺省是打开的,可以通过IP选项IP_MULTICAST_LOOP进行设置。
组播数据报的TTL的缺省值是1,这在很多情况下,显然是不适用的,我们必须能够修改它,IP选项IP_MULTICAST_TTL可用来修改这个TTL
值,该选项把它的参数值赋给套接字结构体的成员mc_ttl。协议栈在为待发送数据报构建IP首部时,发现该数据的目的地址是一个组播地址时,就会把
mc_ttl的值填入IP首部的ttl域。
下面是一个组播客户端和一个组播服务端程序,让它们运行在同一台主机上,试着修改一些参数,你就能得到各种不同的行为。我在调试中发现一个问题,就是现有
的Linux
TCP/IP协议栈代码在往组播环路发数据报时,本机接收进程会收到两个数据包,也就是本示例中,服务端开启了环路以后,发一个数据报,客户端会收到两
个,很有趣的问题,暂时没想通,欢迎大家探讨。
服务端:
#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) ) < 0 ){
perror("socket");
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;
}
if( setsockopt( fd, SOL_IP, IP_MULTICAST_LOOP, &mc_loop, sizeof(int) ) < 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);
}
}
}
客户端程序:
#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) ) < 0 ){
perror("socket");
return -1;
}
if( bind(fd, (struct sockaddr *)&srv, sizeof(srv)) < 0 ){
perror("bind");
return -1;
}
/*
inet_aton( GROUP, &mreq.imr_multiaddr );
inet_aton( "172.16.48.2", &mreq.imr_interface );
if( setsockopt(fd, SOL_IP, IP_ADD_MEMBERSHIP, &mreq,sizeof(mreq)) < 0 ){
perror("setsockopt");
return -1;
}
if( setsockopt(fd, SOL_IP, IP_DROP_MEMBERSHIP, &mreq, sizeof(mreq)) < 0 ){
perror("setsockopt");
return -1;
}
*/
n = sizeof(cli);
while(1){
if( (r = recv(fd, buf, MAXBUF, 0)) < 0 ){
perror("recv:");
}else{
buf[r] = 0;
fprintf(stdout, "Mensaje desde: %s", buf);
}
}
}
阅读(914) | 评论(0) | 转发(1) |