单播、组播(多播)、广播

本文详细解释了网络编程中单播、多播(组播)和广播的区别,强调了它们在网络通信中的应用场景,特别是IOT中设备搜索和组播通信的优势与限制,以及如何在UDP协议中实现广播和组播功能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

日常的网络编程中,不管是TCP还是UDP,应用程序主要是基于单播,即点对点通信,对组播和广播涉及的相对少。这里先用通俗的例子解析一下这三者的区别:

单播:有具体目标地址的帧从源到达目标地址的过程。比如你对张三喊“张三”,哪么只有张三答应你.
多播(组播):帧送往定义在一组内的地址。比如你喊:姓张的请举手,那么只有姓张才会举手回应你,其他不姓张的人,就不会举手。
广播:把帧发往所有能到达的地址。比如你在学校的广播中喊“今天放假”。哪么全校的同学都能听到,然后欢呼。 

特别注意:组播和广播是通过UDP实现的,TCP不支持组播和广播

组播、广播的应用场景:
在IOT物联网领域,组播和广播一般可用作设备的搜索发现,设备启动,并且有网络能力后,会通过组播或广播,向局域网内发布自己的设备信息,然后其他设备就能够发现感兴趣的设备,进而跟这些局域网内的设备通信。


组播地址范围
组播通信必须依赖于IP多播地址,在IPv4中它是一个D类IP地址,范围从 224.0.0.0到239.255.255.255,并被划分为局部链接多播地址、预留多播地址和管理权限多播地址三类:

局部链接多播地址范围在 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,可限制多播范围。

我们一般使用224.0.0.0~224.0.0.255范围作为组播地址,比如mDNS协议,就使用224.0.0.251固定地址。

单播
每次只有两个实体相互通信,发送端和接收端都是唯一确定的。

广播

  • 主机之间的一对多的通信
  • 所有的主机都可以接收到广播消息(不管你是否需要)
  • 广播禁止穿过路由器(只能做局域网通信)
  • 只有UDP可以广播
  • 广播地址 有效网络号+全是1的主机号
  • 192.168.50.123 -----》 192.168.50.255
  • 255.255.255.255    给所有的网段中的所有主机发送广播,也是只能做局域网通信
  • 需要相同端口

一.广播  (UDP协议)
广播地址:  主机号最大的地址;

以192.168.1.0 (255.255.255.0) 网段为例,最大的主机地址192.168.1.255代表该网段的广播地址 

前面介绍的数据包发送方式只有一个接受方,称为单播
如果同时发给局域网中的所有主机,称为广播
     (同一局域网内的主机都会接收到,如果其他主机没有加入广播站,就会将消息丢弃)

只有用户数据报(使用UDP协议)套接字才能广播
一般被设计为局域网搜索协议   

广播的发送者:
广播发送端创建流程
1)广播的发送端流程  ---》类似于UDP的客户端

socket                创建套接字
setsockopt         设置允许广播,默认是不允许的
填充接收方的结构体,给sendto使用,指定发送给谁
a。IP    (192.168.50.255/255.255.255.255)

b。端口号

sendto               发送数据

 

 

广播的接收者:(不需要设置为广播态)
广播接收端创建流程
2)广播的接收端流程  -----》类似于UDP服务器

socket                创建套接字
填充结构体,
ip:广播ip;
0.0.0.0(将本机所有可用的IP都绑定到套接字上:192.168.50.58,127.0.0.1(本地回环))(本地回环地址给自己发送,本地自测,不走网卡)
端口号,一台主机只能打开一个服务器只用同一个端口号
bind
recv

广播的缺点: 
广播方式发给所有的主机,过多的广播会大量的占用网络带宽,造成广播风暴,影响正常的通信;

组播
广播是给网段内的所有机器发消息     占用网络带宽,影响正常通信
单播是一对一的 
主机之间一对一组的通信模式,也就是说只要加入了同一个小组,那就可以收到发送端的消息
组播地址:D类的224.0.0.1~~239.255.255.255
224.10.10.10(相当于组名)

。单播方式只能发给一个接收方

。 组播是一个人发送,加入到多播组的主机接收数据

。 多播方式既可以发给多个主机,又能避免像广播一样造成过多的负载:

组播的地址: 
IP的二级划分中  D类IP:  

  第一字节的前四位固定为 1110  

         D类IP  :    224.0.0.1  -  239.255.255.255

                        224.10.10.10

 

组播的发送端: 
1)组播的发送端流程  ---》类似于UDP的客户端

socket    创建套接字
填充结构体,给sendto函数使用,指定接收方
IP:组播ip(224.0.0.0~~239.255.255.255)
PORT:与接收方绑定的一致
sendto 发送数据

代码 

广播接收端

#include <iostream>
#include <WinSock2.h>
#include <WS2tcpip.h>
#include <vector>
#include "MsgDefForDataServer23Tai.h"
#include "LocatorData.h"

#pragma comment(lib, "ws2_32.lib")
using namespace PT_TAI;

int main()
{
	WSADATA wsaData;
	int iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);
	if (iResult != 0) {
		std::cout << "WSAStartup failed: " << iResult << std::endl;
		return 1;
	}

	SOCKET recvSocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
	if (recvSocket == INVALID_SOCKET) {
		std::cout << "socket failed with error: " << WSAGetLastError() << std::endl;
		WSACleanup();
		return 1;
	}

	sockaddr_in recvAddr;
	recvAddr.sin_family = AF_INET;
	recvAddr.sin_port = htons(9632);
	recvAddr.sin_addr.s_addr = INADDR_ANY;

	iResult = bind(recvSocket, (sockaddr*)&recvAddr, sizeof(recvAddr));
	if (iResult == SOCKET_ERROR) {
		std::cout << "bind failed with error: " << WSAGetLastError() << std::endl;
		closesocket(recvSocket);
		WSACleanup();
		return 1;
	}

	// char recvBuf[1024] = { 0 };
	// char recvBuf[11 * 1024] = { 0 };
	char recvBuf[11 * 1603] = { 0 };
	int recvBufLen = sizeof(recvBuf);
	struct sockaddr_in senderAddr;
	int senderAddrLen = sizeof(senderAddr);

	while (true) {
		iResult = recvfrom(recvSocket, recvBuf, recvBufLen, 0, (struct sockaddr*)&senderAddr, &senderAddrLen);
		if (iResult == SOCKET_ERROR) {
			std::cout << "recvfrom failed with error: " << WSAGetLastError() << std::endl;
			closesocket(recvSocket);
			WSACleanup();
			continue;
			// return 1;
		}

		std::vector<ST_DATA_SEND> vecValue;
		std::string strReceiveData;
		strReceiveData.resize(iResult);
		std::memcpy((void*)strReceiveData.data(), recvBuf, iResult);
		int nSize = CCLocatorData->DeserializeResult(strReceiveData, vecValue);
		std::cout << "--------------------------------------------------------" << std::endl;
		for (auto Item : vecValue)
		{
			std::cout << static_cast<int>(Item.iVersion) << "\t" << Item.szDeviceID << std::endl;
		}

		std::cout << "Received " << iResult << " bytes: " << recvBuf << " from " << inet_ntoa(senderAddr.sin_addr) << std::endl;
	}

	closesocket(recvSocket);
	WSACleanup();

	return 0;
}

广播发送端

   #include <iostream>
   #include <WinSock2.h>
   #include <WS2tcpip.h>
   
   #pragma comment(lib, "ws2_32.lib")
   
   int main()
   {
   	WSADATA wsaData;
   	int iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);
   	if (iResult != 0) {
   		std::cout << "WSAStartup failed: " << iResult << std::endl;
   		return 1;
   	}
   
   	SOCKET sendSocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
   	if (sendSocket == INVALID_SOCKET) {
   		std::cout << "socket failed with error: " << WSAGetLastError() << std::endl;
   		WSACleanup();
   		return 1;
   	}
   
   	BOOL broadcast = TRUE;
   	iResult = setsockopt(sendSocket, SOL_SOCKET, SO_BROADCAST, (char*)&broadcast, sizeof(broadcast));
   	if (iResult == SOCKET_ERROR) {
   		std::cout << "setsockopt failed with error: " << WSAGetLastError() << std::endl;
   		closesocket(sendSocket);
   		WSACleanup();
   		return 1;
   	}
   
   	sockaddr_in sendAddr;
   	sendAddr.sin_family = AF_INET;
   	sendAddr.sin_port = htons(9632);
   	inet_pton(AF_INET, "255.255.255.255", &(sendAddr.sin_addr));
   
   	const char* sendData = "Hello, broadcast!";
   	int sendDataLen = strlen(sendData);
   
   	while (true) {
   		iResult = sendto(sendSocket, sendData, sendDataLen, 0, (sockaddr*)&sendAddr, sizeof(sendAddr));
   		if (iResult == SOCKET_ERROR) {
   			std::cout << "sendto failed with error: " << WSAGetLastError() << std::endl;
   			closesocket(sendSocket);
   			WSACleanup();
   			return 1;
   		}
   
   		std::cout << "Sent " << iResult << " bytes: " << sendData << std::endl;
   		Sleep(1000); // 每隔1秒发送一次数据
   	}
   
   	closesocket(sendSocket);
   	WSACleanup();
   
   	return 0;
   }

组播发送端

	#include <iostream>
	#include <cstring>
	#include <WinSock2.h>
	#include <WS2tcpip.h>
	
	#pragma comment(lib, "ws2_32.lib")
	
	int main() {
		WSADATA wsaData;
		if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
			std::cerr << "Failed to initialize winsock" << std::endl;
			return 1;
		}
	
		SOCKET sockfd;
		struct sockaddr_in addr;
		const char* group = "239.0.0.1";
		const int port = 8888;
	
		// 创建UDP套接字
		sockfd = socket(AF_INET, SOCK_DGRAM, 0);
		if (sockfd == INVALID_SOCKET) {
			std::cerr << "Failed to create socket" << std::endl;
			WSACleanup();
			return 1;
		}
	
		// 设置组播地址和端口
		memset(&addr, 0, sizeof(addr));
		addr.sin_family = AF_INET;
		addr.sin_addr.s_addr = inet_addr(group);
		addr.sin_port = htons(port);
	
		std::string message = "Hello, multicast!";
	
		while (true) {
			// 发送消息
			if (sendto(sockfd, message.c_str(), message.size(), 0, (struct sockaddr*)&addr, sizeof(addr)) == SOCKET_ERROR) {
				std::cerr << "Failed to send message" << std::endl;
				closesocket(sockfd);
				WSACleanup();
				return 1;
			}
	
			std::cout << "Message sent" << std::endl;
	
			Sleep(1000); // 暂停1秒钟
		}
	
		// 关闭套接字
		closesocket(sockfd);
		WSACleanup();
	
		return 0;
	}

组播接收端

	#include <iostream>
	#include <cstring>
	#include <WinSock2.h>
	#include <WS2tcpip.h>
	
	#pragma comment(lib, "ws2_32.lib")
	
	int main() {
		WSADATA wsaData;
		if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
			std::cerr << "Failed to initialize winsock" << std::endl;
			return 1;
		}
	
		SOCKET sockfd;
		struct sockaddr_in addr;
		const char* group = "239.0.0.1";
		const int port = 8888;
	
		// 创建UDP套接字
		sockfd = socket(AF_INET, SOCK_DGRAM, 0);
		if (sockfd == INVALID_SOCKET) {
			std::cerr << "Failed to create socket" << std::endl;
			WSACleanup();
			return 1;
		}
	
		// 设置组播地址和端口
		memset(&addr, 0, sizeof(addr));
		addr.sin_family = AF_INET;
		addr.sin_addr.s_addr = htonl(INADDR_ANY);
		addr.sin_port = htons(port);
	
		// 绑定套接字到本地地址和端口
		if (bind(sockfd, (struct sockaddr*)&addr, sizeof(addr)) == SOCKET_ERROR) {
			std::cerr << "Failed to bind socket" << std::endl;
			closesocket(sockfd);
			WSACleanup();
			return 1;
		}
	
		// 加入组播组
		struct ip_mreq mreq;
		mreq.imr_multiaddr.s_addr = inet_addr(group);
		mreq.imr_interface.s_addr = htonl(INADDR_ANY);
		if (setsockopt(sockfd, IPPROTO_IP, IP_ADD_MEMBERSHIP, (const char*)&mreq, sizeof(mreq)) == SOCKET_ERROR) {
			std::cerr << "Failed to join multicast group" << std::endl;
			closesocket(sockfd);
			WSACleanup();
			return 1;
		}
	
		char buffer[1024];
		struct sockaddr_in senderAddr;
		int senderAddrLen = sizeof(senderAddr);
	
		while (true) {
			// 接收消息
			int numBytes = recvfrom(sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr*)&senderAddr, &senderAddrLen);
			if (numBytes == SOCKET_ERROR) {
				std::cerr << "Failed to receive message" << std::endl;
				closesocket(sockfd);
				WSACleanup();
				return 1;
			}
	
			buffer[numBytes] = '\0';
			std::cout << "Received message from " << inet_ntoa(senderAddr.sin_addr) << ": " << buffer << std::endl;
		}
	
		// 关闭套接字
		closesocket(sockfd);
		WSACleanup();
	
		return 0;
	}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值