Skip to content

概述

UDP 是无连接、不可靠的传输协议:不建立连接、不保证送达,但开销小、延迟低,适合实时性要求高、可容忍少量丢包的场景(如传感器状态推送、局域网控制、音视频流)。本教程演示:向目标服务器发送数据并循环接收回包。

用大白话讲:UDP 就像寄快递——写好收件地址(目标 IP 和端口)直接扔进快递柜(发送),不需要先确认对方在不在。优点是:不用等接通、随寄随走;缺点是不保底:包裹可能丢(丢了不重发)、可能乱序、可能重复。所以它适合「传感器状态推送」这类丢一条、下一条马上又来的场景。

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

🎯本页目标通过 UDP 客户端向电脑端 UDP 服务器收发数据,掌握封装 API 的初始化、发送、接收与释放全流程。
🧰前置条件① Ai-WB2 开发板一块(Type-C 数据线)② 2.4GHz 路由器 ③ 电脑一台(网络调试助手)④ 已按 [SDK 安装](../sdk/sdk_intro) 完成环境搭建,并完成 [连接 Wi-Fi](./wifi_connect)。
🔗相关章节可靠传输见 [TCP 客户端](./tcp_client);开发板做接收方见 [UDP 服务器](./udp_server)。

进入示例工程

打开终端,进入官方 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 OKudp 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_connect(socket_fd)

记录目标地址(UDP 无真实连接,工程封装)。

参数

  • socket_fd:socket 描述符

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

udp_client_send(socket_fd, data)

发送数据报(工程封装)。

参数

  • socket_fd:socket 描述符
  • data:待发送数据

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

udp_client_receive(socket_fd, data)

接收数据报(工程封装)。

参数

  • socket_fd:socket 描述符
  • data:接收缓冲区

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

udp_client_deinit(socket_fd)

关闭 socket 释放资源(工程封装)。

参数

  • socket_fd:socket 描述符

返回值:无

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

发送数据到指定地址(封装层底层)。

参数

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

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

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

从指定地址接收数据(封装层底层)。

参数

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

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

📌 UDP 不可靠性:数据可能丢失、乱序、重复到达,业务层需自行加序号/校验/重发机制(如 CoAP、QUIC 的思路);对可靠性要求高的场景选 TCP。


完整代码

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

📜 点击展开 udp_client/main.c 完整代码
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.cROUTER_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

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