概述
UDP 是无连接、不可靠的传输协议:不建立连接、不保证送达,但开销小、延迟低,适合实时性要求高、可容忍少量丢包的场景(如传感器状态推送、局域网控制、音视频流)。本教程演示:向目标服务器发送数据并循环接收回包。
用大白话讲:UDP 就像寄快递——写好收件地址(目标 IP 和端口)直接扔进快递柜(发送),不需要先确认对方在不在。优点是快:不用等接通、随寄随走;缺点是不保底:包裹可能丢(丢了不重发)、可能乱序、可能重复。所以它适合「传感器状态推送」这类丢一条、下一条马上又来的场景。
本教程基于安信可官方 SDK(Ai-Thinker-Open/Ai-Thinker-WB2,版本
release_bl_iot_sdk_1.6.40)的官方示例applications/protocols/socket/udp_cllient编写(注意官方目录名拼写为udp_cllient),代码可在本地 SDK 中直接找到。
打开终端,进入官方 udp_cllient 示例工程目录:
cd ~/Ai-Thinker-WB2/applications/protocols/socket/udp_cllient
说明:
cd是「进入目录」的命令,进入官方示例工程;后面所有make命令都要在这个目录下执行。
💡 官方目录名为
udp_cllient(两个 l),进入子工程目录udp_client才是代码所在。
打开 udp_client/main.c,修改开头的 SSID/密码与服务器地址:
#define ROUTER_SSID "your ssid"
#define ROUTER_PWD "your password"
#define UDP_SERVER_IP "127.0.0.0"
#define UDP_SERVER_PORT 7878
将 UDP_SERVER_IP 改为电脑的局域网 IP(如 192.168.1.50),端口(网络里的「房门号」)与电脑端 UDP 调试工具一致。
💡 官方默认
127.0.0.0是占位地址,不修改直接烧录将连接失败。电脑 IP 可在ipconfig(Windows)/ifconfig(Linux/Mac)中查看。
打开 udp_client/main.c,本步完整代码已移至文末,见:
📜 完整代码 — 位于本页「完整代码」章节,默认折叠,点击展开,与官方示例(
applications/protocols/socket/udp_cllient/udp_client/main.c)完全一致。
代码要点:
| 代码 | 作用 |
|---|---|
udp_client_init(ip, port) |
创建 UDP socket 并初始化(udp_client.h 封装);不创建就没法寄快递 |
udp_client_connect(socketfd) |
UDP 无连接,此步记录目标地址(返回 0 成功);相当于把收件地址抄在信封上 |
udp_client_send(socketfd, "hell udp server") |
发送数据(不保证送达);把包裹扔出去,丢不丢不负责 |
udp_client_receive(socketfd, buf) |
接收回包(非阻塞轮询);没人回就继续干别的,不干等 |
udp_client_send(socketfd, "client close") |
退出前通知服务器;打个招呼再走,能不能送到看运气 |
udp_client_deinit(socketfd) |
释放 socket 资源;不释放会一直占着内存 |
💡 UDP 与 TCP 的差别:无需三次握手,
connect只是记录目标地址;无连接状态,服务器掉线也不会通知客户端;数据包最大约 1472 字节(以太网 MTU 1500 减 IP/UDP 头)。
在工程目录执行编译:
make -j8
说明:
make是「编译」命令,把代码变成开发板能运行的固件(烧进开发板的程序);-j8表示用 8 个 CPU 核并行编译,更快。
编译成功后生成固件 build_out/udp_client.bin。
开发板保持 USB 连接,确认串口设备号后执行烧录:
make flash p=/dev/ttyUSB0 b=921600
说明:
make flash是「烧录」命令,把编译好的固件下载进开发板;p=后面是串口设备号(Linux 下常为/dev/ttyUSB0,Windows 下是COM3之类,以你电脑实际为准),b=921600是烧录波特率(串口传数据的速度),保持默认即可。
⏳ 烧录过程中按提示长按开发板 EN 键进入下载模式,等待进度条完成即烧录成功。
先准备电脑端:网络调试助手选择 UDP Server 模式,监听 7878 端口(需与 UDP_SERVER_PORT 一致)。
烧录完成后开发板自动重启运行,串口(波特率 921600,串口传数据的「语速」,两边必须一致)打印:
[APP] [EVT] GOT IP 5
udp client task run
udp_client_task:udp client connect OK
udp client send OK
电脑调试助手收到 hell udp server。向开发板回发 hello wb2,串口打印:
udp_client_task:udp receive data:hello wb2
向开发板发送 close,开发板打印 udp receive data:close 并退出任务(发送 client close 给电脑后结束)。
出现 udp client connect OK、udp client send OK,且电脑助手收到 hell udp server、串口能打印回发的 hello wb2 即成功。如果电脑收不到数据,先确认开发板已联网(先看到 GOT IP)、UDP_SERVER_IP 已改成电脑的局域网 IP、电脑端监听端口与 UDP_SERVER_PORT 一致,见文末 FAQ。
代码执行流程
例程从启动到运行的完整流程如下(图中的循环箭头表示反复执行):
本文 API 汇总
udp_client_init(server_ip, port)
创建 UDP socket 并初始化(工程封装)。
参数:
server_ip:目标 IP 字符串port:目标端口
返回值:成功返回 socket 描述符;失败返回 -1
udp_client_send(socket_fd, data)
发送数据报(工程封装)。
参数:
socket_fd:socket 描述符data:待发送数据
返回值:成功返回发送字节数;失败返回 <0
udp_client_receive(socket_fd, data)
接收数据报(工程封装)。
参数:
socket_fd:socket 描述符data:接收缓冲区
返回值:成功返回接收字节数;失败返回负值
sendto(fd, buf, len, flags, addr, addrlen)
发送数据到指定地址(封装层底层)。
参数:
fd:socket 描述符buf:数据缓冲区len:缓冲区长度flags:标志,传0addr:目标地址(struct sockaddr*)addrlen:地址长度
返回值:成功返回发送字节数;失败返回负值
recvfrom(fd, buf, len, flags, addr, addrlen)
从指定地址接收数据(封装层底层)。
参数:
fd:socket 描述符buf:接收缓冲区len:缓冲区长度flags:标志,传0addr:输出参数,来源地址addrlen:地址长度
返回值:成功返回接收字节数;失败返回负值
📌 UDP 不可靠性:数据可能丢失、乱序、重复到达,业务层需自行加序号/校验/重发机制(如 CoAP、QUIC 的思路);对可靠性要求高的场景选 TCP。
完整代码
以下为 udp_client/main.c 完整源码,与官方示例(applications/protocols/socket/udp_cllient/udp_client/main.c)完全一致:
📜 点击展开 udp_client/main.c 完整代码
#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_client.h"
#define ROUTER_SSID "your ssid"
#define ROUTER_PWD "your password"
#define UDP_SERVER_IP "127.0.0.0"
#define UDP_SERVER_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 udp_client_task
*
* @param arg
*/
static void udp_client_task(void* arg)
{
blog_info("udp client task run\r\n");
int socketfd;
int ret = 0;
char* tcp_buff = pvPortMalloc(512);
memset(tcp_buff, 0, 512);
socketfd = udp_client_init(UDP_SERVER_IP, UDP_SERVER_PORT);
if (!udp_client_connect(socketfd)) {
blog_info("%s:udp client connect OK\r\n", __func__);
}
else goto __exit;
if (udp_client_send(socketfd, "hell udp server")<0) {
printf("udp client send fail\r\n");
goto __exit;
}
else
blog_info("udp client send OK\r\n");
while (1) {
ret = udp_client_receive(socketfd, tcp_buff);
if (ret>0) {
blog_info("%s:udp receive data:%s \r\n", __func__, tcp_buff);
if (strstr(tcp_buff, "close")) goto __exit;
memset(tcp_buff, 0, 512);
}
vTaskDelay(100/portTICK_PERIOD_MS);
}
__exit:
vPortFree(tcp_buff);
udp_client_send(socketfd, "client close");
udp_client_deinit(socketfd);
vTaskDelete(NULL);
}
/**
* @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:
{
printf("[APP] [EVT] INIT DONE %lld\r\n", aos_now_ms());
wifi_mgmr_start_background(&conf);
}
break;
case CODE_WIFI_ON_MGMR_DONE:
{
printf("[APP] [EVT] MGMR DONE %lld\r\n", aos_now_ms());
//_connect_wifi();
wifi_sta_connect(ROUTER_SSID, ROUTER_PWD);
}
break;
case CODE_WIFI_ON_SCAN_DONE:
{
printf("[APP] [EVT] SCAN Done %lld\r\n", aos_now_ms());
// wifi_mgmr_cli_scanlist();
}
break;
case CODE_WIFI_ON_DISCONNECT:
{
printf("[APP] [EVT] disconnect %lld\r\n", aos_now_ms());
}
break;
case CODE_WIFI_ON_CONNECTING:
{
printf("[APP] [EVT] Connecting %lld\r\n", aos_now_ms());
}
break;
case CODE_WIFI_CMD_RECONNECT:
{
printf("[APP] [EVT] Reconnect %lld\r\n", aos_now_ms());
}
break;
case CODE_WIFI_ON_CONNECTED:
{
printf("[APP] [EVT] connected %lld\r\n", aos_now_ms());
}
break;
case CODE_WIFI_ON_PRE_GOT_IP:
{
printf("[APP] [EVT] connected %lld\r\n", aos_now_ms());
}
break;
case CODE_WIFI_ON_GOT_IP:
{
printf("[APP] [EVT] GOT IP %lld\r\n", aos_now_ms());
printf("[SYS] Memory left is %d Bytes\r\n", xPortGetFreeHeapSize());
//WiFi connection succeeded, create UDP client task
xTaskCreate(udp_client_task, (char*)"udp_client_task", 1024*2, NULL, 16, NULL);
}
break;
case CODE_WIFI_ON_PROV_SSID:
{
printf("[APP] [EVT] [PROV] [SSID] %lld: %s\r\n",
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:
{
printf("[APP] [EVT] [PROV] [BSSID] %lld: %s\r\n",
aos_now_ms(),
event->value ? (const char*)event->value : "UNKNOWN");
if (event->value)
{
vPortFree((void*)event->value);
}
}
break;
case CODE_WIFI_ON_PROV_PASSWD:
{
printf("[APP] [EVT] [PROV] [PASSWD] %lld: %s\r\n", 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:
{
printf("[APP] [EVT] [PROV] [CONNECT] %lld\r\n", aos_now_ms());
printf("connecting to %s:%s...\r\n", ssid, password);
wifi_sta_connect(ssid, password);
}
break;
case CODE_WIFI_ON_PROV_DISCONNECT:
{
printf("[APP] [EVT] [PROV] [DISCONNECT] %lld\r\n", aos_now_ms());
}
break;
default:
{
printf("[APP] [EVT] Unknown code %u, %lld\r\n", 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...\r\n");
tcpip_init(NULL, NULL);
puts("[OS] proc_main_entry task...\r\n");
xTaskCreate(proc_main_entry, (char*)"main_entry", 1024, NULL, 15, NULL);
}常见问题与踩坑提示
⚠️ 电脑收不到数据
原因:UDP_SERVER_IP 未改(默认 127.0.0.0 占位)、端口不一致、或电脑防火墙拦截
解决:确认 IP 为电脑局域网 IP、端口一致;关闭防火墙或放行 UDP 端口
⚠️ 收到数据但偶尔丢失
原因:UDP 本身不可靠,Wi-Fi 弱网下丢包正常
解决:业务上做应用层重传;或改用 TCP
⚠️ 发送大包失败
原因:超过 UDP 单包上限(MTU 1500 - 40 = 1460 字节)
解决:单包控制在 1400 字节以内;大文件用 TCP 或 UDP 分包 + 重组
⚠️ 服务器换了 IP 后旧地址仍能收到数据
原因:UDP socket 未重新 connect 更新目标地址
解决:换目标地址时重新调用 udp_client_connect 或重建 socket
⚠️ 一直打印 Connecting,始终没有 GOT IP(连不上路由器)
原因:SSID/密码填错、路由器是 5GHz、或信号太弱
解决:核对 udp_client/main.c 里 ROUTER_SSID/ROUTER_PWD 与路由器完全一致;确认路由器是 2.4GHz(开发板不支持 5GHz);把开发板靠近路由器再试
⚠️ 串口找不到设备 / 打不开
原因:USB 转串口驱动未装、权限不足,或数据线只能充电不能传数据
解决:Linux 用 lsusb/dmesg 查看设备,权限不足可 sudo chmod 666 /dev/ttyUSB0;Windows 装驱动后到设备管理器查 COM 口;换一根能传数据的线
⚠️ 烧录一直等待 / 失败
原因:未进入下载模式、波特率不对、或串口号填错
解决:烧录时按提示长按 EN 键进入下载模式;确认 p=/dev/ttyUSB0 换成你实际的串口;换 USB 口或数据线重试
运行自检
电脑调试助手收到 hell udp server,且开发板能收到回发数据,即 UDP 客户端验证通过。
遇到问题?
如有其他问题,请到统一的提问与讨论区:Ai-Thinker Discussions

