广播与组播

文章目录

广播

核心概念

  • 广播是局域网内的一对所有传输:将数据包发送到局域网中所有主机,所有主机均可接收该数据包
  • 仅 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 类地址对应一个组播组
相关推荐
funnycoffee1233 天前
multicast 组播流必须用udp吗? tcp为何不行
网络协议·tcp/ip·udp·组播
恒星科通18 天前
隧道调频广播覆盖系统:隧道无线广播技术赋能行车安全升级
安全·广播·应急广播
恒星科通1 个月前
校园广播系统:全场景校园音频解决方案
运维·服务器·安全·音视频·广播·应急广播
故事不长丨1 个月前
《Android EventBus详解与实战:从入门到精通,组件通信不再难》
android·事件·eventbus·广播·组件通信
恒星科通1 个月前
监狱广播系统的核心架构-深度融合,智能联动
广播·应急广播
恒星科通1 个月前
隧道应急广播的秒级响应核心:群载波强制切入技术
广播
Yxw52642 个月前
setsockopt套接字属性设置,广播,组播,并发服务器
组播·并发服务器·广播
灵感菇_2 个月前
Android Broadcast全面解析
android·广播·四大组件
赖small强3 个月前
【Linux 网络基础】网络通信中的组播与广播:基础概念、原理、抓包与应用
linux·网络·broadcast·组播·广播·multicast