当前位置: 首页 > article >正文

【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;
}

4. 测试结果

在这里插入图片描述



http://www.kler.cn/a/611221.html

相关文章:

  • 蓝桥杯 R格式
  • K8S学习之基础四十三:k8s中部署elasticsearch
  • 保姆级教程 在linux上启动Docker并且使用IntelliJ DockerCompose一键部署Springboot应用 常见命令
  • C语言-适配器模式详解与实践
  • 技术迭代、流量困境与营销突破:基于开源AI大模型与S2B2C模式的创新路径研究
  • Rust从入门到精通之进阶篇:11.所有权系统详解
  • 第十一节 MATLAB关系运算符
  • 电动自行车/电动工具锂电池PCM方案--SH367003、SH367004、SH79F329
  • 深度分页优化思路
  • C++ 多线程简要讲解
  • Modbus RTU ---> Modbus TCP透传技术实现(Modbus透传、RS485透传、RTU透传)分站代码实现、协议转换器
  • Postman 下载文件指南:如何请求 Excel/PDF 文件?
  • 2025BAT大厂Java面试2000题精选(附答案+考点分析)
  • 人员进出新视界:视觉分析算法的力量
  • 淘宝获取商品sku详情API接口如何调用?
  • 前端学习笔记--CSS
  • vue vue3 走马灯Carousel
  • 如何 编译 px4
  • 物理环境与安全
  • 第十四届蓝桥杯大赛软件赛省赛C/C++ 大学 B 组(部分题解)