Skip to content

概念先知道

  • 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 客户端

操作步骤

1
进入例程目录

SDK 没有 UDP 组播例程,本页在 wifi_tcp 例程骨架上编写(标注自编示例)。先进入目录:

cd examples/wifi/sta/wifi_tcp
2
替换 main.c

用下面的自编 main.c 覆盖例程目录里的 main.c(SDK 的 lwipopts 默认已开启 LWIP_IGMP,无需额外配置):

3
编译工程

统一填写 bl616

make CHIP=bl616 BOARD=bl616dk
4
烧录固件

按住 BOOT 短按 EN/RST 进入下载模式后烧录:

make flash CHIP=bl616 COMX=/dev/ttyUSB0
5
连接 Wi-Fi 并验证

串口助手波特率 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))

把套接字加入组播组。mreqstruct ip_mreqimr_multiaddr 填组地址 239.0.0.1imr_interface 填本机接口地址(示例用 INADDR_ANY 自动选择)。

参数

  • sock:套接字句柄
  • IP_ADD_MEMBERSHIP:加入组播组选项
  • &mreqip_mreq 结构体

返回值0 成功;-1 失败

bind(sock, addr, len)

绑定本地端口(0.0.0.0:6000),组播数据到达该端口后才会被本套接字接收。

参数

  • sock:套接字句柄
  • addrsockaddr_in,端口 htons(6000)、地址 INADDR_ANY
  • len:地址结构长度

返回值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 完整代码
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

Released under the MIT License. Build Time 2026-09-11 14:52:23