日常的网络编程中,不管是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;
}