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 网络编程中实现「一对多」通信的两种重要手段:

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

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

相关推荐
IT大白鼠8 小时前
GitLab 未认证 RCE 应急实战CVE-2026-97229(CVSS 10.0)
网络·安全·gitlab
陈希1238 小时前
Linux 网络诊断工具实战:ping、curl、tcpdump、mtr、dig 用法详解
linux·网络·tcpdump
草莓熊Lotso10 小时前
【C++ 基于微服务的即时通讯系统】文件存储子服务实战:UUID生成、文件IO封装与全接口单元测试
开发语言·网络·数据库·c++·微服务·架构·单元测试
wuyk55511 小时前
Spider-第03讲-HTTP速通与读懂响应
网络·爬虫·网络协议·http
默|笙21 小时前
【Linux网络】网络层协议 IP(1)理解报头、网段划分
linux·网络·tcp/ip
梦到啥说啥1 天前
Linux网络编程:TCP网络协议
网络·网络协议·tcp/ip
Shanks.1 天前
2026 远程控制软件实测:ToDesk / 向日葵 / RustDesk / TeamViewer 在跨城与弱网下的真实差距
网络·远程工作·软件需求
Jeremy_WW1 天前
802.3协议解读 06:117章节 RS 和 200GMII / 400GMII I
网络·网络协议·信息与通信·光模块·802.3协议
坤坤子吖1 天前
Linux网络编程:自定义应用层协议与 JSON 序列化
linux·服务器·网络·c++·笔记·json
顧棟1 天前
INFINI Gateway 服务器端口重用(SO_REUSEPORT)实现与分析
网络·gateway