UDP多播与广播的实践

原文在这里:http://blog.youkuaiyun.com/raoxuanxuan/article/details/20012931

最近项目的需要,了解了一下UDP多播与广播。

1. UDP多播

UDP多播是这样子的:老师要给班上学生通知消息,他要么一个个地给每个同学去说(这就是单播),要么他写在黑板上,每个同学进教室都可以看得见。后者就是多播,即说有一台主机(老师)往一个地址上(黑板上)发数据,可以有一群客户端(班上学生)同时去那个地址上拿数据(看黑板),就不用发送主机一个一个地往这些客户端所在的地址上去发了,那么自然会节约很多主机资源和网络资源。并不是随意一个地址就可以进行多播的,多播的地址是固定了的,只有224.0.0.0到239.255.255.255这一个范围,其中这些范围的地址又做了进一步划分,具体可以去Google一下。OK, 大概了解了多播这个东西,我们来看看代码:

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. #include <winsock.h>  
  2. #include <iostream>  
  3. #include <string>  
  4. using namespace std;  
  5. #pragma comment(lib,"WSOCK32.lib")  
  6. #define MULTICAST_IP "239.239.1.1"  
  7. #define MULTICAST_PORT 10086  
  8.   
  9. int _tmain(int argc, _TCHAR* argv[])  
  10. {  
  11.     //启动winsock环境  
  12.     WSADATA wsd;  
  13.     if(WSAStartup(MAKEWORD(2,2), &wsd) != 0)  
  14.     {  
  15.         cerr<<"Could not open Windows Connection.\n"<<endl;  
  16.         return 1;  
  17.     }  
  18.     //创建一个UDP Socket  
  19.     SOCKET clientSocket;  
  20.     clientSocket = socket(AF_INET, SOCK_DGRAM, 0);  
  21.     if(clientSocket == INVALID_SOCKET)  
  22.     {  
  23.         cerr<<"Could not create socket.\n"<<endl;  
  24.         WSACleanup();  
  25.         return 1;  
  26.     }  
  27.     //创建地址并绑定到该Socket上  
  28.     sockaddr_in clientAddress;  
  29.     memset(&clientAddress, 0, sizeof(sockaddr_in));  
  30.     clientAddress.sin_family = AF_INET;  
  31.     clientAddress.sin_addr.s_addr = INADDR_ANY;  
  32.     clientAddress.sin_port = htons(MULTICAST_PORT);  
  33.     if (bind(clientSocket, (struct sockaddr *) &clientAddress, sizeof(clientAddress)) < 0)      
  34.     {      
  35.         cerr<<"Could not bind socket to a sockaddr_in"<<endl;  
  36.         return 1;  
  37.     }      
  38.     //加入多播组  
  39.     struct ip_mreq mreq;      
  40.     mreq.imr_multiaddr.s_addr = inet_addr(MULTICAST_IP);      
  41.     mreq.imr_interface.s_addr = INADDR_ANY;  
  42.     if (setsockopt(clientSocket, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char *)&mreq, sizeof(mreq)) < 0)       
  43.     {      
  44.         int err = GetLastError();  
  45.         cerr<<"Could not add into MultiCast Group.\n"<<endl;  
  46.         return 1;  
  47.     }      
  48.     //接收数据  
  49.     char buff[20] = {0};  
  50.     while (1)       
  51.     {      
  52.         //ssize_t recvfrom(int s, void *buf, size_t len, int flags, struct sockaddr *from, socklen_t *fromlen);      
  53.         int addrlen = sizeof(sockaddr);    
  54.         int nBytes = 0;  
  55.         if((nBytes = recvfrom(clientSocket, buff, 20, 0,NULL,NULL)) < 0)  
  56.         {      
  57.             cerr<<"receive from multicast group failed"<<endl;    
  58.             return 1;  
  59.         }   
  60.         buff[nBytes]  ='\0';   
  61.         cout<<"Client Receive : "<<buff<<endl;  
  62.     }      
  63.     return 0;  
  64. }  
以上是一个简单的UDP多播客户端,从代码中我们可以看出来,客户端的代码的关键之处在于:
  1. 让客户端加入到多播组中,即用函数setsockopt(fd,IPPROTO_IP,IP_ADD_MEMBERSHIP,(char *)&mreq,sizeof(mreq),即可加入到ip_mrep结构体指明的多播地址中。
  2. 让客户端把socket绑定到服务器发送所用的端口的地址中。
[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. #define MULTICAST_IP "239.239.1.1"  
  2. #define MULTICAST_PORT 10086  
  3. #include <WinSock2.h>  
  4. #include <iostream>  
  5. #include <string>  
  6. using namespace std;  
  7. #pragma  comment(lib,"ws2_32.lib")  
  8.   
  9. int _tmain(int argc, _TCHAR* argv[])  
  10. {  
  11.     WSADATA wsd;  
  12.     if(WSAStartup(MAKEWORD(2,2), &wsd) != 0)  
  13.     {  
  14.         cerr<<"Could not open Windows Connection.\n"<<endl;  
  15.         return 1;  
  16.     }  
  17.   
  18.     SOCKET serverSocket;  
  19.     serverSocket = socket(AF_INET, SOCK_DGRAM, 0);  
  20.     if(serverSocket == INVALID_SOCKET)  
  21.     {  
  22.         cerr<<"Could not create socket.\n"<<endl;  
  23.         WSACleanup();  
  24.         return 1;  
  25.     }  
  26.   
  27.     sockaddr_in serverAddress;  
  28.     memset(&serverAddress, 0, sizeof(sockaddr_in));  
  29.     serverAddress.sin_family = AF_INET;  
  30.     serverAddress.sin_addr.s_addr = inet_addr(MULTICAST_IP);  
  31.     serverAddress.sin_port = htons(MULTICAST_PORT);  
  32.     std::string message = "Hello World!";  
  33.     while(1)  
  34.     {  
  35.         int error = sendto(serverSocket, message.c_str(), message.length(), 0, (sockaddr*)&serverAddress, sizeof(sockaddr));  
  36.         if(error < 0)  
  37.         {  
  38.             cerr<<"Send Message Failed..."<<endl;  
  39.             return 1;  
  40.         }  
  41.         cout<<"Server Send : " << message << endl;  
  42.         Sleep(1000);  
  43.     }  
  44.     return 0;  
  45. }  
以上是一个UDP多播的服务器代码。服务器代码的关键点只有一个就是把服务器的socket绑定到多播ip和多播端口构成的地址中,然后向这个地址发送数据即可。
注意到,在一般情况下,只有服务器的socket需要做bind操作,因为这样客户端的socket才能够知道要访问的服务器的IP和端口号。而客户端的socket是不需要bind操作的,协议栈会隐式地自动生成一个可用端口号再加上客户端本机的IP地址bind到其socket上。但是多播这里不同,服务器socket没有bind,客户端socket必须要bind到一个地址上,这个地址的端口号是服务器多播的发送端口号(IP随意,一般就是INADDR_ANY,表示由协议栈来完成bind)。我们可以这样理解这个行为:其实服务器并不是发送数据到x.x.x.255这个地址,这个地址是虚拟的,这个地址就相当于发送到x.x.x.1~x.x.x.254这一堆地址上,加上端口号,服务器发送到这一堆地址的指定端口号上,所以如果客户端socket不绑定到这个端口号上就接收不到了。而服务器自己的IP和端口则无所谓(如果客户端不需要发送数据到服务器)。

2. UDP广播

接下来是UDP广播,UDP广播的话分两种,一种是广播地址是255.255.255.255,一种是广播到x.x.x.255。前面一种是受限的广播,路由器不会转发,局域网里所有主机都收得到;后一种是子网的广播,路由器会转发到该子网的所有主机上,不过必须要保证子网掩码的正确(因为它是子网的广播嘛)。例子就是,如果某个客户端A的IP是192.168.1.10,看起来服务器广播到192.168.1.255时,客户端A可以收到,但是如果A的子网掩码不是255.255.255.0,则A就收不到,因为它不是192.168.1. 这个子网的。
[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. #include <WinSock2.h>  
  2. #include <iostream>  
  3. #include <string>  
  4. using namespace std;  
  5. #pragma comment(lib,"ws2_32.lib")  
  6. int _tmain(int argc, _TCHAR* argv[])  
  7. {  
  8.     WSADATA wsd;  
  9.     if(WSAStartup(MAKEWORD(2,2), &wsd) != 0)  
  10.     {  
  11.         cerr<<"Could not open Windows Connection.\n"<<endl;  
  12.         return 1;  
  13.     }  
  14.   
  15.     SOCKET serverSocket;  
  16.     serverSocket = socket(AF_INET, SOCK_DGRAM, 0);  
  17.     if(serverSocket == INVALID_SOCKET)  
  18.     {  
  19.         cerr<<"Could not create socket.\n"<<endl;  
  20.         WSACleanup();  
  21.         return 1;  
  22.     }  
  23.   
  24.     bool broadCast;  
  25.     int optLen = sizeof(broadCast);  
  26.     if(SOCKET_ERROR == setsockopt(serverSocket, SOL_SOCKET, SO_BROADCAST, (char*)&broadCast, optLen))  
  27.     {  
  28.         cerr<<"Could not Set BroadCast socket.\n"<<endl;  
  29.         closesocket(serverSocket);  
  30.         WSACleanup();  
  31.         return -1;  
  32.     }  
  33.   
  34.     sockaddr_in localAddress;  
  35.     memset(&localAddress, 0,sizeof(localAddress));  
  36.     localAddress.sin_family = AF_INET;  
  37.     localAddress.sin_port = htons(10086);  
  38.     localAddress.sin_addr.s_addr = inet_addr("172.31.8.255");  
  39.     //localAddress.sin_addr.s_addr = INADDR_BROADCAST;  
  40.   
  41.     string message = "hello world!";  
  42.     int server_length = (int)sizeof(sockaddr_in);  
  43.     int r;  
  44.     while (1)  
  45.     {  
  46.         r = sendto(serverSocket, message.c_str(), message.length(),0,(sockaddr*)&localAddress,sizeof(localAddress));  
  47.         if(r < 0)  
  48.             break;  
  49.         else  
  50.         {  
  51.             cout<<"Server Send : "<<message<<endl;  
  52.             Sleep(1000);  
  53.         }  
  54.     }  
  55.     return 0;  
  56. }  
以上是一个UDP广播的服务器代码,其关键之处在于:
  1. 要打开socket的广播属性,即setsockopt(serverSocket, SOL_SOCKET, SO_BROADCAST, (char*)&broadCast, optLen)
  2. 发送数据到设置了广播IP的地址中。在代码中可以看见,我的本机IP是172.31.8.41,子网掩码是255.255.255.0,所以我是172.31.8这个子网的,那么服务器发送到172.31.8.255这个广播IP上,就相当于发给我们实验室172.31.8.这个网段的所有小伙伴;另外,注释起来的这句话localAddress.sin_addr.s_addr = INADDR_BROADCAST; 即发送到受限的广播地址255.255.255.255,INADDR_BROADCAST是WinSock定义的宏,就代表这个地址。
[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. #include <WinSock2.h>  
  2. #include <iostream>  
  3. #include <string>  
  4. using namespace  std;  
  5. #pragma comment(lib,"ws2_32.lib")  
  6. int _tmain(int argc, _TCHAR* argv[])  
  7. {  
  8.     WSADATA wsd;  
  9.     if(WSAStartup(MAKEWORD(2,2), &wsd) != 0)  
  10.     {  
  11.         cerr<<"Could not open Windows Connection.\n"<<endl;  
  12.         return 1;  
  13.     }  
  14.   
  15.     SOCKET clientSocket;  
  16.     clientSocket = socket(AF_INET, SOCK_DGRAM, 0);  
  17.     if(clientSocket == INVALID_SOCKET)  
  18.     {  
  19.         cerr<<"Could not create socket.\n"<<endl;  
  20.         WSACleanup();  
  21.         return 1;  
  22.     }  
  23.   
  24.     sockaddr_in clientAddress;  
  25.     memset(&clientAddress, 0,sizeof(clientAddress));  
  26.     clientAddress.sin_family = AF_INET;  
  27.     clientAddress.sin_port = htons(10086);  
  28.     clientAddress.sin_addr.s_addr = INADDR_ANY;  
  29.   
  30.     if(-1 == bind(clientSocket, (sockaddr*)&clientAddress,sizeof(sockaddr)))  
  31.     {  
  32.         cerr<<"Could not bind socket.\n"<<endl;  
  33.         closesocket(clientSocket);  
  34.         WSACleanup();  
  35.         return -1;  
  36.     }  
  37.   
  38.     char buff[20] = {0};  
  39.     int serverLength = sizeof(clientAddress);  
  40.     int r;  
  41.     while (1)  
  42.     {  
  43.          r = recvfrom(clientSocket, buff, 20, 0, NULL,NULL);  
  44.          if(r < 0)  
  45.              return 1;  
  46.          else  
  47.          {  
  48.              cout<<"Client Receive : "<<buff<<endl;  
  49.          }  
  50.     }  
  51.     return 0;  
  52. }  
以上是UDP广播的客户端代码。客户端的关键之处只是把客户端socket绑定到服务器广播发送的那个端口上。具体解释和多播相同。

总结:
  1. UDP多播和UDP广播都必须把客户端socket绑定到一个地址上,这个地址指明了服务端发送的端口号。
  2. UDP多播的客户端需要加入多播组才能收到数据;服务端则只需要向指定的多播IP和端口发送数据即可。
  3. UDP广播的服务端需要打开广播属性,然后才能向指定的广播IP和端口发送数据;客户端则只需要从那个端口接收即可。
补充一点:
在我的测试中,如果两台机器用网线直接相连,那么UDP子网广播和受限广播都是正常的。但是在实验室环境下,在一台主机上发,另一台主机上收,子网广播是OK的,但是受限广播就不可以。这可能是路由器不转发受限广播的缘故吧。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值