概述
UDP 广播(broadcast)是把数据同时发往同一局域网内所有设备的方式:发送端只需往广播地址发一份数据,网内每台监听该端口的设备都能收到,无需知道对方 IP。常用于设备发现、固件升级通知、局域网同步等场景。本教程演示:开发板每 2 秒发送一条广播消息,同时监听广播端口并打印收到的数据。
用大白话讲:UDP 广播就像小区里的大喇叭——物业对着喇叭喊一声(发一份数据到广播地址),不用挨家挨户通知、也不用知道每家在哪(无需知道对方 IP),全小区(同一局域网)开着收音机(监听同一端口)的设备都能听到。省事,但喊一次所有人都听见了——不适合说悄悄话(私密数据),喊太频繁也会吵到别人(占用带宽)。
本教程基于安信可官方 SDK(Ai-Thinker-Open/Ai-Thinker-WB2,版本
release_bl_iot_sdk_1.6.40)的官方示例applications/protocols/socket/udp_broadcast编写,代码可在本地 SDK 中直接找到。
打开终端,进入官方 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_BROADCAST(255.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)。
参数:
domain:AF_INET(IPv4)type:SOCK_DGRAM(UDP 数据报)proto:传0
返回值:成功返回 socket 描述符;失败返回 -1
setsockopt(fd, SOL_SOCKET, SO_BROADCAST, &opt, len)
允许发送广播包(发送广播前必须设置)。
参数:
fd:socket 描述符level:SOL_SOCKEToptname:SO_BROADCASTopt:允许标志(int传1)len:sizeof(int)
返回值:成功返回 0;失败返回负值错误码
sendto(fd, buf, len, flags, addr, addrlen)
发送数据报到指定地址(广播地址)。
参数:
fd:socket 描述符buf:数据缓冲区len:缓冲区长度flags:标志,传0addr:目标地址(广播地址)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:标志,传0addr:输出参数,来源地址(不关心可传NULL)addrlen:地址长度
返回值:成功返回接收字节数;失败返回负值
pvPortMalloc / vPortFree(size / ptr)
FreeRTOS 动态内存分配 / 释放。
参数:
size:分配字节数ptr:待释放指针(pvPortMalloc返回值)
返回值:pvPortMalloc:成功返回指针,失败返回 NULL;vPortFree:无
xTaskCreate(fn, name, stack, arg, prio, handle)
创建 FreeRTOS 任务。
参数:
fn:任务入口函数指针,必填name:任务名称字符串stack:任务栈大小(单位:字)arg:入口函数参数指针,无参传NULLprio:任务优先级(官方示例10/11)handle:任务句柄输出指针,不需要可传NULL
返回值:成功返回 pdPASS;失败返回 pdFAIL
📌 广播地址:受限广播
255.255.255.255(INADDR_BROADCAST)发往本网段所有设备;子网定向广播(如192.168.1.255)发往指定子网。路由器默认隔离广播,广播只在同一局域网(二层)内传播。
完整代码
以下为 udp_broadcast/main.c 完整源码,与官方示例(applications/protocols/socket/udp_broadcast/udp_broadcast/main.c)完全一致:
📜 点击展开 udp_broadcast/main.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 完整代码
/**
* @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;
}常见问题与踩坑提示
⚠️ 局域网内其他设备收不到广播
原因:官方示例发送目标是本机接口 IP(sta_netif->ip_addr.addr),广播仅在本机回环,未真正发往全网
解决:将 udp_broadcast.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.c 里 ROUTER_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

