Skip to content

概述

UDP 广播(broadcast)是把数据同时发往同一局域网内所有设备的方式:发送端只需往广播地址发一份数据,网内每台监听该端口的设备都能收到,无需知道对方 IP。常用于设备发现、固件升级通知、局域网同步等场景。本教程演示:开发板每 2 秒发送一条广播消息,同时监听广播端口并打印收到的数据。

用大白话讲:UDP 广播就像小区里的大喇叭——物业对着喇叭喊一声(发一份数据到广播地址),不用挨家挨户通知、也不用知道每家在哪(无需知道对方 IP),全小区(同一局域网)开着收音机(监听同一端口)的设备都能听到。省事,但喊一次所有人都听见了——不适合说悄悄话(私密数据),喊太频繁也会吵到别人(占用带宽)

本教程基于安信可官方 SDKAi-Thinker-Open/Ai-Thinker-WB2,版本 release_bl_iot_sdk_1.6.40)的官方示例 applications/protocols/socket/udp_broadcast 编写,代码可在本地 SDK 中直接找到。

🎯本页目标通过 SO_BROADCAST 选项实现向局域网发送 UDP 广播并监听收包,掌握广播 socket 的设置与收发流程。
🧰前置条件① Ai-WB2 开发板一块(Type-C 数据线)② 2.4GHz 路由器 ③ 电脑一台(网络调试助手)④ 已按 [SDK 安装](../sdk/sdk_intro) 完成环境搭建,并完成 [连接 Wi-Fi](./wifi_connect)。
🔗相关章节点对点收发见 [UDP 服务器](./udp_server);组播(选择性接收)见 [UDP 组播](./udp_multicast)。

进入示例工程

打开终端,进入官方 udp_broadcast 示例工程目录:

cd ~/Ai-Thinker-WB2/applications/protocols/socket/udp_broadcast

说明:cd 是「进入目录」的命令,进入官方示例工程;后面所有 make 命令都要在这个目录下执行。

修改路由器参数

打开 udp_broadcast/main.c,修改开头的 SSID/密码(官方示例默认是 FAE@Seahi,需改成自己的路由器):

#define ROUTER_SSID "your ssid"
#define ROUTER_PWD "your password"

#define BROADCAST_PORT 7878

💡 BROADCAST_PORT 7878 为广播/监听端口(网络里的「房门号」):发送端以该端口发出广播,接收端也必须绑定同一端口才能收到。所有设备需在同一局域网。

编写代码

打开 udp_broadcast/main.c,本步完整代码已移至文末,见:

📜 完整代码 — 位于本页「完整代码」章节,默认折叠,点击展开,与官方示例(applications/protocols/socket/udp_broadcast/udp_broadcast/main.c)完全一致。

代码要点:

代码 作用
udp_broadcast_send_init(7878) 创建发送任务(优先级 10)与接收任务(优先级 11);一个喊一个听,互不耽误
setsockopt(fd, SOL_SOCKET, SO_BROADCAST, &opt, 1) 关键:允许 socket 发送广播包,不加此选项 sendto 报错;相当于给喇叭办「大声说话」许可证
netif_find("st1") 获取 station 网络接口(st1),从中取接口信息;找不到网卡,广播就没处发
sendto(fd, buff, len, 0, &addr, len) 发送广播消息(此处目标为接口自身 IP,每 2 秒一次);官方代码是「对着自己喊」做回环验证
sin_addr.s_addr = INADDR_ANY 接收任务绑定所有地址,网内广播包都能收到;不挑地址,谁来都收
bind(fd, &addr, len) 绑定 7878 端口开始监听;不绑定就收不到别人发来的广播
recvfrom(fd, buff, ..., 0, NULL, NULL) 接收广播数据(不关心来源,地址参数传 NULL);大喇叭不用知道是谁喊的

⚠️ 官方示例说明:发送目标为 sta_netif->ip_addr.addr(本机接口 IP),烧录后广播消息在本机回环验证收发。要让局域网内其他设备真正收到广播,需把发送目标改为广播地址 INADDR_BROADCAST255.255.255.255)或子网定向广播地址(如 192.168.1.255),详见下方踩坑提示。

编译工程

在工程目录执行编译:

make -j8

说明:make 是「编译」命令,把代码变成开发板能运行的固件(烧进开发板的程序);-j8 表示用 8 个 CPU 核并行编译,更快。

编译成功后生成固件 build_out/udp_broadcast.bin

烧录固件

开发板保持 USB 连接,确认串口设备号后执行烧录:

make flash p=/dev/ttyUSB0 b=921600

说明:make flash 是「烧录」命令,把编译好的固件下载进开发板;p= 后面是串口设备号(Linux 下常为 /dev/ttyUSB0,Windows 下是 COM3 之类,以你电脑实际为准),b=921600 是烧录波特率(串口传数据的速度),保持默认即可。

⏳ 烧录过程中按提示长按开发板 EN 键进入下载模式,等待进度条完成即烧录成功。

运行验证

烧录完成后开发板自动重启运行,串口(波特率 921600,串口传数据的「语速」,两边必须一致)打印:

[APP] [EVT] GOT IP 5594
[SYS] Memory left is 155904 Bytes
udp send task start >>>>>>>>>>>>>>>
udp recv start >>>>>>>>>>>>>>>>>>
udp recv:hello recv node,I am send

出现每 2 秒一次的 udp recv:hello recv node,I am send 即成功——说明发送任务发出的广播被接收任务成功回环接收。如果只看到 udp send task start 而没有 udp recv:,先确认开发板已联网(先看到 GOT IP)、串口波特率一致,见文末 FAQ。

让电脑也收到广播:把发送目标改为广播地址后重新编译烧录(或保持官方代码,用另一块开发板验证),电脑网络调试助手绑定 UDP 端口 7878(监听模式),即可收到 hello recv node,I am send

💡 广播 vs 单播:单播需知道对方 IP 一对一发送;广播发一份全体接收,但会占用局域网带宽、能被同网段所有设备收到,不适合传输大量数据或私密数据

代码执行流程

例程从启动到运行的完整流程如下(图中的循环箭头表示反复执行):


本文 API 汇总

udp_broadcast_send_init(brct_port)

创建广播发送与接收两个任务(工程封装入口)。

参数

  • brct_port:广播端口(官方示例 7878

返回值:成功返回 0;失败返回负值错误码

socket(domain, type, proto)

创建 UDP socket(SOCK_DGRAM)。

参数

  • domainAF_INET(IPv4)
  • typeSOCK_DGRAM(UDP 数据报)
  • proto:传 0

返回值:成功返回 socket 描述符;失败返回 -1

setsockopt(fd, SOL_SOCKET, SO_BROADCAST, &opt, len)

允许发送广播包(发送广播前必须设置)。

参数

  • fd:socket 描述符
  • levelSOL_SOCKET
  • optnameSO_BROADCAST
  • opt:允许标志(int1
  • lensizeof(int)

返回值:成功返回 0;失败返回负值错误码

netif_find(name)

按名称查找网络接口(station 口为 "st1")。

参数

  • name:网卡名称,如 "st1"

返回值:成功返回 struct netif*;失败返回 NULL

sendto(fd, buf, len, flags, addr, addrlen)

发送数据报到指定地址(广播地址)。

参数

  • fd:socket 描述符
  • buf:数据缓冲区
  • len:缓冲区长度
  • flags:标志,传 0
  • addr:目标地址(广播地址)
  • addrlen:地址长度

返回值:成功返回发送字节数;失败返回负值

bind(fd, addr, len)

绑定本地端口(INADDR_ANY 监听所有地址)。

参数

  • fd:socket 描述符
  • addr:本地地址
  • len:地址长度

返回值:成功返回 0;失败返回负值错误码

recvfrom(fd, buf, len, flags, addr, addrlen)

接收数据报(地址参数传 NULL 可不关心来源)。

参数

  • fd:socket 描述符
  • buf:接收缓冲区
  • len:缓冲区长度
  • flags:标志,传 0
  • addr:输出参数,来源地址(不关心可传 NULL
  • addrlen:地址长度

返回值:成功返回接收字节数;失败返回负值

htons(val)

主机字节序转网络字节序(端口号必须转换)。

参数

  • val:主机字节序数值(如端口 7878

返回值:网络字节序数值

pvPortMalloc / vPortFree(size / ptr)

FreeRTOS 动态内存分配 / 释放。

参数

  • size:分配字节数
  • ptr:待释放指针(pvPortMalloc 返回值)

返回值pvPortMalloc:成功返回指针,失败返回 NULLvPortFree:无

xTaskCreate(fn, name, stack, arg, prio, handle)

创建 FreeRTOS 任务。

参数

  • fn:任务入口函数指针,必填
  • name:任务名称字符串
  • stack:任务栈大小(单位:字)
  • arg:入口函数参数指针,无参传 NULL
  • prio:任务优先级(官方示例 10/11
  • handle:任务句柄输出指针,不需要可传 NULL

返回值:成功返回 pdPASS;失败返回 pdFAIL

📌 广播地址:受限广播 255.255.255.255INADDR_BROADCAST)发往本网段所有设备;子网定向广播(如 192.168.1.255)发往指定子网。路由器默认隔离广播,广播只在同一局域网(二层)内传播。


完整代码

以下为 udp_broadcast/main.c 完整源码,与官方示例(applications/protocols/socket/udp_broadcast/udp_broadcast/main.c)完全一致:

📜 点击展开 udp_broadcast/main.c 完整代码
c
/**
 * @file main.c
 * @author your name (you@domain.com)
 * @brief
 * @version 0.1
 * @date 2022-10-13
 *
 * @copyright Copyright (c) 2022
 *
 */
#include <FreeRTOS.h>
#include <task.h>
#include <stdio.h>
#include <string.h>
#include <blog.h>
#include <aos/yloop.h>
#include <aos/kernel.h>
#include <lwip/sockets.h>
#include <lwip/tcpip.h>
#include <wifi_mgmr_ext.h>
#include <cli.h>
#include <hal_wifi.h>
#include <lwip/init.h>
#include "udp_broadcast.h"

#define ROUTER_SSID "FAE@Seahi"
#define ROUTER_PWD "fae12345678"


#define BROADCAST_PORT 7878

static wifi_conf_t conf = {
    .country_code = "CN",
};

/**
 * @brief wifi_sta_connect
 *        wifi station mode connect start
 * @param ssid
 * @param password
 */
static void wifi_sta_connect(char* ssid, char* password)
{
    wifi_interface_t wifi_interface;
    wifi_interface = wifi_mgmr_sta_enable();
    wifi_mgmr_sta_connect(wifi_interface, ssid, password, NULL, NULL, 0, 0);
}

/**
 * @brief event_cb_wifi_event
 *      wifi connet ap event Callback function
 * @param event
 * @param private_data
 */
static void event_cb_wifi_event(input_event_t* event, void* private_data)
{
    static char* ssid;
    static char* password;

    switch (event->code)
    {
        case CODE_WIFI_ON_INIT_DONE:
        {
            blog_info("[APP] [EVT] INIT DONE %lld", aos_now_ms());
            wifi_mgmr_start_background(&conf);
        }
        break;
        case CODE_WIFI_ON_MGMR_DONE:
        {
            blog_info("[APP] [EVT] MGMR DONE %lld", aos_now_ms());
            //_connect_wifi();

            wifi_sta_connect(ROUTER_SSID, ROUTER_PWD);
        }
        break;
        case CODE_WIFI_ON_SCAN_DONE:
        {
            blog_info("[APP] [EVT] SCAN Done %lld", aos_now_ms());
            // wifi_mgmr_cli_scanlist();
        }
        break;
        case CODE_WIFI_ON_DISCONNECT:
        {
            blog_error("[APP] [EVT] disconnect %lld", aos_now_ms());
        }
        break;
        case CODE_WIFI_ON_CONNECTING:
        {
            blog_info("[APP] [EVT] Connecting %lld", aos_now_ms());
        }
        break;
        case CODE_WIFI_CMD_RECONNECT:
        {
            blog_info("[APP] [EVT] Reconnect %lld", aos_now_ms());
        }
        break;
        case CODE_WIFI_ON_CONNECTED:
        {
            blog_info("[APP] [EVT] connected %lld", aos_now_ms());

        }
        break;
        case CODE_WIFI_ON_PRE_GOT_IP:
        {
            blog_info("[APP] [EVT] connected %lld", aos_now_ms());

        }
        break;
        case CODE_WIFI_ON_GOT_IP:
        {
            blog_info("[APP] [EVT] GOT IP %lld", aos_now_ms());
            blog_info("[SYS] Memory left is %d Bytes", xPortGetFreeHeapSize());
            // wifi connection succeeded, create udp multicast task
            udp_broadcast_send_init(BROADCAST_PORT);
        }
        break;
        case CODE_WIFI_ON_PROV_SSID:
        {
            blog_info("[APP] [EVT] [PROV] [SSID] %lld: %s",
                   aos_now_ms(),
                   event->value ? (const char*)event->value : "UNKNOWN");
            if (ssid)
            {
                vPortFree(ssid);
                ssid = NULL;
            }
            ssid = (char*)event->value;
        }
        break;
        case CODE_WIFI_ON_PROV_BSSID:
        {
            blog_info("[APP] [EVT] [PROV] [BSSID] %lld: %s",
                   aos_now_ms(),
                   event->value ? (const char*)event->value : "UNKNOWN");
            if (event->value)
            {
                vPortFree((void*)event->value);
            }
        }
        break;
        case CODE_WIFI_ON_PROV_PASSWD:
        {
            blog_info("[APP] [EVT] [PROV] [PASSWD] %lld: %s", aos_now_ms(),
                   event->value ? (const char*)event->value : "UNKNOWN");
            if (password)
            {
                vPortFree(password);
                password = NULL;
            }
            password = (char*)event->value;
        }
        break;
        case CODE_WIFI_ON_PROV_CONNECT:
        {
            blog_info("[APP] [EVT] [PROV] [CONNECT] %lld", aos_now_ms());
            blog_info("connecting to %s:%s...", ssid, password);
            wifi_sta_connect(ssid, password);
        }
        break;
        case CODE_WIFI_ON_PROV_DISCONNECT:
        {
            blog_error("[APP] [EVT] [PROV] [DISCONNECT] %lld", aos_now_ms());
        }
        break;
        default:
        {
            blog_warn("[APP] [EVT] Unknown code %u, %lld", event->code, aos_now_ms());
            /*nothing*/
        }
    }
}

static void proc_main_entry(void* pvParameters)
{

    aos_register_event_filter(EV_WIFI, event_cb_wifi_event, NULL);
    hal_wifi_start_firmware_task();
    aos_post_event(EV_WIFI, CODE_WIFI_ON_INIT_DONE, 0);

    vTaskDelete(NULL);
}

void main()
{
    puts("[OS] Starting TCP/IP Stack...");
    tcpip_init(NULL, NULL);
    puts("[OS] proc_main_entry task...");
    xTaskCreate(proc_main_entry, (char*)"main_entry", 1024, NULL, 15, NULL);
}

以下为 components/src/udp_broadcast.c 完整源码,与官方示例(applications/protocols/socket/udp_broadcast/components/src/udp_broadcast.c)完全一致:

📜 点击展开 components/src/udp_broadcast.c 完整代码
c
/**
 * @file udp_broadcast.c
 * @author your name (you@domain.com)
 * @brief
 * @version 0.1
 * @date 2022-11-25
 *
 * @copyright Copyright (c) 2022
 *
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <FreeRTOS.h>
#include <task.h>
#include <lwip/sockets.h>
#include <blog.h>
#include "lwip/udp.h"
#include "lwip/inet.h"
#include "lwip/netdb.h"
#include "lwip/netif.h"

static int port;
/**
 * @brief udp_send_task
 *
 * @param arg
 */
static void udp_send_task(void* arg)
{

    struct netif* sta_netif = netif_find("st1");



    int udp_send = socket(AF_INET, SOCK_DGRAM, 0);
    if (udp_send <0) {
        blog_error("socket creat fail");
        vTaskDelete(NULL);
    }

    int opt = 1;
    setsockopt(udp_send, SOL_SOCKET, SO_BROADCAST, &opt, sizeof(opt));

    struct sockaddr_in addr = {
      .sin_family = AF_INET,
      .sin_port = htons(port),
      .sin_addr.s_addr = sta_netif->ip_addr.addr,
    };
    char* buff = "hello recv node,I am send";
    blog_info("udp send task start >>>>>>>>>>>>>>>");
    while (1) {
        if (sendto(udp_send, buff, strlen(buff), 0, (struct sockaddr*)&addr, sizeof(addr))<0) {
            blog_error("buff send error");

        }
        vTaskDelay(2000/portTICK_PERIOD_MS);
    }
    vTaskDelete(NULL);
}
/**
 * @brief  udp_recv_task
 *
 * @param arg
 */
static void udp_recv_task(void* arg)
{
    struct netif* sta_netif = netif_find("st1");
    if (sta_netif==NULL) {
        blog_error("station netif fail");
        vTaskDelete(NULL);
        return;
    }

    char* buff = pvPortMalloc(512);
    int udp_recv = socket(AF_INET, SOCK_DGRAM, 0);
    struct sockaddr_in addr = {
       .sin_family = AF_INET,
       .sin_port = htons(port),
       .sin_addr.s_addr = INADDR_ANY,
    };
    int ret = bind(udp_recv, (struct sockaddr*)&addr, sizeof(addr));
    if (ret<0) {
        blog_error("socket bind error");
        vTaskDelete(NULL);
        return;
    }

    blog_info("udp recv start >>>>>>>>>>>>>>>>");
    while (1) {
        if (recvfrom(udp_recv, buff, sizeof(buff)*8, 0, NULL, NULL)) {
            blog_info("udp recv:%s", buff);
            memset(buff, 0, strlen(buff));
        }
        vTaskDelay(2000/portTICK_PERIOD_MS);
    }
    vTaskDelete(NULL);
}
/**
 * @brief
 *
 * @param brct_port
 * @return int
 */
int udp_broadcast_send_init(int brct_port)
{

    port = brct_port;

    xTaskCreate(udp_send_task, "udp_send", 1024, NULL, 10, NULL);

    xTaskCreate(udp_recv_task, "udp_recv", 1024, NULL, 11, NULL);
    return 0;
}

常见问题与踩坑提示

⚠️ 局域网内其他设备收不到广播
原因:官方示例发送目标是本机接口 IPsta_netif->ip_addr.addr),广播仅在本机回环,未真正发往全网
解决:将 udp_broadcast.c 中发送地址改为广播地址,如:

c
.sin_addr.s_addr = htonl(INADDR_BROADCAST);   // 255.255.255.255 全网广播

或子网定向广播(192.168.1.255,用 inet_addr("192.168.1.255") 设置);SO_BROADCAST 选项保持开启

⚠️ sendto 返回错误(-1)
原因:未设置 SO_BROADCAST 选项就向广播地址发送,lwIP 默认拒绝
解决:发送前必须 setsockopt(udp_send, SOL_SOCKET, SO_BROADCAST, &opt, sizeof(opt))(官方代码已设置,勿删除)

⚠️ 电脑能收到但手机收不到
原因:手机系统(iOS/部分 Android)后台限制 UDP 广播接收,或手机 App 未绑定端口
解决:优先用电脑网络调试助手验证;手机 App 需前台运行并监听对应端口

⚠️ 广播刷屏 / 网络卡顿
原因:广播发送频率过高、多台设备同时广播
解决:降低发送频率(官方 2 秒一次);生产环境用组播(见 UDP 组播)替代广播,减少对无关设备的打扰

⚠️ 一直打印 Connecting,始终没有 GOT IP(连不上路由器)
原因:SSID/密码填错、路由器是 5GHz、或信号太弱
解决:核对 udp_broadcast/main.cROUTER_SSID/ROUTER_PWD 与路由器完全一致(官方默认是 FAE@Seahi);确认路由器是 2.4GHz(开发板不支持 5GHz);把开发板靠近路由器再试

⚠️ 串口找不到设备 / 打不开
原因:USB 转串口驱动未装、权限不足,或数据线只能充电不能传数据
解决:Linux 用 lsusb/dmesg 查看设备,权限不足可 sudo chmod 666 /dev/ttyUSB0;Windows 装驱动后到设备管理器查 COM 口;换一根能传数据的线

⚠️ 烧录一直等待 / 失败
原因:未进入下载模式、波特率不对、或串口号填错
解决:烧录时按提示长按 EN 键进入下载模式;确认 p=/dev/ttyUSB0 换成你实际的串口;换 USB 口或数据线重试

运行自检

串口交替打印 udp send task start 与每 2 秒一次的 udp recv:hello recv node,I am send,即广播收发验证通过。

遇到问题?

如有其他问题,请到统一的提问与讨论区:Ai-Thinker Discussions

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