Skip to content

概念先知道

  • UDP 服务器:UDP 没有“连接”,服务器只需把套接字绑定(bind)到端口,就可以收发任意来源的数据报。
  • 数据报(datagram):UDP 每次收发是一个完整的数据包,边界保留;不像 TCP 是字节流。
  • recvfrom / sendto:UDP 收发函数比 TCP 多两个参数,用来携带对方的地址,这样才知道“谁发来的、回给谁”。
  • 回显(echo):例程把收到的数据原样发回给发送方,用来验证双向收发。

例程功能简介

本页对应博流官方 SDK 的 wifi_udp 例程中的 UDP 回显服务器部分(examples/wifi/sta/wifi_udp/wifi_udp_echo.c):

  • socket(AF_INET, SOCK_DGRAM, 0) 创建 UDP 套接字,bind() 绑定 0.0.0.0:port
  • 循环 recvfrom() 接收数据报,打印来源 IP 与内容,再 sendto() 原样回给发送方;
  • shell 命令 wifi_udp_echo <port> 启动服务器,Ctrl+C 退出并打印收发统计。
  • 同族参考:UDP 客户端与广播/组播没有独立例程,教程提供自编 UDP 客户端UDP 广播UDP 组播示例。

操作步骤

1
进入例程目录

在终端进入 SDK 的 UDP 例程目录(前提:环境已按快速开始(Linux)Windows搭好):

cd examples/wifi/sta/wifi_udp
2
编译工程

统一填写 bl616

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

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

make flash CHIP=bl616 COMX=/dev/ttyUSB0
4
连接 Wi-Fi 并启动 UDP 服务器

串口助手波特率 2000000,看到 bouffalolab /> 后连接路由器,再启动 UDP 回显服务器监听 3365 端口:

wifi_sta_connect Your_SSID 12345678
wifi_udp_echo 3365
5
运行验证

在电脑上用 netcat 的 UDP 模式向模组发包,模组收到后原样回显:

nc -uv 192.168.1.3 3365

代码执行流程

UDP 服务器从启动到回显的完整流程如下:

例程调用的 API 介绍

socket(AF_INET, SOCK_DGRAM, 0)

创建 UDP 套接字。SOCK_DGRAM(数据报)是 UDP 与 TCP 的关键区别。

参数

  • AF_INET:IPv4 地址族
  • SOCK_DGRAM:无连接的数据报套接字
  • 0:协议自动选择

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

bind(sock, addr, len)

把套接字绑定到本地端口(例程 0.0.0.0:3365),之后该端口收到的数据报都会进这个套接字。

参数

  • sock:socket 句柄
  • addrsockaddr_insin_port = htons(port)sin_addr = INADDR_ANY
  • len:地址结构长度

返回值0 成功;-1 失败

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

阻塞接收一个数据报,并把发送方地址写入 from。UDP 没有连接,每次都要从 from 知道对方是谁。

参数

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

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

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

向指定地址发送一个数据报。回显时把 recvfrom 得到的 from 原样传回即可。

参数

  • sock:套接字句柄
  • buf / len:数据与长度
  • to / tolen:目标地址与长度

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

完整代码

以下为 wifi_udp_echo.c 完整源码,与官方示例(examples/wifi/sta/wifi_udp)一致,默认折叠,点击展开:

📜 点击展开 wifi_udp/main.c 完整代码
c
/****************************************************************************
 *
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.  The
 * ASF licenses this file to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance with the
 * License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
 * License for the specific language governing permissions and limitations
 * under the License.
 *
 ****************************************************************************/

/****************************************************************************
 * Included Files
 ****************************************************************************/

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

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

#include "wifi_mgmr_ext.h"

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

#include "rfparam_adapter.h"

#include "board.h"
#include "shell.h"

#ifndef BL602
#include "fhost_api.h"
#include "wifi_mgmr.h"
#endif
#include "async_event.h"
#include "mm.h"

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

struct bflb_device_s *gpio;

/****************************************************************************
 * Pre-processor Definitions
 ****************************************************************************/

#define WIFI_STACK_SIZE  (1536)
#define TASK_PRIORITY_FW (16)

/****************************************************************************
 * Private Types
 ****************************************************************************/

/****************************************************************************
 * Private Data
 ****************************************************************************/

static struct bflb_device_s *uart0;


extern void shell_init_with_task(struct bflb_device_s *shell);
extern void wifi_event_handler(async_input_event_t ev, void *priv);
#ifdef BL602
extern void wifi_task_create(void);
extern int fhost_init(void);
extern int wifi_mgmr_task_start(void);
#endif

/****************************************************************************
 * Private Function Prototypes
 ****************************************************************************/

/****************************************************************************
 * Functions
 ****************************************************************************/

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);
}

void wifi_event_handler(async_input_event_t ev, void *priv)
{
    uint32_t code = ev->code;

    switch (code) {
        case CODE_WIFI_ON_INIT_DONE: {
            LOG_I("[APP] [EVT] %s, CODE_WIFI_ON_INIT_DONE\r\n", __func__);
            wifi_mgmr_task_start();
        } break;
        case CODE_WIFI_ON_MGMR_DONE: {
            LOG_I("[APP] [EVT] %s, CODE_WIFI_ON_MGMR_DONE\r\n", __func__);
        } break;
        case CODE_WIFI_ON_SCAN_DONE: {
            LOG_I("[APP] [EVT] %s, CODE_WIFI_ON_SCAN_DONE\r\n", __func__);
            wifi_mgmr_sta_scanlist();
        } break;
        case CODE_WIFI_ON_CONNECTED: {
            LOG_I("[APP] [EVT] %s, CODE_WIFI_ON_CONNECTED\r\n", __func__);
            void mm_sec_keydump();
            mm_sec_keydump();
        } break;
        case CODE_WIFI_ON_GOT_IP: {
            LOG_I("[APP] [EVT] %s, CODE_WIFI_ON_GOT_IP\r\n", __func__);
            LOG_I("[SYS] Memory left is %d Bytes\r\n", kfree_size(0));
        } break;
        case CODE_WIFI_ON_DISCONNECT: {
            LOG_I("[APP] [EVT] %s, CODE_WIFI_ON_DISCONNECT\r\n", __func__);
        } break;
        case CODE_WIFI_ON_AP_STARTED: {
            LOG_I("[APP] [EVT] %s, CODE_WIFI_ON_AP_STARTED\r\n", __func__);
        } break;
        case CODE_WIFI_ON_AP_STOPPED: {
            LOG_I("[APP] [EVT] %s, CODE_WIFI_ON_AP_STOPPED\r\n", __func__);
        } break;
        case CODE_WIFI_ON_AP_STA_ADD: {
            LOG_I("[APP] [EVT] [AP] [ADD] %lld\r\n", xTaskGetTickCount());
        } break;
        case CODE_WIFI_ON_AP_STA_DEL: {
            LOG_I("[APP] [EVT] [AP] [DEL] %lld\r\n", xTaskGetTickCount());
        } break;
        default: {
            LOG_I("[APP] [EVT] Unknown code %u \r\n", code);
        }
    }
}

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;
    }

    LOG_I("PHY RF init success!\r\n");

    tcpip_init(NULL, NULL);
    xTaskCreate(wifi_start_firmware_task, "wifi init", 1024, NULL, 10, NULL);

    vTaskStartScheduler();

    while (1) {
    }
}
📜 点击展开 wifi_udp_echo.c 完整代码
c

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/socket.h>
#include <lwip/api.h>
#include <lwip/arch.h>
#include <lwip/opt.h>
#include <lwip/inet.h>
#include <lwip/errno.h>
#include <netdb.h>

#include "shell.h"
#include "utils_getopt.h"
#include "bflb_mtimer.h"

// clang-format off
uint32_t recv_buf[2 * 1024] = {0};
// clang-format on

shell_sig_func_ptr abort_exec;
uint64_t recv_len = 0;
int sock = -1;

static void test_close(int sig)
{
    if (sock) {
        closesocket(sock);
    }
    abort_exec(sig);
    if (recv_len > 0) {
        printf("Total send data=%lld\r\n", recv_len);
    }
}

#define PING_USAGE             \
    "wifi_udp_echo [port]\r\n" \
    "\t port: local listen port, default port 5001\r\n"

static void wifi_test_udp_echo_init(int argc, char **argv)
{
    abort_exec = shell_signal(SHELL_SIGINT, test_close);
    printf("udp server task start ...\r\n");

    char *port;
    struct sockaddr_in udp_addr, remote_addr;
    socklen_t addr_len;

    if (argc < 2) {
        printf("%s", PING_USAGE);
        return;
    }

    /* get port number (argv[1] if present) */
    if (argc > 1) {
        port = argv[1];
    } else {
        port = "5001";
    }

    memset(&recv_buf[0], 0, sizeof(recv_buf));

    while (1) {
        if ((sock = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
            printf("udp create socket error\r\n");
            return;
        }
        udp_addr.sin_family = AF_INET;
        udp_addr.sin_port = htons(atoi(port));
        udp_addr.sin_addr.s_addr = INADDR_ANY;
        memset(&(udp_addr.sin_zero), 0, sizeof(udp_addr.sin_zero));

        printf("Server ip Address : %s:%s\r\n", udp_addr.sin_addr.s_addr, port);

        if (bind(sock, (struct sockaddr *)&udp_addr, sizeof(struct sockaddr)) != 0) {
            printf("udp bind falied!\r\n");
            closesocket(sock);
            return;
        }

        printf("udp bind port success!\r\n");
        printf("Press CTRL-C to exit.\r\n");
        recv_len = 0;
        while (1) {
            recv_len = recvfrom(sock, recv_buf, 1024, 0, (struct sockaddr *)&remote_addr, &addr_len);
            printf("recv from %s\r\n", inet_ntoa(remote_addr.sin_addr));
            printf("recv:%s \r\n", recv_buf);
            sendto(sock, recv_buf, recv_len, 0, (struct sockaddr *)&remote_addr, addr_len);
        }
        closesocket(sock);
        return;
    }
}

#ifdef CONFIG_SHELL
#include <shell.h>

int cmd_wifi_udp_echo(int argc, char **argv)
{
    wifi_test_udp_echo_init(argc, argv);

    return 0;
}

SHELL_CMD_EXPORT_ALIAS(cmd_wifi_udp_echo, wifi_udp_echo, wifi udp test);
#endif

FAQ

nc -uv 连不上 / 收不到回显

确认模组与电脑在同一网段,且先执行了 wifi_sta_connect 拿到 IP;UDP 无连接,nc 的“Connection succeeded”仅表示本端 socket 就绪;回显数据要等模组收到后再看,可先在电脑上多发包(如 echo hi | nc -uv 192.168.1.3 3365)。

收不到自己发的广播

本页是单播回显;向全网段广播或组播发包请分别参考 UDP 广播UDP 组播,广播包需要 SO_BROADCAST 选项。

遇到问题?

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

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