Skip to content

概述

开发板作为 UDP 服务器时监听固定端口,接收来自任意客户端的 UDP 数据报并回显。UDP 服务器无需维护连接,一个 socket 即可服务所有客户端,代码比 TCP 服务器更简单,常用于局域网内多设备广播/多设备上报汇聚。本教程演示:监听 7878 端口,收到数据打印来源 IP 并原样回发。

用大白话讲:UDP 服务器就是开发板开了个收发快递窗口——在 7878 这个「房门号」(端口)上收件:谁寄来的(来源 IP 和端口)都照单全收,拆开看一眼、再原样包一个寄回去(回显)。和 TCP 服务器不同,它不用登记每个客人(不用 accept、不用维护连接),一个窗口就能同时接待所有客户端,代码也因此更简单。

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

🎯本页目标通过开发板开启 UDP 服务器并用电脑客户端验证收发,掌握 UDP socket 的绑定、接收(recvfrom)与发送(sendto)。
🧰前置条件① Ai-WB2 开发板一块(Type-C 数据线)② 2.4GHz 路由器 ③ 电脑一台(网络调试助手)④ 已按 [SDK 安装](../sdk/sdk_intro) 完成环境搭建,并完成 [连接 Wi-Fi](./wifi_connect)。
🔗相关章节TCP 版服务器见 [TCP 服务器](./tcp_server);发送广播见 [UDP 广播](./udp_broadcast)。

进入示例工程

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

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

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

修改路由器参数

打开 udp_server/main.c,修改开头的 SSID/密码(同 连接 Wi-Fi):

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

#define UDP_SERVER_PORT 7878

💡 UDP_SERVER_PORT 7878 为监听端口(服务器上的「房门号」),电脑端客户端需使用同一端口才能找对门。服务器与客户端需在同一局域网。

编写代码

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

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

代码要点:

代码 作用
udp_server_init(NULL, 7878) 创建 socket 并绑定监听 7878 端口;占好窗口才收得到件
recvfrom(fd, buf, 512, MSG_DONTWAIT, &addr, &len) 非阻塞接收数据,同时得到来源地址(IP + 端口);没件就继续等下一轮,不卡住
inet_ntoa(u_sockaddr.sin_addr.s_addr) 打印客户端 IP;把二进制地址翻译成人能看的数字
sendto(fd, buf, len, 0, &addr, len) 回发给来源客户端;原样寄回去,对方才知道你收到了
strstr(udp_buf, "close") 收到 close 时回复并退出;对方说关门就关门
udp_server_deinit() 释放服务器资源;不释放,下次启动可能报端口占用

💡 与 TCP 服务器对比:UDP 不需要 accept,一个 socket 直接收/发所有客户端;recvfrom 返回数据时顺带填充 u_sockaddr(来源地址),回发时原样用回即可。MSG_DONTWAIT 非阻塞 + vTaskDelay(50) 轮询是官方示例的做法。

编译工程

在工程目录执行编译:

make -j8

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

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

烧录固件

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

make flash p=/dev/ttyUSB0 b=921600

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

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

运行验证

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

电脑网络调试助手选择 UDP Client 模式:

配置项
目标 IP 开发板 IP(如 192.168.1.100
端口 7878

发送 hello wb2,开发板串口打印:

udp server task run
192.168.1.50:hello wb2

电脑助手收到回显 hello wb2。发送 close,开发板回复 server close connect 后服务器任务退出。

串口打印 udp server task run 和「客户端 IP:内容」(如 192.168.1.50:hello wb2),且电脑助手收到回显 hello wb2 即成功。如果电脑发送后开发板没反应,先确认开发板已联网(先看到 GOT IP)、电脑与开发板在同一局域网、端口填的是 7878,见文末 FAQ。

💡 UDP 服务器天然支持多客户端:电脑、手机、另一块开发板同时发数据都会正常收到并回显,互不干扰(无需为每个客户端建连接)。

代码执行流程

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


本文 API 汇总

udp_server_init(s_ip, s_port)

创建 socket 并绑定监听端口(工程封装,s_ip 传 NULL 监听所有地址)。

参数

  • s_ip:监听地址,传 NULL 表示监听所有地址
  • s_port:监听端口(官方示例 7878

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

udp_server_deinit

释放服务器资源(工程封装)。

返回值:无

socket(domain, type, proto)

创建 socket(SOCK_DGRAM 数据报类型)。

参数

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

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

bind(fd, addr, len)

绑定端口(封装层底层)。

参数

  • fd:socket 描述符
  • addr:本地地址(INADDR_ANY 监听所有地址)
  • len:地址长度

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

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

接收数据并获取来源地址(MSG_DONTWAIT 非阻塞)。

参数

  • fd:socket 描述符
  • buf:接收缓冲区
  • len:缓冲区长度
  • flags:可选 MSG_DONTWAIT(非阻塞)
  • addr:输出参数,来源地址
  • addrlen:地址长度

返回值:成功返回接收字节数;非阻塞无数据时 <= 0;失败返回负值

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

发送数据到指定地址。

参数

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

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

inet_ntoa(addr)

IP 地址转点分十进制字符串。

参数

  • addr:网络字节序 IP(in_addr

返回值:点分十进制字符串(非线程安全)

pvPortMalloc / vPortFree(size / ptr)

FreeRTOS 动态内存分配 / 释放。

参数

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

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

📌 struct sockaddr_in 关键字段:sin_addr.s_addr(客户端 IP)、sin_port(客户端端口);recvfrom 非阻塞时 ret <= 0 表示暂无数据,配合 vTaskDelay 轮询。


完整代码

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

📜 点击展开 udp_server/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_server.h"

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

#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_server_task
 *
 * @param arg
 */
static void udp_server_task(void* arg)
{
    int socket_fd;
    int ret = 0;
    socket_fd = udp_server_init(NULL, UDP_SERVER_PORT);
    struct sockaddr_in u_sockaddr;
    int socke_len = sizeof(struct sockaddr_in);
    char* udp_buf = pvPortMalloc(512);
    while (1) {
        memset(udp_buf, 0, 512);
        ret = recvfrom(socket_fd, udp_buf, 512, MSG_DONTWAIT, (struct sockaddr*)&u_sockaddr, (socklen_t*)&socke_len);
        if (ret>0) {
            blog_info("%s:%s\r\n", inet_ntoa(u_sockaddr.sin_addr.s_addr), udp_buf);
            ret = sendto(socket_fd, udp_buf, strlen(udp_buf), 0, (struct sockaddr*)&u_sockaddr, socke_len);
            if (strstr(udp_buf, "close")!=NULL) {
                ret = sendto(socket_fd, "server close connect", strlen("server close connect"), 0, (struct sockaddr*)&u_sockaddr, socke_len);
                goto __exit;
            }
        }
        vTaskDelay(50/portTICK_PERIOD_MS);
    }
__exit:
    vPortFree(udp_buf);
    udp_server_deinit();
    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 server task
            xTaskCreate(udp_server_task, "udp_server_task", 2048, 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);
}

常见问题与踩坑提示

⚠️ 电脑客户端发送后开发板无反应
原因:端口不一致、目标 IP 错误、或开发板未 GOT IP
解决:确认电脑端目标 IP 为开发板 IP、端口为 7878;确认串口先打印了 GOT IPudp server task run

⚠️ 能收到但回显丢失
原因:UDP 本身不可靠,弱网下丢包
解决:连续多发几次验证;业务上要求可靠时改用 TCP

⚠️ 接收缓冲区不够导致截断
原因udp_buf 只有 512 字节
解决:单包数据控制 512 字节内;需要更大则同步增大 pvPortMallocrecvfrom 长度(UDP 单包上限 1460)

⚠️ 重启后端口占用报错
原因:上次连接未完全释放
解决:等待几秒重启;或 udp_server_deinit() 后重建任务

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

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

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

运行自检

电脑客户端发送任意内容,开发板串口打印客户端 IP 与内容并回显,即 UDP 服务器验证通过。

遇到问题?

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

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