【C++网络编程】第5篇:UDP与广播通信
一、UDP协议核心特性
1. UDP vs TCP
特性 | UDP | TCP |
---|---|---|
连接方式 | 无连接 | 面向连接(三次握手) |
可靠性 | 不保证数据到达或顺序 | 可靠传输(超时重传、顺序控制) |
传输效率 | 低延迟,高吞吐 | 相对较低(因握手和确认机制) |
适用场景 | 实时音视频、广播、在线游戏 | 文件传输、Web请求、数据库操作 |
2. UDP数据包结构
- **首部(8字节)**:
| 源端口(2) | 目标端口(2) |
| 数据包长度(2) | 校验和(2) |
- 数据载荷:最大长度受限于IPv4的MTU(通常1500字节)。
3. 不同方式介绍
单播(Unicast):1 对 1(普通 UDP/TCP 通信)。
广播(Broadcast):1 对同一子网内所有设备。
组播(Multicast):1 对一组指定的设备(跨子网)。
二、UDP单播通信实现
1. UDP服务器与客户端流程
服务器:socket() → bind() → recvfrom() → sendto()
客户端:socket() → sendto() → recvfrom()
2. 完整代码示例
UDP服务器(接收并回复)
#include <iostream>
#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 << "WSAStartup failed!" << std::endl;
return 1;
}
SOCKET sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
sockaddr_in serverAddr{};
serverAddr.sin_family = AF_INET;
serverAddr.sin_addr.s_addr = htonl(INADDR_ANY);
serverAddr.sin_port = htons(8080);
bind(sock, (sockaddr*)&serverAddr, sizeof(serverAddr));
std::cout << "UDP Server listening on port 8080..." << std::endl;
char buffer[1024];
sockaddr_in clientAddr{};
int clientAddrLen = sizeof(clientAddr);
while (true) {
int bytesReceived = recvfrom(sock, buffer, sizeof(buffer), 0,
(sockaddr*)&clientAddr, &clientAddrLen);
if (bytesReceived == SOCKET_ERROR) {
std::cerr << "recvfrom failed: " << WSAGetLastError() << std::endl;
continue;
}
char clientIp[INET_ADDRSTRLEN] = { 0 };
if (InetNtopA(
AF_INET, // IPv4
&clientAddr.sin_addr, // 输入地址结构体
clientIp, // 输出缓冲区
sizeof(clientIp) // 缓冲区大小
) == NULL) {
std::cerr << "IP conversion failed: " << WSAGetLastError() << std::endl;
strcpy_s(clientIp, "unknown"); // 错误时显示 "unknown"
}
std::cout << "Received " << bytesReceived << " bytes from "
<< clientIp << ":" // 使用转换后的 IP 字符串
<< ntohs(clientAddr.sin_port) << std::endl;
// 原样返回数据
sendto(sock, buffer, bytesReceived, 0,
(sockaddr*)&clientAddr, clientAddrLen);
}
closesocket(sock);
WSACleanup();
return 0;
}
UDP客户端(发送消息)
#include <iostream>
#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 << "WSAStartup failed!" << std::endl;
return 1;
}
SOCKET sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
sockaddr_in serverAddr{};
serverAddr.sin_family = AF_INET;
inet_pton(AF_INET, "127.0.0.1", &(serverAddr.sin_addr.s_addr));
serverAddr.sin_port = htons(8080);
const char* message = "Hello UDP Server!";
sendto(sock, message, strlen(message), 0,
(sockaddr*)&serverAddr, sizeof(serverAddr));
char buffer[1024];
int bytesReceived = recvfrom(sock, buffer, sizeof(buffer), 0, nullptr, nullptr);
if (bytesReceived > 0) {
std::cout << "Server echoed: " << std::string(buffer, bytesReceived) << std::endl;
}
closesocket(sock);
WSACleanup();
return 0;
}
测试结果
三、UDP广播通信
1. 广播地址与设置
- 广播地址:255.255.255.255(全局广播)或子网广播地址(如192.168.1.255)。
- 套接字选项:启用
SO_BROADCAST
。
int enable = 1;
setsockopt(sock, SOL_SOCKET, SO_BROADCAST, (char*)&enable, sizeof(enable));
2. 广播发送端代码
#include <iostream>
#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 << "WSAStartup failed!" << std::endl;
return 1;
}
SOCKET sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
// 启用广播
int enable = 1;
setsockopt(sock, SOL_SOCKET, SO_BROADCAST, (char*)&enable, sizeof(enable));
sockaddr_in broadcastAddr{};
broadcastAddr.sin_family = AF_INET;
inet_pton(AF_INET, "127.0.0.1", &(broadcastAddr.sin_addr.s_addr));
broadcastAddr.sin_port = htons(8080);
const char* message = "Broadcast Message!";
sendto(sock, message, strlen(message), 0,
(sockaddr*)&broadcastAddr, sizeof(broadcastAddr));
closesocket(sock);
WSACleanup();
return 0;
}
3. 服务器端修改部分
std::cout << "Received " << bytesReceived << " bytes from "
<< clientIp << ":" // 使用转换后的 IP 字符串
<< ntohs(clientAddr.sin_port) << std::endl;
std::cout << "Datas:" << std::string(buffer, bytesReceived) << std::endl;
4. 测试结果
四、UDP组播(Multicast)
1. 组播地址范围
- IPv4:224.0.0.0 ~ 239.255.255.255(如239.255.0.1)。
2. 接收端
#include <iostream>
#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 << "WSAStartup failed!" << std::endl;
return 1;
}
// 创建 UDP 套接字
SOCKET sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (sock == INVALID_SOCKET) {
std::cerr << "socket() failed: " << WSAGetLastError() << std::endl;
WSACleanup();
return 1;
}
// 设置端口复用(允许其他进程绑定相同端口)
int reuse = 1;
if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR,
(char*)&reuse, sizeof(reuse)) == SOCKET_ERROR) {
std::cerr << "setsockopt(SO_REUSEADDR) failed: " << WSAGetLastError() << std::endl;
closesocket(sock);
WSACleanup();
return 1;
}
// 绑定到本地端口
sockaddr_in localAddr{};
localAddr.sin_family = AF_INET;
localAddr.sin_addr.s_addr = htonl(INADDR_ANY);
localAddr.sin_port = htons(8080);
if (bind(sock, (sockaddr*)&localAddr, sizeof(localAddr)) == SOCKET_ERROR) {
std::cerr << "bind() failed: " << WSAGetLastError() << std::endl;
closesocket(sock);
WSACleanup();
return 1;
}
// 加入组播组
ip_mreq mreq{};
if (inet_pton(AF_INET, "239.255.255.250", &(mreq.imr_multiaddr.s_addr)) != 1) {
std::cerr << "inet_pton() failed for multicast address" << std::endl;
closesocket(sock);
WSACleanup();
return 1;
}
mreq.imr_interface.s_addr = htonl(INADDR_ANY); // 使用默认网络接口
if (setsockopt(sock, IPPROTO_IP, IP_ADD_MEMBERSHIP,
(char*)&mreq, sizeof(mreq)) == SOCKET_ERROR) {
std::cerr << "setsockopt(IP_ADD_MEMBERSHIP) failed: " << WSAGetLastError() << std::endl;
closesocket(sock);
WSACleanup();
return 1;
}
std::cout << "Listening for multicast on 239.255.255.250:8080..." << std::endl;
char buffer[1024];
sockaddr_in senderAddr{};
int senderAddrLen = sizeof(senderAddr);
while (true) {
// 接收数据
int bytesReceived = recvfrom(sock, buffer, sizeof(buffer) - 1, 0,
(sockaddr*)&senderAddr, &senderAddrLen);
if (bytesReceived == SOCKET_ERROR) {
std::cerr << "recvfrom() failed: " << WSAGetLastError() << std::endl;
continue;
}
// 显示来源信息
char senderIp[INET_ADDRSTRLEN] = { 0 };
inet_ntop(AF_INET, &senderAddr.sin_addr, senderIp, INET_ADDRSTRLEN);
std::cout << "Received " << bytesReceived << " bytes from "
<< senderIp << ":" << ntohs(senderAddr.sin_port) << std::endl;
// 安全处理数据(防止缓冲区溢出)
buffer[bytesReceived] = '\0'; // 添加字符串终止符
std::cout << "Data: " << buffer << std::endl;
}
// 退出时清理(虽然 while(true) 会阻止执行到这里)
closesocket(sock);
WSACleanup();
return 0;
}
3. 发送端
#include <iostream>
#include <winsock2.h>
#include <ws2tcpip.h>
#pragma comment(lib, "ws2_32.lib")
int main() {
// 初始化 Winsock
WSADATA wsaData;
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
std::cerr << "WSAStartup failed: " << WSAGetLastError() << std::endl;
return 1;
}
// 创建 UDP 套接字
SOCKET sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (sock == INVALID_SOCKET) {
std::cerr << "socket() failed: " << WSAGetLastError() << std::endl;
WSACleanup();
return 1;
}
// 设置组播 TTL(控制传输范围)
int ttl = 32;
if (setsockopt(sock, IPPROTO_IP, IP_MULTICAST_TTL,
(char*)&ttl, sizeof(ttl)) == SOCKET_ERROR) {
std::cerr << "setsockopt(IP_MULTICAST_TTL) failed: " << WSAGetLastError() << std::endl;
closesocket(sock);
WSACleanup();
return 1;
}
// 构建组播目标地址
sockaddr_in multicastAddr{};
multicastAddr.sin_family = AF_INET;
multicastAddr.sin_port = htons(8080); // 目标端口
// 转换组播地址
if (inet_pton(AF_INET, "239.255.255.250", &multicastAddr.sin_addr.s_addr) != 1) {
std::cerr << "inet_pton() failed for multicast address" << std::endl;
closesocket(sock);
WSACleanup();
return 1;
}
// 发送数据
const char* msg = "Hello Multicast!";
int msgLen = static_cast<int>(strlen(msg));
int bytesSent = sendto(sock, msg, msgLen, 0,
(sockaddr*)&multicastAddr, sizeof(multicastAddr));
if (bytesSent == SOCKET_ERROR) {
std::cerr << "sendto() failed: " << WSAGetLastError() << std::endl;
}
else if (bytesSent != msgLen) {
std::cerr << "sendto() partial send: " << bytesSent << "/" << msgLen << std::endl;
}
else {
std::cout << "Successfully sent " << bytesSent
<< " bytes to multicast group 239.255.255.250:8080" << std::endl;
}
// 清理资源
closesocket(sock);
WSACleanup();
return 0;
}