文章目录
-
[广播 VS 组播](#广播 VS 组播)
-
广播和组播均为UDP 协议专属的数据包发送方式(TCP 为面向连接的单播,不支持),用于实现一对多的网络数据传输,二者在传输范围、寻址方式、实现方式上存在核心差异
广播
核心概念
- 广播是局域网内的一对所有传输:将数据包发送到局域网中所有主机,所有主机均可接收该数据包
- 仅 UDP(用户数据报套接字)支持广播,TCP 因面向连接的特性无法实现
- 单播为 "一对一" 传输,是与广播对应的基础传输方式
广播地址
- 核心规则:一个网络内主机号全为 1 的 IP 地址为该网段的广播地址,发往此地址的数据包会被网段内所有主机接收;
- 通用广播地址:255.255.255.255,在所有网段中均表示广播地址,适用于本地局域网广播
实现流程

发送端
- 创建 UDP 套接字:socket(AF_INET, SOCK_DGRAM, 0);
- 开启广播权限:通过setsockopt设置SO_BROADCAST选项(核心步骤,不设置则无法发送广播包);
c
// 开启套接字的广播能力,必须在发送前设置
int on = 1; // 1为开启,0为关闭
setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, &on, sizeof(on));
- 填充广播地址结构体:目的 IP 设为广播地址(如255.255.255.255),目的端口与接收端一致;
- 发送广播数据:调用sendto向广播地址发送数据包;
- 关闭套接字:close(sockfd)
接收端
- 创建 UDP 套接字;
- 绑定本机 IP / 任意 IP(INADDR_ANY)和固定端口(需与发送端目的端口一致);
- 调用recvfrom阻塞接收广播数据;
- 关闭套接字
demo
- receiver
c
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */
#include <unistd.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <strings.h>
#define ErrExit(msg) do {perror(msg); exit(EXIT_FAILURE);} while(0)
typedef struct sockaddr Addr;
typedef struct sockaddr_in Addr_in;
int main(int argc, char *argv[])
{
int fd = -1;
Addr_in myaddr, peeraddr;
socklen_t peerlen = sizeof(peeraddr);
char buf[BUFSIZ] = {};
/*参数检查*/
if(argc < 3){
fprintf(stderr, "%s<addr><port>", argv[0]);
exit(EXIT_FAILURE);
}
/*创建套接字*/
if( (fd = socket(AF_INET, SOCK_DGRAM, 0) ) < 0)
ErrExit("socket");
/*设置通信结构体*/
myaddr.sin_family = AF_INET;
myaddr.sin_port = htons(atoi(argv[2]));
if(!inet_aton(argv[1], &myaddr.sin_addr) ){
fprintf(stderr, "Invalid address\n");
exit(EXIT_FAILURE);
}
/*绑定通信结构体*/
if( bind(fd, (Addr *)&myaddr, sizeof(Addr_in)) )
ErrExit("bind");
while(1){
recvfrom(fd, buf, BUFSIZ, 0, (Addr *)&peeraddr, &peerlen);
printf("[%s:%d]%s\n", inet_ntoa(peeraddr.sin_addr), ntohs(peeraddr.sin_port), buf);
}
return 0;
}
- send
c
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */
#include <unistd.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <strings.h>
#include <string.h>
#define ErrExit(msg) do {perror(msg); exit(EXIT_FAILURE);} while(0)
typedef struct sockaddr Addr;
typedef struct sockaddr_in Addr_in;
int main(int argc, char *argv[])
{
int fd = -1;
Addr_in peeraddr;
socklen_t peerlen = sizeof(peeraddr);
char buf[BUFSIZ] = {};
/*参数检查*/
if(argc < 3){
fprintf(stderr, "%s<multiaddr><port>", argv[0]);
exit(EXIT_FAILURE);
}
/*创建套接字*/
if( (fd = socket(AF_INET, SOCK_DGRAM, 0) ) < 0)
ErrExit("socket");
/*允许广播*/
int on = 1;
setsockopt(fd, SOL_SOCKET, SO_BROADCAST, &on, sizeof(on));
/*设置通信结构体*/
peeraddr.sin_family = AF_INET;
peeraddr.sin_port = htons(atoi(argv[2]));
if(!inet_aton(argv[1], &peeraddr.sin_addr) ){
fprintf(stderr, "Invalid address\n");
exit(EXIT_FAILURE);
}
while(1){
fgets(buf, BUFSIZ, stdin);
sendto(fd, buf, strlen(buf)+1, 0, (Addr *)&peeraddr, peerlen);
}
return 0;
}
组播(多播)
核心概念
- 组播是一对一组的精准传输:将数据包发送到指定的组播组,只有加入该组的主机才能接收数据包,未加入的主机无法感知
- 仅 UDP 支持,组播数据包的源地址为单播地址,目的地址为专属组播地址
- 相比广播,组播更节省网络带宽(避免无意义的全网发送),适用于需要精准一对多传输的场景(如视频直播、设备组网)
组播地址
- 组播地址为 IP 地址中的D 类地址,与 A/B/C 类单播地址、E 类保留地址区分;
- 地址范围:224.0.0.0 ~ 239.255.255.255;
- 核心规则:一个 D 类地址对应一个唯一的组播组,组播地址仅能作为目的地址,不能作为源地址
核心结构体
- 通过setsockopt设置IP_ADD_MEMBERSHIP选项时,需传入组播组配置结构体
ip_mreq(常用)
c
struct ip_mreq {
struct in_addr imr_multiaddr; /* 组播组的D类IP地址(如224.0.0.1) */
struct in_addr imr_interface; /* 本地网卡的IP地址,设为INADDR_ANY表示默认网卡 */
};
ip_mreqn(含网卡编号,更精细)
c
struct ip_mreqn {
struct in_addr imr_multiaddr; /* 组播组的D类IP地址 */
struct in_addr imr_address; /* 本地接口的IP地址 */
int imr_ifindex; /* 本地网卡的编号,设为0表示默认网卡 */
};
实现流程
接收端
- 创建 UDP 套接字:socket(AF_INET, SOCK_DGRAM, 0)
- 填充组播结构体:设置组播组 IP、本地网卡 IP(默认INADDR_ANY)
- 加入组播组:通过setsockopt设置IP_ADD_MEMBERSHIP选项(核心步骤,不加入则无法接收组播数据)
c
struct ip_mreq mreq;
// 设置要加入的组播组IP(示例:224.0.0.1,局域网所有主机默认组)
mreq.imr_multiaddr.s_addr = inet_addr("224.0.0.1");
// 本地网卡IP,INADDR_ANY表示使用系统默认网卡
mreq.imr_interface.s_addr = htonl(INADDR_ANY);
// 加入组播组,级别为IPPROTO_IP,选项为IP_ADD_MEMBERSHIP
if(setsockopt(fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) < 0){
perror("setsockopt"); // 设置失败时打印错误
exit(0);
}
- 绑定组播地址和端口:绑定的目的 IP 为组播组 D 类地址,端口与发送端一致
- 接收组播数据:调用recvfrom阻塞接收
- 关闭套接字:close(fd)
发送端
- 发送端无需加入组播组,按 UDP 普通发送流程,仅需将目的 IP 设为组播组 D 类地址即可:
- 创建 UDP 套接字
- 填充目的地址结构体:目的 IP 为组播组地址,端口与接收端一致
- 调用sendto发送组播数据
- 关闭套接字
demo
- receiver
c
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */
#include <unistd.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <strings.h>
#define ErrExit(msg) do {perror(msg); exit(EXIT_FAILURE);} while(0)
typedef struct sockaddr Addr;
typedef struct sockaddr_in Addr_in;
int main(int argc, char *argv[])
{
int fd = -1;
Addr_in myaddr, peeraddr;
socklen_t peerlen = sizeof(peeraddr);
struct ip_mreqn mreq;
char buf[BUFSIZ] = {};
/*参数检查*/
if(argc < 3){
fprintf(stderr, "%s<addr><port>", argv[0]);
exit(EXIT_FAILURE);
}
/*创建套接字*/
if( (fd = socket(AF_INET, SOCK_DGRAM, 0) ) < 0)
ErrExit("socket");
/*加入多播组*/
bzero(&mreq, sizeof(mreq) );
if(!inet_aton(argv[1], &mreq.imr_multiaddr) ){
fprintf(stderr, "Invalid address\n");
exit(EXIT_FAILURE);
}
if(setsockopt(fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) < 0){
perror("setsockopt");
exit(0);
}
/*设置通信结构体*/
myaddr.sin_family = AF_INET;
myaddr.sin_port = htons(atoi(argv[2]));
if(!inet_aton(argv[1], &myaddr.sin_addr) ){
fprintf(stderr, "Invalid address\n");
exit(EXIT_FAILURE);
}
/*绑定通信结构体*/
if( bind(fd, (Addr *)&myaddr, sizeof(Addr_in)) )
ErrExit("bind");
while(1){
recvfrom(fd, buf, BUFSIZ, 0, (Addr *)&peeraddr, &peerlen);
printf("[%s:%d]%s\n", inet_ntoa(peeraddr.sin_addr), ntohs(peeraddr.sin_port), buf);
}
return 0;
}
- send
c
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */
#include <unistd.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <strings.h>
#include <string.h>
#define ErrExit(msg) do {perror(msg); exit(EXIT_FAILURE);} while(0)
typedef struct sockaddr Addr;
typedef struct sockaddr_in Addr_in;
int main(int argc, char *argv[])
{
int fd = -1;
Addr_in peeraddr;
socklen_t peerlen = sizeof(peeraddr);
char buf[BUFSIZ] = {};
/*参数检查*/
if(argc < 3){
fprintf(stderr, "%s<multiaddr><port>", argv[0]);
exit(EXIT_FAILURE);
}
/*创建套接字*/
if( (fd = socket(AF_INET, SOCK_DGRAM, 0) ) < 0)
ErrExit("socket");
/*设置通信结构体*/
peeraddr.sin_family = AF_INET;
peeraddr.sin_port = htons(atoi(argv[2]));
if(!inet_aton(argv[1], &peeraddr.sin_addr) ){
fprintf(stderr, "Invalid address\n");
exit(EXIT_FAILURE);
}
while(1){
fgets(buf, BUFSIZ, stdin);
sendto(fd, buf, strlen(buf)+1, 0, (Addr *)&peeraddr, peerlen);
}
return 0;
}
广播 VS 组播
| 特性 | 广播 | 组播 |
|---|---|---|
| 传输范围 | 局域网所有主机 | 仅加入组播组的主机 |
| 地址类型 | 网段广播地址 / 255.255.255.255 | D 类地址(224.0.0.0~239.255.255) |
| 核心配置 | 发送端开启SO_BROADCAST |
接收端加入IP_ADD_MEMBERSHIP |
| 带宽占用 | 高(全网发送) | 低(精准发送) |
| 适用场景 | 局域网全网通知(如设备发现) | 一对多精准传输(如直播、组播组网) |
| 地址规则 | 可使用通用广播地址 | 一个 D 类地址对应一个组播组 |