多播的实现和需要注意的问题

网络 网络管理
网络中存在三种传输概念,单播,多播,广播,单播和广播大家可能都很了解,单播,连接的建立是一对一的,广播则是向一个网络内所有用户发送。

前段时间研究了一小段时间的网络多播问题,自己很有感触,把自己的经历写出来,希望有需要的可以少走一些弯路。

先说一下原理,我觉得这个还是需要说一下的。

网络中存在三种传输概念,单播,多播,广播,单播和广播大家可能都很了解,单播,连接的建立是一对一的,广播则是向一个网络内所有用户发送。

我们这里只说多播,多播的好处我就不说了,节省带宽什么的。

其实我个人觉得,单播多播都可以看错是某种意义上的广播,单播可以理解为网络只有一个用户,多播则可以理解为是受限制的一组广播用户(指定的一组用户)。

网络中存在五种IP地址,A,B,C,D,E类

需要明白的一点事,IP地址分为两部分,IP=类别+网络号+主机号

多播的实现和需要注意的问题

其中,对于A类地址来说,10.0.0.0 ~ 10.255.255.255为私有地址,127.0.0.0~127.255.255.255为回环地址,主机ID全0标识一个网络,主机ID全1表示广播地址,B类地址:172.16.0.0 ~ 172.31.255.255为私有地址,主机ID全0标识一个网络,主机ID全1表示广播地址,C类地址:192.168.0.0 ~ 192.168.255.255为私有地址(这个应该很熟悉吧),主机ID全0标识一个网络,主机ID全1表示广播地址。

组播对应的MAC地址:01-00-5e-xx-xx-xx

映射关系:

 

 

多播只会由感兴趣的端口接收,他是怎么知道这些端口的呢?这里就要说D类地址了。这里一定要理解一个概念:多播组。多播的数据是定向的发给一个多播组的,这样凡是多播组内的成员就会收到数据,有人问了,网络上有那么多多播组,是怎么知道要发给哪个组。这里就是D类地址了,D类地址充当了多播组的标识,记住,仅仅是标识。可以理解为,多播组的目的地址,多播组的ID。所有的主机可以选择加入多播组,也就是被标记为一个该多播组的一个ID。如何加入多播组是技术问题,我们后面讲。

那么接下来的问题是,分布在全球的这么多台主机,如果美国的一台主机加入了这个多播组,英国的一个主机也加入了同样一个多播组,而我源头是中国这边的主机,那么他怎么发过去呢?

需要考虑的问题,数据包如何到达子网路由器(主机-路由器之间的组成员关系协议),数据包如何在公网内路由(路由器-路由器之间的组播路由协议)(转发规则),数据如何被目的子网路由器接受并转发。

首先,这个数据包要能到达你所在的子网的路由器,这一步如何实现的?答案,是IGMP协议。

IGMP(Internet Group Management Protocol),看名字就可以看出是因特网组播管理协议。是主机与路由器之间唯一的信令协议。目前有三个版本,V1,V2,V3(不同之处主要是V1,V2,V2是主动离开组播组,V1则是不会主动离开).通过用wireshark软件抓包测试,你会发现,目前网络上大部分是V2版本的组播协议包。主机向本地路由器发送一个IGMP,加入相对应的组播(组播地址端口,自己定义)。这样主机是可以加入组播了,但是到来的组播数据包是如何知道数据包要发给谁呢?还是IGMP!当发现有数据包来的时候,本地路由器向本地子网内的主机发送一个查询报文(IGMP),加入了多播组的主机则会发送一个回复给路由器(IGMP包),那后面就会转发此数据包了。如果主机要离开组播组怎么办呢?也是IGMP!主机只需要向路由器发送一个离开的消息(IGMP包)给路由器就可以了。

数据发送到路由器后,路由器根据什么将数据包转发到其他路由器呢(公网内路由器的转发)?答案是:域内组播路由协议及域间组播路由协议。其实个人感觉不需要区分这个域间和域内路由,我们只需要关心,数据包可以在因特网上自由转发就可以了。这里需要知道的是两个域内路由协议,PIM-SM,PIM-DM,DVMRP(主要区别是密集模式和稀疏模式区别)。路由器间的转发需要的是这几个路由协议,原理在网上一搜一大堆,我就不讲了。这几个协议主要在转发,邻居发现什么的有些区别,比如说剪枝策略。

组播的转发利用了一个叫逆向路径转发策略(RPF),RPF协议决定是否转发次数据包以及丢弃掉。

路由器检查到达组播包的源地址,如果信息包是在可返回源站点的接口上到达,则RPF检查成功,信息包被转发如果RPF检查失败,丢弃信息包。

大家这个时候可能对这个有些概念,组播说的也很神乎,比如很省带宽,毕竟是一发多,只需要发送一份,但是可以很多都接受。比单播好用多了,那你可能会问,那组播的应用应该很广了?答案是肯定的,组播的应用很多,比如多媒体会议,联网游戏等。但是有个问题目前确实致命的!应用的条件:路由器没有开启这个功能!

大致可以说一下组播路由的过程,数据包从源端口出发,经路由器转发(这个应该是所有路由器都会经过,可是所有哦),然后到达有目的组播的成员则转发给他。这个时候你可能会发现一个问题,如果组播大规模应用的话,那网络上这种数据包会非常多,毕竟谁都可以创建组播组,然后发送,路由器是要进行转发的。

我自己本来也是想实现一个组播功能的类似于视频会议的应用的,但是测试的时候发现,数据包就是冲不出去内网,只能在局域网内转(局域网内可以收到)。后来发现是路由器虽然有这个功能,但是默认都给关闭了。记得当时查这个资料的时候,在一个路由器管理员配置的一个BBS,上面一个人发帖,有人回答说:管理员如果开启这个功能,那只能说有病。足可以看出,目前路由器对组播支持的尴尬处境。

所以这里只是提醒一下做这个的朋友,路由器对这个支持不是很好,如果要做的话多考虑一下。可能我理解不对,如果有做出来这个的,希望能提供观点哈。

附一个简单的多播程序,同一子网下运行无误,只需要打开这个客户端就可以。两个进程,一个负责发,一个负责接收。

  1. #include <iostream>   
  2. #include <winsock2.h> //注意这里的include文件顺序   
  3. #include <Ws2tcpip.h>   
  4. #include <process.h> //_beginthread要求   
  5.    
  6. #pragma comment(lib, "ws2_32.lib")   
  7.    
  8. using namespace std;   
  9.    
  10. const char* MULTICAST_IP = "230.1.1.99"; //多播组地址   
  11. const int MULTICAST_PORT = 2002; //多播组端口   
  12.    
  13. const int BUFFER_SIZE = 1024;   
  14.    
  15. void do_send(void* arg); //读取用户输入并发送到多播组线程函数   
  16. void do_read(void* arg); //读物多播组数据函数   
  17.    
  18. int main()   
  19. {   
  20.     //这个结构被用来存储被WSAStartup函数调用后返回的Windows Sockets数据。   
  21.     //它包含Winsock.dll执行的数据。   
  22.         WSAData wsaData;   
  23.    
  24.         /*  
  25.         使用Socket的程序在使用Socket之前必须调用WSAStartup函数。该函数的第一个参数指明程序请求使用的Socket版本,其中高位字节指明副版本、低位字节指明主版本;操作系统利用第二个参数返回请求的Socket的版本信息。  
  26.         加载Windows套接字动态链接库  
  27.         */   
  28.         if( WSAStartup(MAKEWORD(2,2), &wsaData) != 0 )   
  29.         {   
  30.         cout <<"Error in WSAStartup"<<endl;   
  31.         return 0;   
  32.         }   
  33.    
  34.         SOCKET server;   
  35.         //原始的方式   
  36.         /*  
  37.         第一个参数指定应用程序使用的通信协议的协议族,对于TCP/IP协议族,该参数置AF_INET;  
  38.         第二个参数指定要创建的套接字类型,流套接字类型为SOCK_STREAM、数据报套接字类型为SOCK_DGRAM、  
  39.         原始套接字SOCK_RAW(WinSock接口并不适用某种特定的协议去封装它,而是由程序自行处理数据包以及协议首部);  
  40.         另一种方式WSASocket  
  41.         */   
  42.         server = socket(AF_INET, SOCK_DGRAM, 0); //创建一个UDP套接口   
  43.         cout<<"create socket: "<<server<<endl;   
  44.    
  45.         int ret ;   
  46.    
  47.         const int on = 1; //允许程序的多个实例运行在同一台机器上   
  48.         /*  
  49.         调用setsockopt()函数为套接字设置SO_REUSEADDR选项,以允许套接字绑扎到一个已在使用的地址上。设置套接字的选项  
  50.         */   
  51.         ret = setsockopt(server, SOL_SOCKET, SO_REUSEADDR, (char *)&on, sizeof(on));   
  52.         if( ret == SOCKET_ERROR )   
  53.         {   
  54.         WSACleanup();   
  55.            
  56.         cout<<"Error in setsockopt(SO_REUSEADDR): "<<WSAGetLastError()<<endl;   
  57.         return 0;   
  58.         }   
  59.            
  60.         const int routenum = 10;   
  61.    
  62.         //ret = setsockopt(server,IPPROTO_IP,IP_MULTICAST_TTL,\   
  63.            
  64.         ret = setsockopt(server,IPPROTO_IP,IP_MULTICAST_TTL,\   
  65.         (char*)&routenum,sizeof(routenum));   
  66.         if( ret == SOCKET_ERROR )   
  67.         {   
  68.         WSACleanup();   
  69.    
  70.         cout<<"Error in setsockopt(IP_MULTICAST_TTL): "<<WSAGetLastError()<<endl;   
  71.         return 0;   
  72.         }   
  73.    
  74.         const int loopback = 0; //禁止回馈   
  75.         //使组播报文环路有效或无效   
  76.         ret = setsockopt(server,IPPROTO_IP,IP_MULTICAST_LOOP,\   
  77.         (char*)&loopback,sizeof(loopback));   
  78.         if( ret == SOCKET_ERROR )   
  79.         {   
  80.         WSACleanup();   
  81.    
  82.         cout<<"Error in setsockopt(IP_MULTICAST_LOOP): "<<WSAGetLastError()<<endl;   
  83.         return 0;   
  84.         }   
  85.         //地址信息,local设置为多播组端口   
  86.         sockaddr_in local;   
  87.         memset(&local, 0, sizeof(local));   
  88.         local.sin_family = AF_INET;   
  89.         local.sin_port = htons(MULTICAST_PORT);   
  90.         //INADDR_ANY为0.0.0.0   
  91.         local.sin_addr.S_un.S_addr = INADDR_ANY;   
  92.            
  93.         ret = bind(server, (sockaddr*)(&local), sizeof(local));   
  94.    
  95.         if( ret == SOCKET_ERROR )   
  96.         {   
  97.         WSACleanup();   
  98.    
  99.         cout<<"Error in bind: "<<WSAGetLastError()<<endl;   
  100.         return 0;   
  101.         }   
  102.         //多播组结构   
  103.         ip_mreq mreq;   
  104.         memset(&mreq, 0, sizeof(mreq));   
  105.         //本机地址   
  106.         mreq.imr_interface.S_un.S_addr = INADDR_ANY;   
  107.         //点分十进制地址转化为IP地址   
  108.         mreq.imr_multiaddr.S_un.S_addr = inet_addr(MULTICAST_IP);   
  109.    
  110.         //加入一个多播组   
  111.         ret = setsockopt(server,IPPROTO_IP,IP_ADD_MEMBERSHIP,\   
  112.         (char*)&mreq,sizeof(mreq));   
  113.         if( ret == SOCKET_ERROR )   
  114.         {   
  115.         WSACleanup();   
  116.    
  117.         cout<<"Error in setsockopt(IP_ADD_MEMBERSHIP): "<<WSAGetLastError()<<endl;   
  118.         return 0;   
  119.         }   
  120.    
  121.         //创建了两个线程,一个读用户输入并发送,一个读多播组数据   
  122.         HANDLE hHandle[2];   
  123.         hHandle[0] = (HANDLE)_beginthread(do_send,0,(void*)server);   
  124.         hHandle[1] = (HANDLE)_beginthread(do_read,0,(void*)server);   
  125.    
  126.         //如果用户输入结束,程序就终止了   
  127.         WaitForSingleObject(hHandle[0], INFINITE);   
  128.    
  129.         WSACleanup();   
  130.    
  131.         return 0;   
  132. }   
  133.    
  134. void do_send(void* arg)   
  135.         {   
  136.         SOCKET server = (SOCKET)arg;   
  137.    
  138.         char sendline[BUFFER_SIZE+1];   
  139.    
  140.         sockaddr_in remote;   
  141.         memset(&remote, 0, sizeof(remote));   
  142.         remote.sin_addr.s_addr = inet_addr ( MULTICAST_IP );   
  143.         remote.sin_family = AF_INET ;   
  144.         remote.sin_port = htons(MULTICAST_PORT);   
  145.    
  146.         for(;;) //读取用户输入知道用户输入"end"   
  147.         {   
  148.         cin.getline(sendline, BUFFER_SIZE);   
  149.    
  150.         if(strncmp(sendline,"end",3)==0)   
  151.             break;   
  152.    
  153.         //发送用户输入的数据到多播组   
  154.         sendto(server, sendline, strlen(sendline), 0, (sockaddr*)(&remote), sizeof(remote));    
  155.         }   
  156.    
  157.         cout<<"do_send end..."<<endl;   
  158. }   
  159.    
  160. void do_read(void* arg)   
  161. {   
  162.         SOCKET server = (SOCKET)arg;   
  163.    
  164.         char buf[BUFFER_SIZE+1];   
  165.         int ret;   
  166.    
  167.         sockaddr_in client;   
  168.         int clientLen;   
  169.    
  170.         for(;;) //一直读取知道主线程终止   
  171.         {   
  172.         clientLen = sizeof(client);   
  173.         memset(&client, 0, clientLen);   
  174.    
  175.         ret = recvfrom(server, buf, BUFFER_SIZE, 0, (sockaddr*)(&clientLen), &clientLen);   
  176.         if ( ret == 0) //do_read在用户直接回车发送了一个空字符串   
  177.         {   
  178.         continue;   
  179.         }   
  180.         else if( ret == SOCKET_ERROR )   
  181.         {   
  182.         if( WSAGetLastError() == WSAEINTR ) //主线程终止recvfrom返回的错   
  183.         break;   
  184.    
  185.         cout<<"Error in recvfrom: "<<WSAGetLastError()<<endl;   
  186.         break ;   
  187.         }   
  188.         buf[ret] = '\0';   
  189.         cout<<"received: "<<buf<<endl;   
  190.         }   
  191.    
  192.         cout<<"do_read end..."<<endl;   
  193. }   

 

责任编辑:林琳 来源: CSDN博客
相关推荐

2020-10-26 14:01:22

Java泛型

2013-09-29 10:36:08

VMware虚拟化

2009-04-23 14:30:19

UML建模

2023-10-04 00:03:00

SQL数据库

2021-07-30 09:00:40

鸿蒙HarmonyOS应用

2010-10-08 09:38:42

mysql修改表

2009-08-10 15:56:35

802局域网网桥兼容性

2010-03-26 14:23:47

Python入门

2011-05-26 17:37:11

Ajax

2010-04-21 10:04:33

Oracle移植

2013-09-03 13:01:01

团队管理团队

2021-02-05 17:35:07

数据高管CIO技术

2016-12-26 18:51:34

AndroidJavascriptJSONObject

2017-03-17 11:00:08

数字化陈勇Gartner

2010-07-12 13:00:49

UML建模

2013-05-14 09:17:10

企业无线网无缝漫游无线网

2012-07-04 14:40:37

Ajax

2009-11-10 14:15:40

2013-08-21 09:38:21

802.11acMeru5G wifi

2011-12-21 09:54:15

项目经理
点赞
收藏

51CTO技术栈公众号