Skip to content

概念先知道

  • UDP 客户端:主动向服务器“扔”数据报的一方。UDP 不需要建立连接,sendto 指定目标地址即可发送。
  • 数据报:UDP 每次收发是一个完整数据包,边界保留;不保证送达、不保证顺序,适合容忍少量丢失的实时场景。
  • sendto / recvfrom:发送要带目标地址,接收要带对方地址;因为没有连接,每次都要显式告诉内核“发给谁 / 谁发来的”。
  • UDP 端口:服务器必须监听某个端口,客户端的源端口由系统随机分配。

例程功能简介

SDK 无独立 UDP 客户端例程,本页自编示例wifi_tcp 例程骨架上编写(网络部分与官方 wifi_udp 例程同样基于 lwIP socket):

  • 拿到 IP 后创建 udp_client_task
  • socket(AF_INET, SOCK_DGRAM, 0) 创建 UDP 套接字;
  • 每 1 秒向 UDP_SERVER_IP:6000 发送 hello from ai-m6x,同时接收服务器回复并打印;
  • 修改文件开头的 UDP_SERVER_IP / UDP_SERVER_PORT 即可切换目标。
  • 同族参考:官方 wifi_udp 例程(UDP 服务器页)演示了服务器/回显端。

操作步骤

1
进入例程目录

SDK 没有独立的 UDP 客户端例程(wifi_udp 只有回显服务器),本页在 wifi_tcp 例程骨架上编写(标注自编示例)。先进入目录:

cd examples/wifi/sta/wifi_tcp
2
替换 main.c

用下面的自编 main.c 覆盖例程目录里的 main.c(记得把文件里的 UDP_SERVER_IP 改成你的服务器 IP):

3
编译工程

统一填写 bl616

make CHIP=bl616 BOARD=bl616dk
4
烧录固件

按住 BOOT 短按 EN/RST 进入下载模式后烧录:

make flash CHIP=bl616 COMX=/dev/ttyUSB0
5
连接 Wi-Fi 并验证

串口助手波特率 2000000,连接路由器后程序自动开始:每 1 秒向 UDP_SERVER_IP:6000 发送一次 hello from ai-m6x,并打印服务器回复。电脑上可先启动一个 UDP 回显服务(如 nc -ul 6000nc -u -l 6000)观察。

wifi_sta_connect Your_SSID 12345678

代码执行流程

UDP 客户端从启动到收发的完整流程如下:

例程调用的 API 介绍

socket(AF_INET, SOCK_DGRAM, 0)

创建 UDP 套接字。

参数

  • AF_INET:IPv4 地址族
  • SOCK_DGRAM:数据报套接字(UDP)
  • 0:协议自动选择

返回值:大于等于 0 的句柄;< 0 失败

sendto(sock, buf, len, 0, to, tolen)

向指定地址发送一个数据报。

参数

  • sock:套接字句柄
  • buf / len:数据与长度
  • to / tolen:目标地址(服务器 IP 与端口)

返回值:实际发送字节数;< 0 出错

recvfrom(sock, buf, len, 0, from, fromlen)

阻塞接收一个数据报并返回发送方地址。示例把它当作“等待服务器回复”。

参数

  • sock:套接字句柄
  • buf / len:接收缓冲区与长度
  • from / fromlen:发送方地址与长度

返回值:实际接收字节数;< 0 出错

完整代码

以下为自编示例 main.c(基于 SDK wifi_tcp 例程骨架编写,非 SDK 自带文件),默认折叠,点击展开:

📜 点击展开 自编 udp_client main.c 完整代码
c
/*
 * 自编示例:UDP 客户端(基于 lwIP socket)
 * 使用方法:
 *   1. 用本文件覆盖 examples/wifi/sta/wifi_tcp/main.c;
 *   2. 把 UDP_SERVER_IP 改成你的服务器 IP;
 *   3. make CHIP=bl616 BOARD=bl616dk && make flash CHIP=bl616 COMX=/dev/ttyUSB0;
 *   4. 串口执行 wifi_sta_connect <SSID> <密码>,程序自动发送/接收。
 */

#include "FreeRTOS.h"
#include "task.h"
#include "timers.h"

#include <string.h>

#include <lwip/tcpip.h>
#include <lwip/sockets.h>
#include <lwip/netdb.h>

#include "wifi_mgmr_ext.h"
#include "fhost_api.h"
#include "wifi_mgmr.h"

#include "bflb_irq.h"
#include "bflb_uart.h"

#include "rfparam_adapter.h"
#include "async_event.h"
#include "mm.h"
#include "board.h"
#include "shell.h"

#define DBG_TAG "MAIN"
#include "log.h"

#define UDP_SERVER_IP   "192.168.1.2"
#define UDP_SERVER_PORT 6000

static volatile uint32_t wifi_state = 0;
static struct bflb_device_s *uart0;

extern void shell_init_with_task(struct bflb_device_s *shell);

void wifi_event_handler(async_input_event_t ev, void *priv)
{
    switch (ev->code) {
        case CODE_WIFI_ON_GOT_IP:
            wifi_state = 1;
            LOG_I("Got IP\r\n");
            break;
        case CODE_WIFI_ON_DISCONNECT:
            wifi_state = 0;
            LOG_I("WiFi disconnected\r\n");
            break;
        default:
            break;
    }
}

static void udp_client_task(void *param)
{
    int sock;
    struct sockaddr_in server;
    char send_buf[] = "hello from ai-m6x";
    char recv_buf[128];
    socklen_t addr_len = sizeof(server);

    /* 等待 Wi-Fi 拿到 IP */
    while (!wifi_state) {
        vTaskDelay(pdMS_TO_TICKS(200));
    }

    sock = socket(AF_INET, SOCK_DGRAM, 0);
    if (sock < 0) {
        LOG_E("create socket failed\r\n");
        vTaskDelete(NULL);
        return;
    }

    memset(&server, 0, sizeof(server));
    server.sin_family = AF_INET;
    server.sin_port = htons(UDP_SERVER_PORT);
    server.sin_addr.s_addr = inet_addr(UDP_SERVER_IP);

    LOG_I("UDP client send to %s:%d\r\n", UDP_SERVER_IP, UDP_SERVER_PORT);

    while (1) {
        int ret = sendto(sock, send_buf, strlen(send_buf), 0,
                         (struct sockaddr *)&server, sizeof(server));
        if (ret < 0) {
            LOG_E("sendto failed\r\n");
        } else {
            LOG_I("send %d bytes\r\n", ret);
        }

        ret = recvfrom(sock, recv_buf, sizeof(recv_buf) - 1, 0,
                       (struct sockaddr *)&server, &addr_len);
        if (ret >= 0) {
            recv_buf[ret] = '\0';
            LOG_I("recv %d bytes: %s\r\n", ret, recv_buf);
        }

        vTaskDelay(pdMS_TO_TICKS(1000));
    }
}

static void wifi_start_firmware_task(void *param)
{
    LOG_I("Starting wifi ...\r\n");
    async_register_event_filter(EV_WIFI, wifi_event_handler, NULL);
    wifi_task_create();
    LOG_I("Starting fhost ...\r\n");
    fhost_init();
    vTaskDelete(NULL);
}

int main(void)
{
    board_init();

    uart0 = bflb_device_get_by_name("uart0");
    shell_init_with_task(uart0);

    if (0 != rfparam_init(0, NULL, 0)) {
        LOG_I("PHY RF init failed!\r\n");
        return 0;
    }

    tcpip_init(NULL, NULL);

    xTaskCreate(wifi_start_firmware_task, "wifi init", 1024, NULL, 10, NULL);
    xTaskCreate(udp_client_task, "udp client", 1024, NULL, 11, NULL);

    vTaskStartScheduler();

    while (1) {
    }
}

FAQ

服务器收不到数据

确认模组已拿到 IP(日志出现 Got IP);UDP_SERVER_IP 必须改成服务器实际 IP 并重新编译烧录;服务器防火墙可能拦截 UDP,先在同网段用 nc -u -l 6000 验证。

recvfrom 一直阻塞、打印不了回复

UDP 不保证送达,服务器没回包时 recvfrom 会一直等。可以给套接字设置接收超时(setsockopt(SO_RCVTIMEO)),或先只发送、把接收注释掉联调。

遇到问题?

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

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