linux编程之多播与广播.docx
《linux编程之多播与广播.docx》由会员分享,可在线阅读,更多相关《linux编程之多播与广播.docx(16页珍藏版)》请在冰豆网上搜索。
linux编程之多播与广播
广播1
嵌入式设备unreachable问题2
多播3
广播
广播即向局域网内所有的IP节点发送数据。
1)节点收数据:
先让接收的socketblind一个本地地址(关键要设置广播端口),然后再recvfrm该socket即可
2)节点发数据:
发送的socket不一定要blind地址,关键需要创建一个INADDR_BROADCAST的structsockaddr_in结构,然后直接通过socket通过sendto即可完成。
广播是指在一个局域网中向所有的网上节点发送信息。
这是UDP连接的一种广播有一个广播组,即只有一个广播组内的节点才能收到发往这个广播组的信息。
什么决定了一个广播组呢,就是端口号,局域网内一个节点,如果设置了广播属性并监听了端口号A后,那么他就加入了A组广播,这个局域网内所有发往广播端口A的信息他都收的到。
在广播的实现中,如果一个节点想接受A组广播信息,那么就要先将他绑定给地址和端口A,然后设置这个socket的属性为广播属性。
如果一个节点不想接受广播信息,而只想发送广播信息,那么不用绑定端口,只需要先为socket设置广播属性后,向广播地址INADDR_BROADCAST的A端口发送udp信息即可。
详细的程序实现如下:
1.初始化
WSAStartup(MAKEWORD(2,2),&wsad);
2.创建一个UDP的socket
s=socket(AF_INET,SOCK_DGRAM,0);
3.如果这个socket希望收到信息,则需要绑定地址和这组广播的端口号,如果只是希望发送广播信息,则不需要这步
SOCKADDR_INudpAdress,sender;
intsenferAddSize=sizeof(sender);
udpAdress.sin_family=AF_INET;
udpAdress.sin_port=htons(11114);
udpAdress.sin_addr.s_addr=inet_addr("10.11.131.32");
bind(s,(SOCKADDR*)&udpAdress,sizeof(udpAdress));
//这样这个节点即可收到局域网内所有发往端口11114的广播信息
4.设置socket的属性为广播
booloptval=true;
setsockopt(s,SOL_SOCKET,SO_BROADCAST,(char*)&optval,sizeof(bool));
5.下面就可以使用recvfrom或sendto来收发广播信息了
这里是接受,这是一个阻塞操作
ret=recvfrom(s,data,1000,0,(SOCKADDR*)&sender,&senferAddSize);
这里是像该广播组发送信息,注意发送的地址为广播地址INADDR_BROADCAST,端口号为改组广播的端口号11114
SOCKADDR_INdstAdd;
dstAdd.sin_family=AF_INET;
dstAdd.sin_port=htons(11114);
dstAdd.sin_addr.s_addr=INADDR_BROADCAST;
sendto(s,data(),totalbyte,0,(SOCKADDR*)&dstAdd,sizeof(SOCKADDR));
嵌入式设备unreachable问题
Linux下不能向255.255.255.255发送udp广播
我的ip是192.168.0.X,路由IP是192.168.0.254,子网掩码255.255.255.0,广播发送的地址为255.255.255.255,Ubuntu下发送正常,然而在嵌入式linux设备里运行,sendto函数返回-1,perror显示networkisunreachable.困扰了一天,后来看了多个帖子,受到了启发,问题解决。
现在跟大家分享一下:
我添加了到255.255.255.255的路由就可以发送成功啊,添加的命令是:
routeadd-net255.255.255.255netmask255.255.255.255deveth0metric1
routeadd-net224.0.1.1netmask224.0.1.1deveth0metric1
或者
routeadd-host255.255.255.255deveth0
这样就OK了!
但是具体原理目前还不知道
多播
多播即组播,相比单播,它能够向加入组内的几个地址接口发送数据;相比广播,它可以向子网外的其他网的地址接口发送数据。
步骤如下:
1)发送:
先创建一个structsockaddr_in结构体,注意该结构体内地址为组播地址,端口为组播端口,然后直接向其sendto即可。
2)接收:
先创建一个socket,blind本地地址和广播端口,然后通过setsockopt设置以下的属性:
IP_ADD_MEMBERSHIPstructip_mreq加入到组播组中//加入时必须定义
IP_ROP_MEMBERSHIPstructip_mreq从组播组中退出//退出时必须定义
IP_MULTICAST_IFstructip_mreq指定提交组播报文的接口(提供必要信息的IP地址)
IP_MULTICAST_TTLu_char指定提交组播报文的TTL(也就是经过的路由器个数,0为单机)
IP_MULTICAST_LOOPu_char使组播报文环路有效或无效(是否本地发送反馈)
最后然后直接对该socket进行recvfrom即可!
本文可做为TCP/IP组播技术的入门材料,文中介绍了组播通信的概念及原理,以及用于组播应用编程的LinuxAPI的详细资料。
为了使读者更加完整的了解Linux组播的整体概念,文中对实现该技术的核心函数也做了介绍。
在文章的最后给出了一个简单的C语言套接字编程例子,说明如何创建组播应用程序。
一、导言
在网络中,主机间可以用三种不同的地址进行通信:
单播地址(unicast):
即在子网中主机的唯一地址(接口)。
如IP地址:
192.168.100.9或MAC地址:
80:
C0:
F6:
A0:
4A:
B1。
广播地址:
这种类型的地址用来向子网内的所有主机(接口)发送数据。
如广播IP地址是192.168.100.255,MAC广播地址:
FF:
FF:
FF:
FF:
FF。
组播地址:
通过该地址向子网内的多个主机即主机群(接口)发送数据。
如果只是向子网内的部分主机发送报文,组播地址就很有用处了;在需要向多个主机发送多媒体信息(如实时音频、视频)的情况下,考虑到其所需的带宽,分别向每一客户端主机发送数据并不是个好办法,如果发送主机与某些接收端的客户主机不在子网之内,采用广播方式也不是一个好的解决方案。
二、组播地址
大家知道,IP地址空间被划分为A、B、C三类。
第四类即D类地址被保留用做组播地址。
在第四版的IP协议(IPv4)中,从224.0.0.0到239.255.255.255间的所有IP地址都属于D类地址。
组播地址中最重要的是第24位到27位间的这四位,对应到十进制是224到239,其它28位保留用做组播的组标识,如下图所示:
图1组播地址示意图
IPv4的组播地址在网络层要转换成网络物理地址。
对一个单播的网络地址,通过ARP协议可以获取与IP地址对应的物理地址。
但在组播方式下ARP协议无法完成类似功能,必须得用其它的方法获取物理地址。
在下面列出的RFC文档中提出了完成这个转换过程的方法:
RFC1112:
MulticastIPv4toEthernetphysicaladdresscorrespondence
RFC1390:
CorrespondencetoFDDI
RFC1469:
CorrespondencetoToken-Ringnetworks
在最大的以太网地址范围内,转换过程是这样的:
将以太网地址的前24位最固定为01:
00:
5E,这几位是重要的标志位。
紧接着的一位固定为0,其它23位用IPv4组播地址中的低23位来填充。
该转换过程如下图所示:
图2地址转换示意图
例如,组播地址为224.0.0.5其以太网物理地址为01:
00:
5E:
00:
00:
05。
还有一些特殊的IPv4组播地址:
224.0.0.1:
标识子网中的所有主机。
同一个子网中具有组播功能的主机都是这个组的成员。
224.0.0.2:
该地址用来标识网络中每个具有组播功有的路由器。
224.0.0.0----224.0.0.255范围内的地址被分配给了低层次的协议。
向这些范围内的地址发送数据包,有组播功能的路由器将不会为其提供路由。
239.0.0.0----239.255.255.255间的地址分配用做管理用途。
这些地址被分配给局部的每一个组织,但不可以分配到组织外部,组织内的路由器不向在组织外的地址提供路由。
除了上面列出的部分组播地址外,还有许多的组播地址。
在最新版本的RFC文档“AssingedNumbers”中有完整的介绍。
下面的表中列出了全部的组播地址空间,同时还列出了相应的地址段的常用名称及其TTL(IP包的存活时间)。
在IPv4组播方式下,TTL有双重意义:
正如大家所知的,TTL原本用来控制数据包在网络中的存活时间,防止由于路由器配置错误导致出现数据包传播的死循环;在组播方式下,它还代表了数据包的活动范围,如:
数据包在网络中能够传送多远?
这样就可以基于数据包的分类来定义其传送范围。
范围TTL地址区间描述
节点(Node)0只能向本机发送的数据包,不能向网络中的其它接口传送
链路(Link)1224.0.0.0-224.0.0.255只能在发送主机所在的一个子网内的传送,不会通过路由器转发。
部门32239.255.0.0-239.255.255.255只在整个组织下的一个部门内(Department)传送
组织64239.192.0.0--239.195.255.255在整个组织内传送(Organization)
全局(Global)255224.0.1.0--238.255.255.255没有限制,可全局范围内传送
三、组播的工作过程
在局域网内,主机的网络接口将到目的主机的数据包发送到高层,这些数据包中的目的地址是物理接口地址或广播地址。
如果主机已经加入到一个组播组中,主机的网络接口就会识别出发送到该组成员的数据包。
因此,如果主机接口的物理地址为80:
C0:
F6:
A0:
4A:
B1,其加入的组播组为224.0.1.10,则发送给主机的数据包中的目的地址必是下面三种类型之一:
接口地址:
80:
C0:
F6:
A0:
4A:
B1
广播地址:
FF:
FF:
FF:
FF:
FF:
FF:
FF:
FF
组播地址:
01:
00:
5E:
00:
01:
0A
广域网中,路由器必须支持组播路由。
当主机中运行的进程加入到某个组播组中时,主机向子网中的所有组播路由器发送IGMP(Internet分组管理协议)报文,告诉路由器凡是发送到这个组播组的组播报文都必须发送到本地的子网中,这样主机的进程就可以接收到报文了。
子网中的路由器再通知其它的路由器,这些路由器就知道该将组播报文转发到哪些子网中去。
子网中的路由器也向224.0.0.1发送一个IGMP报文(224.0.0.1代表组中的全部主机),要求组中的主机提供组的相关信息。
组中的主机收到这个报文后,都各将计数器的值设为随机值,当计数器递减为0时再向路由器发送应答。
这样就防止了组中所有的主机同时向路由器发送应答,造成网络拥塞。
主机向组播地址发送一个报文做为对路由器的应答,组中的其它主机一旦看到这个应答报文,就不再发送应答报文了,因为组中的主机向路由器提供的都是相同的信息,所以子网路由器只需得到组中一个主机提供的信息就可以了。
如果组中的主机都退出了,路由器就收不到应答,因此路由器认为该组目前没有主机加入,遂停止到该子网报文的路由。
IGMPv2的解决方案是:
组中的主机在退出时向224.0.0.2发送报文通知组播路由器。
四、应用编程接口(API)
如果你有套接字编程的经验,就会发现,对组播选项所进行的操作只需五个新的套接字操作。
函数setsockopt()及getsockopt()用来建立和读取这五个选项的值。
下表中列出了组播的可选项,并列出其数据类型和描述:
IPv4选项数据类型描述
IP_ADD_MEMBERSHIPstructip_mreq加入到组播组中
IP_ROP_MEMBERSHIPstructip_mreq从组播组中退出
IP_MULTICAST_IFstructip_mreq指定提交组播报文的接口
IP_MULTICAST_TTLu_char指定提交组播报文的TTL
IP_MULTICAST_LOOPu_char使组播报文环路有效或无效
在头文件中定义了ip_mreq结构:
structip_mreq{
structin_addrimr_multiaddr;/*IPmulticastaddressofgroup*/
structin_addrimr_interface;/*localIPaddressofinterface*/
};
在头文件中组播选项的值为:
#defineIP_MULTICAST_IF32
#defineIP_MULTICAST_TTL33
#defineIP_MULTICAST_LOOP34
#defineIP_ADD_MEMBERSHIP35
#defineIP_DROP_MEMBERSHIP36
IP_ADD_MEMBERSHIP
若进程要加入到一个组播组中,用soket的setsockopt()函数发送该选项。
该选项类型是ip_mreq结构,它的第一个字段imr_multiaddr指定了组播组的地址,第二个字段imr_interface指定了接口的IPv4地址。
IP_DROP_MEMBERSHIP
该选项用来从某个组播组中退出。
数据结构ip_mreq的使用方法与上面相同。
IP_MULTICAST_IF
该选项可以修改网络接口,在结构ip_mreq中定义新的接口。
IP_MULTICAST_TTL
设置组播报文的数据包的TTL(生存时间)。
默认值是1,表示数据包只能在本地的子网中传送。
IP_MULTICAST_LOOP
组播组中的成员自己也会收到它向本组发送的报文。
这个选项用于选择是否激活这种状态。
五、一个组播通信的例子
下面给出一个简单的例子实现文中阐述的思想:
由一个进程向一个组播组发送报文,组播组中的相关进程接收报文,并将报文显示到屏幕上。
下面的代码实现了一个服务进程,它将标准输入接口输入的信息全部发送到组播组224.0.1.1。
你会发现,将信息发送到组播组不需要特别的操作,只要设置好组播组的目的地址就足够了。
若在开发过程中,Loopback和TTL这两个选项的默认值不适合应用程序,可以加以调整。
服务程序
将标准输入端口的输入发送到组播组224.0.1.1。
#include
#include
#include
#include
#include
#include
#defineMAXBUF256
#definePUERTO5000
#defineGROUP"224.0.1.1"
intmain(void){
ints;
structsockaddr_insrv;
charbuf;
bzero(&srv,sizeof(srv));
srv.sin_family=AF_INET;
srv.sin_port=htons(PUERTO);
if(inet_aton(GRUPO,&srv.sin_addr)<0){
perror("inet_aton");
return1;
}
if((s=socket(AF_INET,SOCK_DGRAM,0))<0){
perror("socket");
return1;
}
while(fgets(buf,MAXBUF,stdin)){
if(sendto(s,buf,strlen(buf),0,
(structsockaddr*)&srv,sizeof(srv))<0){
perror("recvfrom");
}else{
fprintf(stdout,"Enviadoa%s:
%s
",GRUPO,buf);
}
}
}
客户端程序
#include
#include
#include
#include
#include
#include
#defineMAXBUF256
#definePUERTO5000
#defineGROUP"224.0.1.1"
intmain(void){
ints,n,r;
structsockaddr_insrv,cli;
structip_mreqmreq;
charbuf;
bzero(&srv,sizeof(srv));
srv.sin_family=AF_INET;
srv.sin_port=htons(PUERTO);
if(inet_aton(GRUPO,&srv.sin_addr)<0){
perror("inet_aton");
return1;
}
if((s=socket(AF_INET,SOCK_DGRAM,0))<0){
perror("socket");
return1;
}
if(bind(s,(structsockaddr*)&srv,sizeof(srv))<0){
perror("bind");
return1;
}
if(inet_aton(GRUPO,&mreq.imr_multiaddr)<0){
perror("inet_aton");
return1;
}
mreq.imr_interface.s_addr=htonl(INADDR_ANY);
if(setsockopt(s,IPPROTO_IP,IP_ADD_MEMBERSHIP,&mreq,sizeof(mreq))
<0){
perror("setsockopt");
return1;
}
n=sizeof(cli);
while
(1){
if((r=recvfrom(s,buf,MAXBUF,0,(structsockaddr*)
&cli,&n))<0){
perror("recvfrom");
}else{
buf=0;
fprintf(stdout,"Mensajedesde%s:
%s
",
inet_ntoa(cli.sin_addr),buf);
}
}
}
六、内核与组播
在上面的例子中我们看到:
如果一个进程要加入到组播组中,就要使用setsockopt()函数在IP层设置IP_ADD_MEMBERSHIP。
在/usr/src/linux/net/ipv4/ip_sockglue.c文件中可以找见该函数的源代码。
其中设置IP_ADD_MEMBERSHIP和IP_DROP_MEMBERSHIP的部分代码如下:
structip_mreqnmreq;
if(optlen return-EINVAL;
if(optlen>=sizeof(structip_mreqn)){
if(copy_from_user(&mreq,optval,sizeof(mreq)))
return-EFAULT;
}else{
memset(&mreq,0,sizeof(mreq));
if(copy_from_user(&mreq,optval,sizeof(structip_mreq)))
return-EFAULT;
}
if(optname==IP_ADD_MEMBERSHIP)
returnip_mc_join_group(sk,&mreq);
else
returnip_mc_leave_group(sk,&mreq);
程序一开始先检查输入参数ip_mreq结构的长度是否正确,并将其从用户区复制到内核区。
在得到参数的值后,接着调用ip_mc_join_group()加入到组播组或调用ip_mc_leave_group()退出组播组。
在/usr/src/linux/net/ipv4/igmp.c中可以找到这些函数的代码。
加入组播组的源程序代码如下:
intip_mc_join_group(structsock*sk,structip_mreqn*imr)
{
interr;
u32addr=imr->imr_multiaddr.s_addr;