单播、多播(也称组播)、广播
linux网络编程之-----多播(组播)编程
转自 http://blog.csdn.net/jmq_0000/article/details/7095727
什么是多播
单播用于两个主机之间的端对端通信,广播用于一个主机对整个局域网上所有主机上的数据通信。单播和广播是两个极端,要么对一个主机进行通信,要么对整个 局域网上的主机进行通信。实际情况下,经常需要对一组特定的主机进行通信,而不是整个局域网上的所有主机,这就是多播的用途。
多播,也称为“组播”,将局域网中同一业务类型主机进行了逻辑上的分组,进行数据收发的时候其数据仅仅在同一分组中进行,其他的主机没有加入此分组不能收发对应的数据。
多播的地址是特定的,D类地址用于多播。D类IP地址就是多播IP地址,即224.0.0.0至239.255.255.255之间的IP地址,并被划分为局部连接多播地址、预留多播地址和管理权限多播地址3类:
局部多播地址:在224.0.0.0~224.0.0.255之间,这是为路由协议和其他用途保留的地址,路由器并不转发属于此范围的IP包。
预留多播地址:在224.0.1.0~238.255.255.255之间,可用于全球范围(如Internet)或网络协议。
管理权限多播地址:在239.0.0.0~239.255.255.255之间,可供组织内部使用,类似于私有IP地址,不能用于Internet,可限制多播范围。
属于永久组的地址:
IP到以太网地址映射224.0.0.1 所有组播主机
224.0.0.2 所有组播路由器
224.0.0.4 DRMRP路由器
224.0.0.5 所有OSPF的路由器
224.0.0.6 OSPF指派路由器
224.0.0.9 RPIv2路由器
224.0.0.10 EIGRP路由器
224.0.0.13 PIM路由器
224.0.0.22 IGMPv3
224.0.0.25 RGMP
224.0.1.1 NTP网络时间协议
因为以太网支持多种协议,所以要采取措施分配多播地址,避免冲突。IEEE管理以太网多播地址分配。IEEE把一块以太网多播地址分给IANA以支持IP多播。块的地址都以01:00:5e开头。第25位为0,低23位为IPv4组播地址的低23位。IPv4组播地址与MAC地址的映射关系如图所示:
由于多播组号中的最高5bit在映射过程中被忽略,因此每个以太网多播地址对应的多播组是不唯一的。32个不同的多播组号被映射为一个以太网地址。例如,多播地址
224.128.64.32(十六进制e0.80.40.20)和224.0.64.32(十六进制e0.00.40.20)都映射为同一以太网地址01:00:5e:00:40:20。
多播主机
多播主机分为三个级别:
0级:主机不能发送或接收I P多播。
这种主机应该自动丢弃它收到的具有D类目的地址的分组。
1级:主机能发送但不能接收I P多播。
在向某个I P多播组发送数据报之前,并不要求主机加入该组。多播数据报的发送方式与单播一样,除了多播数据报的目的地址是 I P多播组之外。网络驱动器必须能 够识别出这个地址,把在本地网络上多播数据报。
2级:主机能发送和接收I P多播。
为了接收I P多播,主机必须能够加入或离开多播组,而且必须支持IGMP,能够在至少一个接口上交换组成员信息。多接口主机必须支持在它的接口的一个子网上的多播N et/3符合2级主机要求,可以完成多播路由器的工作。与单播IP选路一样,我们假定所描述的系统是一个多播路由器,并加上了Net/3多播选路的程序。
linux多播编程
linux多播编程步骤:
1>建立一个socket;
2>设置多播的参数,例如超时时间TTL,本地回环许可LOOP等
3>加入多播组
4>发送和接收数据
5>从多播组离开
多播程序设计使用setsockopt()函数和getsockopt()函数来实现,组播的选项是IP层的。
getsockopt()/setsockopt()的选项 |
含 义 |
IP_MULTICAST_TTL |
设置多播组数据的TTL值 |
IP_ADD_MEMBERSHIP |
在指定接口上加入组播组 |
IP_DROP_MEMBERSHIP |
退出组播组 |
IP_MULTICAST_IF |
获取默认接口或设置接口 |
IP_MULTICAST_LOOP |
禁止组播数据回送 |
1.选项IP_MULTICASE_TTL
选项IP_MULTICAST_TTL允许设置超时TTL,范围为0~255之间的任何值,例如:
unsigned char ttl=255;setsockopt(s,IPPROTO_IP,IP_MULTICAST_TTL,&ttl,sizeof(ttl));
2.选项IP_MULTICAST_IF
选项IP_MULTICAST_IF用于设置组播的默认默认网络接口,会从给定的网络接口发送,另一个网络接口会忽略此数据。例如:
struct in_addr addr;
setsockopt(s,IPPROTO_IP,IP_MULTICAST_IF,&addr,sizeof(addr));
参数addr是希望多播输出接口的IP地址,使用INADDR_ANY地址回送到默认接口。
默认情况下,当本机发送组播数据到某个网络接口时,在IP层,数据会回送到本地的回环接口,选项IP_MULTICAST_LOOP用于控制数据是否回送到本地的回环接口。例如:
unsigned char loop;setsockopt(s,IPPROTO_IP,IP_MULTICAST_LOOP,&loop,sizeof(loop));参数loop设置为0禁止回送,设置为1允许回送。
3.选项IP_ADD_MEMBERSHIP和IP_DROP_MEMBERSHIP
加入或者退出一个多播组,通过选项IP_ADD_MEMBERSHIP和IP_DROP_MEMBER- SHIP,对一个结构struct ip_mreq类型的变量进行控制,struct ip_mreq原型如下:
{
struct in_addr imn_multiaddr; /*加入或者退出的广播组IP地址*/
struct in_addr imr_interface; /*加入或者退出的网络接口IP地址*/
};
struct ip_mreq mreq;
setsockopt(s,IPPROTO_IP,IP_ADD_MEMBERSHIP,&mreq,sizeof(mreq));
多播编程实例 服务器端 下面是一个多播服务器的例子。多播服务器的程序设计很简单,建立一个数据包套接字,选定多播的IP地址和端口,直接向此多播地址发送数据就可以了。多播服务器的程序设计,不需要服务器加入多播组,可以直接向某个多播组发送数据。 下面的例子持续向多播IP地址"224.0.0.100"的8888端口发送数据"BROADCAST TEST DATA",每发送一次间隔5s。 /* *broadcast_server.c - 多播服务程序 */ #define MCAST_PORT 8888; #define MCAST_ADDR "224.0.0.100"/ /*一个局部连接多播地址,路由器不进行转发*/ #define MCAST_DATA "BROADCAST TEST DATA" /*多播发送的数据* #define MCAST_INTERVAL 5 /*发送间隔时间*/ int main(int argc, char*argv) { int s; struct sockaddr_in mcast_addr; s = socket(AF_INET, SOCK_DGRAM, 0); /*建立套接字*/ if (s == -1) { perror("socket()"); return -1; } memset(&mcast_addr, 0, sizeof(mcast_addr));/*初始化IP多播地址为0*/ mcast_addr.sin_family = AF_INET; /*设置协议族类行为AF*/ mcast_addr.sin_addr.s_addr = inet_addr(MCAST_ADDR);/*设置多播IP地址*/ mcast_addr.sin_port = htons(MCAST_PORT); /*设置多播端口*/ /*向多播地址发送数据*/ while(1) { int n = sendto(s, /*套接字描述符*/ MCAST_DATA, /*数据*/ sizeof(MCAST_DATA), /*长度*/ 0, (struct sockaddr*)&mcast_addr, sizeof(mcast_addr)) ; if( n < 0) { perror("sendto()"); return -2; } sleep(MCAST_INTERVAL); /*等待一段时间*/ } return 0; } 客户端 多播组的IP地址为224.0.0.88,端口为8888,当客户端接收到多播的数据后将打印 出来。 客户端只有在加入多播组后才能接受多播组的数据,因此多播客户端在接收多播组的数据之前需要先加入多播组,当接收完毕后要退出多播组。 /* *broadcast_client.c - 多播的客户端 */ #define MCAST_PORT 8888; #define MCAST_ADDR "224.0.0.100" /*一个局部连接多播地址,路由器不进行转发*/ #define MCAST_INTERVAL 5 /*发送间隔时间*/ #define BUFF_SIZE 256 /*接收缓冲区大小*/ int main(int argc, char*argv[]) { int s; /*套接字文件描述符*/ struct sockaddr_in local_addr; /*本地地址*/ int err = -1; s = socket(AF_INET, SOCK_DGRAM, 0); /*建立套接字*/ if (s == -1) { perror("socket()"); return -1; } /*初始化地址*/ memset(&local_addr, 0, sizeof(local_addr)); local_addr.sin_family = AF_INET; local_addr.sin_addr.s_addr = htonl(INADDR_ANY); local_addr.sin_port = htons(MCAST_PORT); /*绑定socket*/ err = bind(s,(struct sockaddr*)&local_addr, sizeof(local_addr)) ; if(err < 0) { perror("bind()"); return -2; } /*设置回环许可*/ int loop = 1; err = setsockopt(s,IPPROTO_IP, IP_MULTICAST_LOOP,&loop, sizeof(loop)); if(err < 0) { perror("setsockopt():IP_MULTICAST_LOOP"); return -3; } struct ip_mreq mreq; /*加入多播组*/ mreq.imr_multiaddr.s_addr = inet_addr(MCAST_ADDR); /*多播地址*/ mreq.imr_interface.s_addr = htonl(INADDR_ANY); /*网络接口为默认*/ /*将本机加入多播组*/ err = setsockopt(s, IPPROTO_IP, IP_ADD_MEMBERSHIP,&mreq, sizeof (mreq)); if (err < 0) { perror("setsockopt():IP_ADD_MEMBERSHIP"); return -4; } int times = 0; int addr_len = 0; char buff[BUFF_SIZE]; int n = 0; /*循环接收多播组的消息,5次后退出*/ for(times = 0;times<5;times++) { addr_len = sizeof(local_addr); memset(buff, 0, BUFF_SIZE); /*清空接收缓冲区*/ /*接收数据*/ n = recvfrom(s, buff, BUFF_SIZE, 0,(struct sockaddr*)&local_addr, &addr_len); if( n== -1) { perror("recvfrom()"); } /*打印信息*/ printf("Recv %dst message from server:%s\n", times, buff); sleep(MCAST_INTERVAL); } /*退出多播组*/ err = setsockopt(s, IPPROTO_IP, IP_DROP_MEMBERSHIP,&mreq, sizeof (mreq)); close(s); return 0; }
Windows用Visual C++实现局域网IP多播
http://dev.yesky.com/460/2255460.shtml http://developer.51cto.com/art/200708/53619.htm
在局域网中,管理员常常需要将某条信息发送给一组用户。如果使用一对一的发送方法,虽然是可行的,但是过于麻烦,也常会出现漏发、错发。为了更有效的解决这种组通信问题,出现了一种多播技术(也常称为组播通信),它是基于IP层的通信技术。为了帮助读者理解,下面将简要的介绍一下多播的概念。 众所周知,普通IP通信是在一个发送者和一个接收者之间进行的,我们常把它称为点对点的通信,但对于有些应用,这种点对点的通信模式不能有效地满足实际应用的需求。例如:一个数字电话会议系统由多个会场组成,当在其中一个会场的参会人发言时,要求其它会场都能即时的得到此发言的内容,这是一个典型的一对多的通信应用,通常把这种一对多的通信称为多播通信。采用多播通信技术,不仅可以实现一个发送者和多个接收者之间进行通信的功能,而且可以有效减轻网络通信的负担,避免资源的无谓浪费。 广播也是一种实现一对多数据通信的模式,但广播与多播在实现方式上有所不同。广播是将数据从一个工作站发出,局域网内的其他所有工作站都能收到它。这一特征适用于无连接协议,因为LAN上的所有机器都可获得并处理广播消息。使用广播消息的不利之处是每台机器都必须对该消息进行处理。多播通信则不同,数据从一个工作站发出后,如果在其它LAN上的机器上面运行的进程表示对这些数据"有兴趣",多播数据才会制给它们。 本实例由Sender和Receiver两个程序组成,Sender用户从控制台上输入多播发送数据,Receiver端都要求加入同一个多播组,完成接收Sender发送的多播数据。 一、实现方法 1、 协议支持 并不是所有的协议都支持多播通信,对Win32平台而言,仅两种可从WinSock内访问的协议(IP/ATM)才提供了对多播通信的支持。因通常通信应用都建立在TCP/IP协议之上的,所以本文只针对IP协议来探讨多播通信技术。 支持多播通信的平台包括Windows CE 2.1、Windows 95、Windows 98、Windows NT 4、Windows 2000和WindowsXP。自2.1版开始,Windows CE才开始实现对IP多播的支持。本文实例建立在WindowsXP专业版平台上。 2、多播地址 IP采用D类地址来支持多播。每个D类地址代表一组主机。共有28位可用来标识小组。所以可以同时有多达25亿个小组。当一个进程向一个D类地址发送分组时,会尽最大的努力将它送给小组的所有成员,但不能保证全部送到。有些成员可能收不到这个分组。举个例子来说,假定五个节点都想通过I P多播,实现彼此间的通信,它们便可加入同一个组地址。全部加入之后,由一个节点发出的任何数据均会一模一样地复制一份,发给组内的每个成员,甚至包括始发数据的那个节点。D类I P地址范围在244.0.0.0到239.255.255.255之间。它分为两类:永久地址和临时地址。永久地址是为特殊用途而保留的。比如,244.0.0.0根本没有使用(也不能使用),244.0.0.1代表子网内的所有系统(主机),而244.0.0.2代表子网内的所有路由器。在RFC 1700文件中,提供了所有保留地址的一个详细清单。该文件是为特殊用途保留的所有资源的一个列表,大家可以找来作为参考。"Internet分配数字专家组"(I A N A)负责着这个列表的维护。在表1中,我们总结了目前标定为"保留"的一些地址。临时组地址在使用前必须先创建,一个进程可以要求其主机加入特定的组,它也能要求其主机脱离该组。当主机上的最后一个进程脱离某个组后,该组地址就不再在这台主机中出现。每个主机都要记录它的进程当前属于哪个组。 表1 部分永久地址说明 地 址 说 明 244.0.0.1 基本地址(保留) 244.0.0.1 子网上的所有系统 244.0.0.2 子网上的所有路由器 244.0.0.5 子网上所有OSPF路由器 244.0.0.6 子网上所有指定的OSPF路由器 244.0.0.9 RIP第2版本组地址 244.0.1.1 网络时间协议 244.0.1.24 WINS服务器组地址 3、 多播路由器 多播由特殊的多播路由器来实现,多播路由器同时也可以是普通路由器。各个多播路由器每分钟发送一个硬件多播信息给子网上的主机(目的地址为244.0.0.1),要求它们报告其进程当前所属的是哪一组,各主机将它感兴趣的D类地址返回。这些询问和响应分组使用IGMP(Internet group management protocol),它大致类似于ICMP。它只有两种分组:询问和响应,都有一个简单的固定格式,其中有效载荷字段的第一个字段是一些控制信息,第二字段是一个D类地址,在RFC1112中有详细说明。 多播路由器的选择是通过生成树实现的,每个多播路由器采用修改过的距离矢量协议和其邻居交换信息,以便向每个路由器为每一组构造一个覆盖所有组员的生成树。在修剪生成树及删除无关路由器和网络时,用到了很多优化方法。 4.库支持 WinSock提供了实现多播通信的API函数调用。针对IP多播,WinSock提供了两种不同的实现方法,具体取决于使用的是哪个版本的WinSock。第一种方法是WinSock1提供的,要求通过套接字选项来加入一个组;另一种方法是WinSock2提供的,它是引入一个新函数,专门负责多播组的加入,这个函数便是WSAJoinLeaf,它是基层协议是无关的。本文将通过一个多播通信的实例的实现过程,来讲叙多播实现的主要步骤。因为Window98以后版本都安装了Winsock2.0以上版本,所以本文实例在WinSock2.0平台上开发的,但在其中对WinSock1实现不同的地方加以说明。 二、编程步骤 1、启动Visual C++6.0,创建一个控制台项目工程MultiCase。在此项目工程中添加Sender和Receiver两个项目。 Receiver项目实现步骤: (1)、创建一个SOCK_DGRAM类型的Socket。 (2)、将此Socket绑定到本地的一个端口上,为了接收服务器端发送的多播数据。 (3)、加入多播组。 ①、 WinSock2中引入一个WSAJoinLeaf,此函数原型如下: SOCKET WSAJoinLeaf( SOCKET s, const struct sockaddr FAR *name, int namelen, LPWSABUF lpCallerData, LPWSABUF lpCalleeData, LPQOS lpSQOS, LPQOS lpGQOS, DWORD dwFlags ); 其中,第一个参数s代表一个套接字句柄,是自WSASocket返回的。传递进来的这个套接 字必须使用恰当的多播标志进行创建;否则的话WSAJoinLeaf就会失败,并返回错误WSAEINVAL。第二个参数是SOCKADDR(套接字地址)结构,具体内容由当前采用的协议决定,对于IP协议来说,这个地址指定的是主机打算加入的那个多播组。第三个参数namelen(名字长度)是用于指定name参数的长度,以字节为单位。第四个参数lpCallerData(呼叫者数据)的作用是在会话建立之后,将一个数据缓冲区传输给自己通信的对方。第五个参数lpCalleeData(被叫者数据)用于初始化一个缓冲区,在会话建好之后,接收来自对方的数据。注意在当前的Windows平台上,lpCallerData和lpCalleeData这两个参数并未真正实现,所以均应设为NULL。LpSQOS和lpGQOS这两个参数是有关Qos(服务质量)的设置,通常也设为NULL,有关Qos内容请参阅MSDN或有关书籍。最后一个参数dwFlags指出该主机是发送数据、接收数据或收发兼并。该参数可选值分别是:JL_SENDER_ONLY、JL_RECEIVER_ONLY或者JL_BOTH。 ②、在WinSock1平台上加入多播组需要调用setsockopt函数,同时设置IP_ADD_MEMBERSHIP选项,指定想加入的那个组的地址结构。具体实现代码将在下面代码注释列出。 (4)、接收多播数据。 Sender实现步骤: (1)、创建一个SOCK_DGRAM类型的Socket。 (2)、加入多播组。 (3)、发送多播数据。 3、编译两个项目,在局域网中按如下步骤测试: (1)、将Sender.exe拷贝到发送多播数据的PC上。 (2)、将Receiver.exe拷贝到多个要求接收多播数据的PC上。 (3)、各自运行相应的程序。 (4)、在Sender PC上输入多播数据后,你就可以在Receiver PC上看到输入的多播数据。 //////////////////////////////Receiver.c程序代码: #include <winsock2.h> #include <ws2tcpip.h> #include <stdio.h> #include <stdlib.h> #define MCASTADDR "233.0.0.1" //本例使用的多播组地址。 #define MCASTPORT 5150 //绑定的本地端口号。 #define BUFSIZE 1024 //接收数据缓冲大小。 int main( int argc,char ** argv) { WSADATA wsd; struct sockaddr_in local,remote,from; SOCKET sock,sockM; TCHAR recvbuf[BUFSIZE]; /*struct ip_mreq mcast; // Winsock1.0 */ int len = sizeof( struct sockaddr_in); int ret; //初始化WinSock2.2 if( WSAStartup( MAKEWORD(2,2),&wsd) != 0 ) { printf("WSAStartup() failed\n"); return -1; } /* 创建一个SOCK_DGRAM类型的SOCKET 其中,WSA_FLAG_MULTIPOINT_C_LEAF表示IP多播在控制面层上属于"无根"类型; WSA_FLAG_MULTIPOINT_D_LEAF表示IP多播在数据面层上属于"无根",有关控制面层和 数据面层有关概念请参阅MSDN说明。 */ if((sock=WSASocket(AF_INET,SOCK_DGRAM,0,NULL,0, WSA_FLAG_MULTIPOINT_C_LEAF|WSA_FLAG_MULTIPOINT_D_LEAF| WSA_FLAG_OVERLAPPED)) == INVALID_SOCKET) { printf("socket failed with:%d\n",WSAGetLastError()); WSACleanup(); return -1; } //将sock绑定到本机某端口上。 local.sin_family = AF_INET; local.sin_port = htons(MCASTPORT); local.sin_addr.s_addr = INADDR_ANY; if( bind(sock,(struct sockaddr*)&local,sizeof(local)) == SOCKET_ERROR ) { printf( "bind failed with:%d \n",WSAGetLastError()); closesocket(sock); WSACleanup(); return -1; } //加入多播组 remote.sin_family = AF_INET; remote.sin_port = htons(MCASTPORT); remote.sin_addr.s_addr = inet_addr( MCASTADDR ); /* Winsock1.0 */ /* mcast.imr_multiaddr.s_addr = inet_addr(MCASTADDR); mcast.imr_interface.s_addr = INADDR_ANY; if( setsockopt(sockM,IPPROTO_IP,IP_ADD_MEMBERSHIP,(char*)&mcast,sizeof(mcast)) == SOCKET_ERROR) { printf("setsockopt(IP_ADD_MEMBERSHIP) failed:%d\n",WSAGetLastError()); closesocket(sockM); WSACleanup(); return -1; } */ /* Winsock2.0*/ if(( sockM = WSAJoinLeaf(sock,(SOCKADDR*)&remote,sizeof(remote),NULL,NULL,NULL,NULL, JL_BOTH)) == INVALID_SOCKET) { printf("WSAJoinLeaf() failed:%d\n",WSAGetLastError()); closesocket(sock); WSACleanup(); return -1; } //接收多播数据,当接收到的数据为"QUIT"时退出。 while(1) { if(( ret = recvfrom(sock,recvbuf,BUFSIZE,0,(struct sockaddr*)&from,&len)) == SOCKET_ERROR) { printf("recvfrom failed with:%d\n",WSAGetLastError()); closesocket(sockM); closesocket(sock); WSACleanup(); return -1; } if( strcmp(recvbuf,"QUIT") == 0 ) break; else { recvbuf[ret] = '\0'; printf("RECV:' %s ' FROM <%s> \n",recvbuf,inet_ntoa(from.sin_addr)); } } closesocket(sockM); closesocket(sock); WSACleanup(); return 0; } /////////////////////////////////////////////////////////////////////Sender.c程序代码 #include <winsock2.h> #include <ws2tcpip.h> #include <stdio.h> #include <stdlib.h> #define MCASTADDR "233.0.0.1" //本例使用的多播组地址。 #define MCASTPORT 5150 //本地端口号。 #define BUFSIZE 1024 //发送数据缓冲大小。 int main( int argc,char ** argv) { WSADATA wsd; struct sockaddr_in remote; SOCKET sock,sockM; TCHAR sendbuf[BUFSIZE]; int len = sizeof( struct sockaddr_in); //初始化WinSock2.2 if( WSAStartup( MAKEWORD(2,2),&wsd) != 0 ) { printf("WSAStartup() failed\n"); return -1; } if((sock=WSASocket(AF_INET,SOCK_DGRAM,0,NULL,0, WSA_FLAG_MULTIPOINT_C_LEAF|WSA_FLAG_MULTIPOINT_D_LEAF| WSA_FLAG_OVERLAPPED)) == INVALID_SOCKET) { printf("socket failed with:%d\n",WSAGetLastError()); WSACleanup(); return -1; } //加入多播组 remote.sin_family = AF_INET; remote.sin_port = htons(MCASTPORT); remote.sin_addr.s_addr = inet_addr( MCASTADDR ); if(( sockM = WSAJoinLeaf(sock,(SOCKADDR*)&remote, sizeof(remote),NULL,NULL,NULL,NULL, JL_BOTH)) == INVALID_SOCKET) { printf("WSAJoinLeaf() failed:%d\n",WSAGetLastError()); closesocket(sock); WSACleanup(); return -1; } //发送多播数据,当用户在控制台输入"QUIT"时退出。 while(1) { printf("SEND : "); scanf("%s",sendbuf); if( sendto(sockM,(char*)sendbuf,strlen(sendbuf),0,(struct sockaddr*)&remote,sizeof(remote))==SOCKET_ERROR) { printf("sendto failed with: %d\n",WSAGetLastError()); closesocket(sockM); closesocket(sock); WSACleanup(); return -1; } if(strcmp(sendbuf,"QUIT")==0) break; Sleep(500); } closesocket(sockM); closesocket(sock); WSACleanup(); return 0; }
http://msdn.microsoft.com/en-us/library/ms741628%28v=vs.85%29.aspx
The WSAJoinLeaf function joins a leaf node into a multipoint session, exchanges connect data, and specifies needed quality of service based on the specified FLOWSPEC structures.
http://zh.wikipedia.org/wiki/%E5%A4%9A%E6%92%AD
解释了多播的应用范围。
http://zh.wikipedia.org/wiki/XMPP
解释了新型多播的协议, 可能以后还会出现更多。
主要说multicast, 因为视频传输都用多播。