TCP/IP 网络编程:多播与广播

1. 引言

在网络编程中,我们通常习惯的是「一对一」的通信模式:一台客户端连接一台服务器,数据在两者之间传递。这种模式被称为单播(Unicast)。然而,在很多实际场景下,我们需要把同一份数据同时发送给多个接收者,例如:

  • 视频会议、直播推流;
  • 局域网内的设备发现与自动配置;
  • 股票行情、游戏房间状态同步;
  • 传感器网络中批量下发指令。

如果采用单播逐台发送,不仅浪费带宽,还会增加发送端的负担。为此,TCP/IP 协议栈提供了两种高效的「一对多」通信方式:广播(Broadcast) 与 多播(Multicast)。

本文将围绕这两种方式展开,先厘清它们与单播的区别,再分别讲解 UDP 下广播与多播的编程模型,最后给出可运行的代码示例与常见坑点。

2. 单播、广播与多播

2.1 单播(Unicast)

单播是「一对一」通信。发送端把数据包发往一个明确的目的 IP 地址,只有该地址对应的主机才会处理这个包。TCP 与 UDP 都支持单播,这也是绝大多数网络应用的基础形态。

2.2 广播(Broadcast)

广播是「一对所有」通信。发送端把数据包发往广播地址,位于同一广播域(通常是同一子网)内的所有主机都会收到该数据包。

广播的特点:

  • 仅限局域网内传播,路由器默认不会转发广播包;
  • 使用 UDP 实现,TCP 不支持广播;
  • 会对子网内所有主机造成处理开销,因此不宜频繁使用。

常见的广播地址是 255.255.255.255(受限广播),或子网定向广播地址,例如 192.168.1.255。

2.3 多播(Multicast)

多播是「一对一组」通信。发送端把数据包发往一个多播组地址,只有加入了该组的主机才会收到数据。

多播的特点:

  • 可以跨越路由器,在广域网范围内传播;
  • 使用 UDP 实现,TCP 不支持多播;
  • 相比广播,多播只影响加入组的主机,效率更高、更可控。

IPv4 的多播地址范围是 224.0.0.0 ~ 239.255.255.255(D 类地址),其中 224.0.0.0 ~ 224.0.0.255 为本地网络控制块,通常不用于用户程序。

2.4 三者对比

特性 单播 广播 多播
通信对象 一对一 一对所有 一对一组
传输层协议 TCP / UDP 仅 UDP 仅 UDP
是否跨路由器 是 否 是
接收方开销 仅目标主机 子网内所有主机 仅组成员
典型场景 Web、文件传输 局域网设备发现 直播、行情、组同步

3. 广播编程(UDP Broadcast)

广播基于 UDP 实现,发送端与接收端的编程模型与普通 UDP 几乎一致,区别主要在于目的地址与套接字选项。

3.1 发送端要点

发送广播数据前,必须设置套接字的 SO_BROADCAST 选项,否则发送广播包会返回错误。

c 复制代码
int sock = socket(AF_INET, SOCK_DGRAM, 0);
int broadcast = 1;
setsockopt(sock, SOL_SOCKET, SO_BROADCAST, &broadcast, sizeof(broadcast));

目的地址填写广播地址即可:

c 复制代码
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(9000);
inet_pton(AF_INET, "255.255.255.255", &addr.sin_addr);

3.2 接收端要点

接收端使用普通 UDP 绑定流程即可,无需额外选项:

c 复制代码
int sock = socket(AF_INET, SOCK_DGRAM, 0);

struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(INADDR_ANY);
addr.sin_port = htons(9000);
bind(sock, (struct sockaddr *)&addr, sizeof(addr));

3.3 完整示例:广播发送端

c 复制代码
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

int main() {
    int sock = socket(AF_INET, SOCK_DGRAM, 0);
    if (sock < 0) {
        perror("socket");
        return 1;
    }

    int broadcast = 1;
    setsockopt(sock, SOL_SOCKET, SO_BROADCAST, &broadcast, sizeof(broadcast));

    struct sockaddr_in addr;
    memset(&addr, 0, sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_port = htons(9000);
    inet_pton(AF_INET, "255.255.255.255", &addr.sin_addr);

    const char *msg = "hello broadcast";
    sendto(sock, msg, strlen(msg), 0,
           (struct sockaddr *)&addr, sizeof(addr));

    printf("broadcast sent: %s\n", msg);
    close(sock);
    return 0;
}

3.4 完整示例:广播接收端

c 复制代码
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>

int main() {
    int sock = socket(AF_INET, SOCK_DGRAM, 0);
    if (sock < 0) {
        perror("socket");
        return 1;
    }

    struct sockaddr_in addr;
    memset(&addr, 0, sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = htonl(INADDR_ANY);
    addr.sin_port = htons(9000);
    bind(sock, (struct sockaddr *)&addr, sizeof(addr));

    char buf[1024];
    struct sockaddr_in peer;
    socklen_t len = sizeof(peer);

    ssize_t n = recvfrom(sock, buf, sizeof(buf) - 1, 0,
                         (struct sockaddr *)&peer, &len);
    if (n > 0) {
        buf[n] = '\0';
        printf("recv from %s: %s\n", inet_ntoa(peer.sin_addr), buf);
    }

    close(sock);
    return 0;
}

4. 多播编程(UDP Multicast)

多播的编程模型比广播稍复杂:发送端需要设置多播 TTL,接收端需要加入多播组。

4.1 发送端要点

发送端把目的地址设为多播组地址,并可通过 IP_MULTICAST_TTL 设置数据包的生存时间(TTL),控制数据能跨越多少路由器:

c 复制代码
int sock = socket(AF_INET, SOCK_DGRAM, 0);

unsigned char ttl = 1;
setsockopt(sock, IPPROTO_IP, IP_MULTICAST_TTL, &ttl, sizeof(ttl));

struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(9001);
inet_pton(AF_INET, "239.0.0.1", &addr.sin_addr);

4.2 接收端要点

接收端除了绑定端口,还必须通过 IP_ADD_MEMBERSHIP 加入多播组,否则收不到组内数据:

c 复制代码
struct ip_mreq mreq;
memset(&mreq, 0, sizeof(mreq));
inet_pton(AF_INET, "239.0.0.1", &mreq.imr_multiaddr);
mreq.imr_interface.s_addr = htonl(INADDR_ANY);

setsockopt(sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq));

4.3 完整示例:多播发送端

c 复制代码
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

int main() {
    int sock = socket(AF_INET, SOCK_DGRAM, 0);
    if (sock < 0) {
        perror("socket");
        return 1;
    }

    unsigned char ttl = 1;
    setsockopt(sock, IPPROTO_IP, IP_MULTICAST_TTL, &ttl, sizeof(ttl));

    struct sockaddr_in addr;
    memset(&addr, 0, sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_port = htons(9001);
    inet_pton(AF_INET, "239.0.0.1", &addr.sin_addr);

    const char *msg = "hello multicast";
    sendto(sock, msg, strlen(msg), 0,
           (struct sockaddr *)&addr, sizeof(addr));

    printf("multicast sent: %s\n", msg);
    close(sock);
    return 0;
}

4.4 完整示例:多播接收端

c 复制代码
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

int main() {
    int sock = socket(AF_INET, SOCK_DGRAM, 0);
    if (sock < 0) {
        perror("socket");
        return 1;
    }

    struct sockaddr_in addr;
    memset(&addr, 0, sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = htonl(INADDR_ANY);
    addr.sin_port = htons(9001);
    bind(sock, (struct sockaddr *)&addr, sizeof(addr));

    struct ip_mreq mreq;
    memset(&mreq, 0, sizeof(mreq));
    inet_pton(AF_INET, "239.0.0.1", &mreq.imr_multiaddr);
    mreq.imr_interface.s_addr = htonl(INADDR_ANY);
    setsockopt(sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq));

    char buf[1024];
    struct sockaddr_in peer;
    socklen_t len = sizeof(peer);

    ssize_t n = recvfrom(sock, buf, sizeof(buf) - 1, 0,
                         (struct sockaddr *)&peer, &len);
    if (n > 0) {
        buf[n] = '\0';
        printf("recv from %s: %s\n", inet_ntoa(peer.sin_addr), buf);
    }

    close(sock);
    return 0;
}

5. 常见问题与注意事项

5.1 广播收不到数据

  • 确认发送端已设置 SO_BROADCAST;
  • 确认发送端与接收端位于同一子网;
  • 检查防火墙是否拦截了对应端口的 UDP 包。

5.2 多播收不到数据

  • 确认接收端已调用 IP_ADD_MEMBERSHIP 加入组;
  • 确认发送端与接收端使用相同的多播组地址与端口;
  • 跨路由器时,确认发送端 TTL 足够大,且沿途路由器启用了多播路由协议(如 PIM)。

5.3 广播与多播都不支持 TCP

TCP 是面向连接的可靠传输,无法表达「一对多」的语义。广播与多播只能基于 UDP 实现,因此也继承了 UDP 不保证可靠、不保证有序的特点。若业务需要可靠性,应在应用层自行实现确认与重传。

5.4 多播地址的选择

用户程序应避免使用 224.0.0.0 ~ 224.0.0.255 这段本地网络控制块地址,推荐使用 239.0.0.0 ~ 239.255.255.255 范围内的地址,该段为本地管理多播地址,适合内部网络使用。

6. 总结

广播与多播是 TCP/IP 网络编程中实现「一对多」通信的两种重要手段:

  • 广播实现简单,但只限于局域网,且会打扰子网内所有主机;
  • 多播更高效、可控,能够跨路由器传播,但需要接收端主动加入多播组。

在实际开发中,应根据业务范围与网络环境选择合适的方案:局域网内的设备发现可以优先考虑广播,而需要跨网段、面向特定群体的数据分发则应使用多播。

相关推荐
weixin_416667962 小时前
银河麒麟V10看门狗试验-1
网络·chrome·python
白帽攻防录2 小时前
SRC 挖洞:WSO2 JWT 算法混淆绕过深度复盘,CVE-2026-5430 不支持的算法怎么变成管理员
网络·算法·安全·网络安全·jwt
starzy19902 小时前
Flink 优化之网络缓存优化及参数详解:从数据传输机制到反压原理的生产调优指南
网络·缓存·flink
正经教主3 小时前
【FDE系列】阶段3:Day 58:Prompt 安全 — 注入、越狱与防护
网络·人工智能·安全·prompt·fde
实点科技4 小时前
实点科技IO-Link I/O IOL7A/B系列参数配置方法介绍
网络
梦帮科技5 小时前
LoRA / QLoRA 低秩矩阵流形更新:SVD 分解、秩与缩放因子 α 的物理意义与梯度稳定性保障
网络·深度学习·神经网络·线性代数·矩阵·架构·数据挖掘
阿钱真强道5 小时前
28 嵌入式操作系统 | 网关加 TCP 命令通道:粘包/半包怎么切
网络·网络协议·tcp/ip·粘包
HY小宝F6 小时前
树莓派智能摄像头系统 30 天重学计划之 Day 03 · 网络基础:建立“包“的思维 —— 学习总结
网络·学习
ayaya_mana6 小时前
MCTier 组网联机工具:安装、配置与私有化部署
网络·windows·内网穿透·联机