概念先知道
- UDP 组播(Multicast):把数据发给“加入了同一个组播组的一批设备”,介于单播(一对一)和广播(一对全部)之间,跨子网也能用路由器转发。
- 组播地址:IPv4 的组播地址范围是
224.0.0.0 ~ 239.255.255.255,示例用239.0.0.1(本地管理组地址)。 - IGMP:管理“谁加入了哪个组”的协议。设备加入组播组时要发 IGMP 报文,lwIP 在
LWIP_IGMP=1时支持(SDK 默认开启)。 - IP_ADD_MEMBERSHIP:通过
setsockopt把套接字加入组播组,参数是ip_mreq结构体(组地址 + 本机接口地址)。
例程功能简介
SDK 无 UDP 组播例程,本页自编示例在 wifi_tcp 例程骨架上编写:
- 拿到 IP 后创建
udp_multicast_task; - 绑定本地
6000端口,并用IP_ADD_MEMBERSHIP加入组播组239.0.0.1; - 每 2 秒向
239.0.0.1:6000发送hello multicast; - 同时接收组内其他成员发来的数据并打印。
- 同族参考:发给全网段所有设备用 UDP 广播,点对点用 UDP 客户端。
操作步骤
SDK 没有 UDP 组播例程,本页在 wifi_tcp 例程骨架上编写(标注自编示例)。先进入目录:
cd examples/wifi/sta/wifi_tcp用下面的自编 main.c 覆盖例程目录里的 main.c(SDK 的 lwipopts 默认已开启 LWIP_IGMP,无需额外配置):
统一填写 bl616:
make CHIP=bl616 BOARD=bl616dk按住 BOOT 短按 EN/RST 进入下载模式后烧录:
make flash CHIP=bl616 COMX=/dev/ttyUSB0串口助手波特率 2000000,连接路由器后程序自动加入组播组 239.0.0.1:6000,每 2 秒向组内发一次数据并打印收到的组播。可在另一台电脑上加入同一组播组观察(如 nc -u 239.0.0.1 6000)。
wifi_sta_connect Your_SSID 12345678代码执行流程
UDP 组播从启动到收发的完整流程如下:
例程调用的 API 介绍
setsockopt(sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq))
把套接字加入组播组。mreq 是 struct ip_mreq:imr_multiaddr 填组地址 239.0.0.1,imr_interface 填本机接口地址(示例用 INADDR_ANY 自动选择)。
参数:
sock:套接字句柄IP_ADD_MEMBERSHIP:加入组播组选项&mreq:ip_mreq结构体
返回值:0 成功;-1 失败
bind(sock, addr, len)
绑定本地端口(0.0.0.0:6000),组播数据到达该端口后才会被本套接字接收。
参数:
sock:套接字句柄addr:sockaddr_in,端口htons(6000)、地址INADDR_ANYlen:地址结构长度
返回值:0 成功;-1 失败
sendto / recvfrom(sock, buf, len, 0, addr, len)
发送时目标地址填组播地址 239.0.0.1:6000;接收时 recvfrom 返回发送方地址。
参数:
sock:套接字句柄buf/len:数据与长度addr/len:目标(发送)/ 来源(接收)地址
返回值:实际收/发字节数;< 0 出错
完整代码
以下为自编示例 main.c(基于 SDK wifi_tcp 例程骨架编写,非 SDK 自带文件),默认折叠,点击展开:
📜 点击展开 自编 udp_multicast main.c 完整代码
/*
* 自编示例:UDP 组播(基于 lwIP socket + IGMP)
* 使用方法:
* 1. 用本文件覆盖 examples/wifi/sta/wifi_tcp/main.c;
* 2. make CHIP=bl616 BOARD=bl616dk && make flash CHIP=bl616 COMX=/dev/ttyUSB0;
* 3. 串口执行 wifi_sta_connect <SSID> <密码>,程序自动加入组播组并收发。
*/
#include "FreeRTOS.h"
#include "task.h"
#include "timers.h"
#include <string.h>
#include <lwip/tcpip.h>
#include <lwip/sockets.h>
#include <lwip/netdb.h>
#include "wifi_mgmr_ext.h"
#include "fhost_api.h"
#include "wifi_mgmr.h"
#include "bflb_irq.h"
#include "bflb_uart.h"
#include "rfparam_adapter.h"
#include "async_event.h"
#include "mm.h"
#include "board.h"
#include "shell.h"
#define DBG_TAG "MAIN"
#include "log.h"
#define MULTICAST_IP "239.0.0.1"
#define MULTICAST_PORT 6000
static volatile uint32_t wifi_state = 0;
static struct bflb_device_s *uart0;
extern void shell_init_with_task(struct bflb_device_s *shell);
void wifi_event_handler(async_input_event_t ev, void *priv)
{
switch (ev->code) {
case CODE_WIFI_ON_GOT_IP:
wifi_state = 1;
LOG_I("Got IP\r\n");
break;
case CODE_WIFI_ON_DISCONNECT:
wifi_state = 0;
LOG_I("WiFi disconnected\r\n");
break;
default:
break;
}
}
static void udp_multicast_task(void *param)
{
int sock;
struct sockaddr_in local, group, remote;
struct ip_mreq mreq;
socklen_t addr_len = sizeof(remote);
char send_buf[] = "hello multicast";
char recv_buf[128];
/* 等待 Wi-Fi 拿到 IP */
while (!wifi_state) {
vTaskDelay(pdMS_TO_TICKS(200));
}
sock = socket(AF_INET, SOCK_DGRAM, 0);
if (sock < 0) {
LOG_E("create socket failed\r\n");
vTaskDelete(NULL);
return;
}
/* 绑定本地端口,用于接收组播 */
memset(&local, 0, sizeof(local));
local.sin_family = AF_INET;
local.sin_port = htons(MULTICAST_PORT);
local.sin_addr.s_addr = INADDR_ANY;
if (bind(sock, (struct sockaddr *)&local, sizeof(local)) < 0) {
LOG_E("bind failed\r\n");
}
/* 加入组播组 239.0.0.1 */
memset(&mreq, 0, sizeof(mreq));
mreq.imr_multiaddr.s_addr = inet_addr(MULTICAST_IP);
mreq.imr_interface.s_addr = INADDR_ANY;
if (setsockopt(sock, IPPROTO_IP, IP_ADD_MEMBERSHIP,
&mreq, sizeof(mreq)) < 0) {
LOG_E("add membership failed\r\n");
}
memset(&group, 0, sizeof(group));
group.sin_family = AF_INET;
group.sin_port = htons(MULTICAST_PORT);
group.sin_addr.s_addr = inet_addr(MULTICAST_IP);
LOG_I("UDP multicast join %s:%d\r\n", MULTICAST_IP, MULTICAST_PORT);
while (1) {
int ret = sendto(sock, send_buf, strlen(send_buf), 0,
(struct sockaddr *)&group, sizeof(group));
if (ret < 0) {
LOG_E("sendto multicast failed\r\n");
} else {
LOG_I("multicast send %d bytes\r\n", ret);
}
ret = recvfrom(sock, recv_buf, sizeof(recv_buf) - 1, 0,
(struct sockaddr *)&remote, &addr_len);
if (ret >= 0) {
recv_buf[ret] = '\0';
LOG_I("recv %d bytes from %s: %s\r\n",
ret, inet_ntoa(remote.sin_addr), recv_buf);
}
vTaskDelay(pdMS_TO_TICKS(2000));
}
}
static void wifi_start_firmware_task(void *param)
{
LOG_I("Starting wifi ...\r\n");
async_register_event_filter(EV_WIFI, wifi_event_handler, NULL);
wifi_task_create();
LOG_I("Starting fhost ...\r\n");
fhost_init();
vTaskDelete(NULL);
}
int main(void)
{
board_init();
uart0 = bflb_device_get_by_name("uart0");
shell_init_with_task(uart0);
if (0 != rfparam_init(0, NULL, 0)) {
LOG_I("PHY RF init failed!\r\n");
return 0;
}
tcpip_init(NULL, NULL);
xTaskCreate(wifi_start_firmware_task, "wifi init", 1024, NULL, 10, NULL);
xTaskCreate(udp_multicast_task, "udp mcast", 1024, NULL, 11, NULL);
vTaskStartScheduler();
while (1) {
}
}FAQ
IP_ADD_MEMBERSHIP 失败
确认 lwipopts 里 LWIP_IGMP 为 1(SDK 默认开启);确认已先 bind() 到组播端口;组播地址必须落在 224.0.0.0 ~ 239.255.255.255 范围内。
发得出去但收不到别人的组播
确认双方加入了同一个组播组和端口;部分路由器/AP 默认不转发组播,需在路由器开启“多播/IGMP Snooping”支持;同网段下用 nc -u 239.0.0.1 6000 先验证电脑侧。
遇到问题?
如有其他问题,请到统一的提问与讨论区:Ai-Thinker Discussions

