讲解多播之前我们首先要走出TCP/UDP的一个误区:CS模型。事实上UDP的组播类似于一个消息订阅和发布中心,如果把组播当做一个发布订阅模型来理解的话,那么组播ip+端口就是消息中心。
订阅:客户端加入到当前组播,就类似于在消息中心订阅了数据包,凡是有数据来都会通知到客户端(Note:udp是不可靠的,不能保证数据包的顺序性和可达性)。
取消订阅:客户端退出当前组播就类似于取消订阅不在接收该组的数据包了。
发布消息:通过UDP的客户端像该组发送数据,当该组接收到该数据包的时候就会通知组内的所有成员。
所以通过上述比喻我们基本上可以得出UDP组播,并不存在客户端和服务器的说法,任何UDP连接都可以发送和接收当前消息。
多播详细,请参TCP/IP详解
下面我们将实现一个基本的UDP组播Demo
实现UDP的基本步骤:
1.创建UDP套接字
2.将当前套接字绑定到指定的组播端口(Note:绑定地址是本机地址)
3.加入多播组
4.接收多播地址消息或者发布多播地址消息(Note:1.自己发送的数据包自己也会接收到,因为自己也加入到了群组。2.如果仅仅只是发送消息,可以不加入多播组,只需要直接把消息投递到多播组就行了)
5.使用完了之后关闭套接字,清理资源
1.UDP获取组播消息,获取的时候必须加入到多播组
// UdpMutiCastRecv.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include <iostream>
#include <WinSock2.h>
#include <WS2tcpip.h>
#pragma comment(lib,"ws2_32.lib")
using namespace std;
//测试组播地址:234.2.2.2:8888
// sockaddr_in dstAddr;
// dstAddr.sin_family = AF_INET;
// dstAddr.sin_port = htons(8888);
// inet_pton(AF_INET, "234.2.2.2", &dstAddr.sin_addr);//组播地址
int _tmain(int argc, _TCHAR* argv[])
{
//初始化套接字
WSADATA ws = {0};
if (WSAStartup(MAKEWORD(2, 2), &ws) != 0)
{
perror("WSAStartup failed");
WSACleanup();
return 0;
}
//创建UDP套接字
SOCKET mutcast_sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (mutcast_sock == INVALID_SOCKET)
{
perror("socket server failed");
WSACleanup();
return 0;
}
//复用地址,允许同一个应用启动多个实例
const int on = 1;
setsockopt(mutcast_sock, SOL_SOCKET, SO_REUSEADDR, (char *)&on, sizeof(on));
//设置传输协议、端口以及目的地址
sockaddr_in mutcast_addr;
mutcast_addr.sin_family = AF_INET;
mutcast_addr.sin_port = htons(8888);//端口为组播端口
mutcast_addr.sin_addr.S_un.S_addr = INADDR_ANY;//本机地址
//将socket绑定地址
if (bind(mutcast_sock, (sockaddr*)&mutcast_addr, sizeof(mutcast_addr)) == SOCKET_ERROR)
{
perror("bind failed");
closesocket(mutcast_sock);
WSACleanup();
return 0;
}
//加入组播
ip_mreq multiCast;
multiCast.imr_interface.S_un.S_addr = INADDR_ANY; //本地IP地址。
inet_pton(AF_INET, "234.2.2.2", &multiCast.imr_multiaddr);//组播地址
setsockopt(mutcast_sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char*)&multiCast, sizeof(multiCast));//加入组播地址
//用于解析数据包源头的客户端的地址信息
sockaddr_in client_addr = {0};
int nclient_addr_len = sizeof(client_addr);
char buff[1024]; //建立接收缓存字节数组
//循环接收组播数据包
cout << "wait muticast package..." << endl;
while (true)
{
memset(buff, 0, 1024);
//开始接收数据
int len = recvfrom(mutcast_sock, buff, 1024, 0, (sockaddr*)&client_addr, &nclient_addr_len);
if (len > 0)
{
char szBuf[255] = "";
inet_ntop(AF_INET, &client_addr.sin_addr, szBuf, 255);
cout << "客户端地址:" << szBuf << endl;
cout << buff<<endl;
//如果需要发送到指定的客户端,可以填充sendto的时候填充客户端地址,一般不会这么做
//sendto(mutcast_sock, buff, lstrlenA(buff) + 1, 0, (sockaddr*)&client_addr, nclient_addr_len);
}
}
closesocket(mutcast_sock);//关闭socket
WSACleanup();
return 0;
}
2.UDP仅仅发送播组消息
1.创建UDP套接字
2.发送数据报
3.使用完了之后关闭套接字,清理资源
// UdpMutiCastSend.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include <iostream>
#include <WinSock2.h>
#include <WS2tcpip.h>
#include <time.h>
#pragma comment(lib,"ws2_32.lib")
using namespace std;
int _tmain(int argc, _TCHAR* argv[])
{
//初始化套接字
WSADATA WSAData;
WORD sockVersion = MAKEWORD(2, 2);
if (WSAStartup(sockVersion, &WSAData) != 0)
return 0;
//创建UDP套接字
SOCKET clientSocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (INVALID_SOCKET == clientSocket)
{
cout << "socket error!";
return 0;
}
//组播地址
sockaddr_in dstAddr;
dstAddr.sin_family = AF_INET;
dstAddr.sin_port = htons(8888);
inet_pton(AF_INET, "234.2.2.2", &dstAddr.sin_addr);
//发送数据
srand(time(nullptr));
int nflag = rand();
char szbuf[1024] = "";
sprintf_s<1024>(szbuf, "hellow word %d", nflag);
sendto(clientSocket, szbuf, strlen(szbuf), 0, (sockaddr*)&dstAddr, sizeof(dstAddr));
closesocket(clientSocket);
WSACleanup();
return 0;
}
测试结果:启动多个UdpMutiCastRecv程序,然后启动UdpMutiCastSend程序,运行结果如下: