基于小智派-LZ3863星闪开发板:OpenHarmony WiFi 网络开发 — UDP 服务端
1、实验简介
参考网址:https://gitee.com/Lockzhiner-Electronics/lz3863/tree/master/apps/c5_wifi_udp_server
1.1、实验目的
本实验旨在帮助学习者掌握 OpenHarmony 轻量系统中 WiFi + UDP 服务端 的基本使用方法。通过本实验,你将学会:
- 理解 UDP 协议 的无连接、报文传输特性及客户端/服务端通信模型;
- 在开发板上以 STA 模式连接 WiFi 热点,通过 DHCP 获取 IP 地址;
- 使用 lwIP Socket API 创建 UDP 服务端,完成绑定、接收数据报与回复;
- 掌握 UDP 服务端与客户端的完整联调流程;
- 完成案例代码的编译、烧录与串口现象观察。
1.2、实验内容
本案例在 LZ3863-星闪开发板上实现 UDP 服务端 功能:先连接指定 WiFi 热点并获取 IP,再在本机端口上创建 UDP 服务、等待客户端数据报,收到数据后打印客户端信息并回复测试数据,全过程通过串口打印日志。
| 项目 | 说明 |
|---|---|
| 源文件 | wifi_udp_server_example.c(主程序)、wifi_connecter.c(WiFi 封装) |
| WiFi 模式 | STA(站点/客户端) |
| 目标 SSID | lzdz |
| 目标密码 | 88888888 |
| UDP 监听端口 | 777 |
| 绑定地址 | INADDR_ANY(接受任意 IP 的客户端数据报) |
| 回复测试数据 | test_data |
| 任务线程 | udp_server_demo_task(栈大小 8192 字节) |
| 初始化入口 | APP_FEATURE_INIT(udp_server_demo_entry) |
典型联调拓扑:
┌─────────────────┐ WiFi ┌─────────────────┐
│ PC / 手机热点 │ ◄──────────────────► │ LZ3863 开发板 │
│ SSID: lzdz │ │ (UDP Server) │
│ │ UDP :777 │ IP: 192.168. │
│ (UDP Client) │ ◄──────────────────► │ 137.x │
└─────────────────┘ └─────────────────┘
说明:开发板作为 UDP 服务端,需先通过串口日志确认其 DHCP 获取到的 IP 地址,客户端(PC 网络调试工具、
nc命令或c4_wifi_udp_client案例)应向该 IP 及端口777发送 UDP 数据报。
1.3、实验环境
| 项目 | 说明 |
|---|---|
| 硬件 | LZ3863-星闪开发板、USB 数据线 |
| 软件 | OpenHarmony v5.1.0 源码、hb 编译工具 |
| 网络环境 | 可连接的 WiFi 热点(SSID/密码与代码一致) |
| UDP 客户端 | PC 端网络调试工具 / nc / Python 脚本,或 c4_wifi_udp_client 案例 |
| 调试工具 | 串口助手(波特率 115200,8N1) |
| 案例路径 | applications/sample/wifi-iot/app/c5_wifi_udp_server/ |
2、基础知识
2.1、UDP 协议概述
UDP(User Datagram Protocol,用户数据报协议) 是一种无连接的、面向报文的传输层协议,具有以下特点:
| 特性 | 说明 |
|---|---|
| 无连接 | 通信前无需建立连接,发送方直接发送数据报 |
| 不可靠传输 | 不保证数据到达、顺序和完整性,无重传机制 |
| 低开销 | 协议头仅 8 字节,无握手/挥手过程,延迟低 |
| 面向报文 | 每次收发以完整数据报为单位,保留报文边界 |
UDP 通信同样采用 客户端/服务端(C/S) 模型,但与 TCP 不同,双方无需先建立连接:
| 角色 | 职责 | 本实验对应 |
|---|---|---|
| UDP 服务端 | 绑定端口、接收数据报、向客户端地址回复 | 本案例开发板 |
| UDP 客户端 | 指定目标 IP/端口、发送/接收数据报 | PC 或另一块开发板 |
2.2、UDP 与 TCP 对比
| 对比项 | TCP(c3 案例) | UDP(本案例) |
|---|---|---|
| 连接方式 | 面向连接,需三次握手 | 无连接,直接发送 |
| 可靠性 | 可靠,有确认与重传 | 不可靠,尽力交付 |
| 套接字类型 | SOCK_STREAM |
SOCK_DGRAM |
| 服务端流程 | bind() + listen() + accept() |
bind() + recvfrom() |
| 发送 API | send() |
sendto() |
| 接收 API | recv() |
recvfrom() |
| 适用场景 | 文件传输、HTTP 等需可靠传输 | 实时音视频、传感器上报、广播等 |
2.3、Socket 服务端编程基础
Socket(套接字)是网络编程的抽象接口,lwIP 提供了与 BSD Socket 兼容的 API。UDP 服务端的典型流程:
socket(SOCK_DGRAM) → 创建 UDP 套接字
↓
配置 sockaddr_in(INADDR_ANY + 端口)
↓
bind() → 绑定本地 IP 和端口
↓
recvfrom() → 阻塞等待客户端数据报,同时获取发送方地址
↓
sendto() → 向客户端地址回复数据报
↓
closesocket() → 关闭套接字
与 TCP 服务端的关键区别:
| 步骤 | TCP 服务端(c3 案例) | UDP 服务端(本实验) |
|---|---|---|
| 地址配置 | INADDR_ANY + 监听端口 |
INADDR_ANY + 监听端口 |
| 等待连接 | listen() + accept() 被动等待连接 |
无需 listen/accept,直接 recvfrom() 等待数据报 |
| 通信对象 | 监听套接字 + 连接套接字 | 单一套接字,通过 recvfrom() 获取客户端地址 |
| 回复方式 | send(connfd, ...) |
sendto(sockfd, ..., client_addr) |
关键数据结构 sockaddr_in(服务端):
struct sockaddr_in server_addr = {0};
server_addr.sin_family = AF_INET; // IPv4
server_addr.sin_port = htons(port); // 端口号(转网络字节序)
server_addr.sin_addr.s_addr = htonl(INADDR_ANY); // 绑定所有网卡,接受任意 IP 接入
字节序转换:
| 函数 | 作用 |
|---|---|
htons() |
主机字节序 → 网络字节序(16 位,用于端口) |
htonl() |
主机字节序 → 网络字节序(32 位,用于 IP) |
inet_ntoa() |
二进制网络地址 → 字符串 IP(用于打印客户端地址) |
ntohs() |
网络字节序 → 主机字节序(16 位,用于端口) |
2.4、WiFi STA 连接与网络层
本案例在启动 UDP 服务之前,需先通过 ConnectToHotspot() 完成 WiFi 连接与 IP 获取。完整流程为:
启用 STA 模式 → 扫描热点 → 匹配 SSID → 关联连接
→ 启动 DHCP 客户端 → 获取 IP 地址 → 网络层就绪
获取 IP 后,开发板与 UDP 客户端处于同一局域网,客户端可通过该 IP 地址和监听端口发送数据报。
2.5、软件调用层次
本案例的软件调用层次如下:
应用层(wifi_udp_server_example.c)
├── udp_server_demo_entry() ← APP_FEATURE_INIT 注册入口
├── udp_server_demo_task() ← 任务线程:WiFi 连接 + UDP 服务端
└── udp_server_test() ← UDP 核心逻辑
│
WiFi 封装层(wifi_connecter.c)
└── ConnectToHotspot() ← 扫描、连接、DHCP 获取 IP
│
协议栈 / 驱动层
├── lwIP Socket API(socket/bind/recvfrom/sendto)
├── lwIP 网络协议栈(UDP/IP、DHCP)
└── WiFi 驱动(HMAC/DMAC)
2.6、核心 API 介绍
2.6.1、头文件
#include "cmsis_os2.h"
#include "lwip/sockets.h"
#include "ohos_init.h"
#include "wifi_connecter.h"
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
2.6.2、Socket API(UDP 服务端)
| API 名称 | 功能说明 |
|---|---|
socket(AF_INET, SOCK_DGRAM, 0) |
创建 IPv4 UDP 套接字,返回套接字描述符 |
bind(sockfd, addr, addrlen) |
将套接字绑定到指定的 IP 地址和端口 |
recvfrom(sockfd, buf, len, flags, from, fromlen) |
接收 UDP 数据报,同时获取发送方(客户端)地址信息 |
sendto(sockfd, buf, len, flags, to, tolen) |
基于获取的客户端地址,发送回复数据报 |
closesocket(sockfd) |
关闭套接字,释放资源 |
inet_ntoa(in_addr) |
将网络字节序 IP 地址转换为字符串 |
2.6.3、WiFi 与应用层 API
| API 名称 | 功能说明 |
|---|---|
ConnectToHotspot(ssid, password) |
扫描并连接指定热点,完成 DHCP 获取 IP |
DisconnectWithHotspot() |
停止 DHCP 并断开 WiFi 连接 |
osThreadNew(func, arg, &attr) |
创建 RTOS 线程 |
osDelay(ticks) |
线程延时,100 ticks ≈ 1 秒(tick = 10 ms) |
APP_FEATURE_INIT(func) |
注册应用特性初始化入口,系统启动后自动执行 |
3、程序设计
3.1、程序架构
本案例目录结构:
c5_wifi_udp_server/
├── wifi_udp_server_example.c # UDP 服务端主程序
├── wifi_connecter.c # WiFi 连接封装实现
├── wifi_connecter.h # 封装接口头文件
├── BUILD.gn # GN 编译配置
├── README_zh.md # 案例简要说明
└── 实验手册.md # 本实验手册
程序执行流程:
系统启动
│
▼
udp_server_demo_entry() ← APP_FEATURE_INIT 注册,自动执行
│
▼
osThreadNew(udp_server_demo_task) ← 创建 UDP 服务端任务线程
│
▼
udp_server_demo_task()
├── ConnectToHotspot() ← 连接 WiFi 热点,DHCP 获取 IP
├── osDelay(800) ← 等待网络稳定(约 8 秒)
└── udp_server_test() ← 启动 UDP 服务端
├── socket() ← 创建 UDP 套接字
├── bind() ← 绑定端口 777
├── recvfrom() 循环 ← 接收客户端数据报,获取客户端地址
├── sendto() ← 向客户端地址回复 test_data
└── closesocket() ← 关闭套接字
3.2、源文件说明
| 文件 | 说明 |
|---|---|
wifi_udp_server_example.c |
UDP 服务端主程序,包含 WiFi 连接、UDP 服务及任务线程 |
wifi_connecter.c |
WiFi 封装,实现 ConnectToHotspot、StartHotspot、DisconnectWithHotspot |
wifi_connecter.h |
封装接口声明 |
BUILD.gn |
编译配置,生成 wifi_udp_server_example 静态库 |
3.3、关键代码分析
(1)WiFi 与 UDP 配置参数
#define WIFI_SSID "lzdz"
#define WIFI_PASSWORD "88888888"
#define UDP_SERVER_PORT 777
static char send_data[] = "test_data";
static char recv_data[100];
实验前请根据实际网络环境修改 WIFI_SSID 和 WIFI_PASSWORD,确保与可用热点一致。UDP 监听端口可通过 UDP_SERVER_PORT 修改。
(2)系统入口 — udp_server_demo_entry
通过 APP_FEATURE_INIT 注册应用入口,创建 udp_server_demo_task 线程:
static void udp_server_demo_entry(void)
{
osThreadAttr_t attr = {
.name = "udp_server_demo_task",
.stack_size = 8192,
.priority = osPriorityNormal
};
if (osThreadNew(udp_server_demo_task, NULL, &attr) == NULL)
{
printf("[udp_server_demo_entry] Failed to create udp_server_demo_task!\r\n");
}
}
APP_FEATURE_INIT(udp_server_demo_entry);
(3)任务线程 — udp_server_demo_task
任务线程先完成 WiFi 连接,延时等待网络稳定后再启动 UDP 服务端:
static void udp_server_demo_task(void *arg)
{
(void)arg;
printf("Starting Wi-Fi connection to SSID: %s...\r\n", WIFI_SSID);
if (ConnectToHotspot(WIFI_SSID, WIFI_PASSWORD) != 0)
{
printf("Failed to connect to AP.\r\n");
return;
}
printf("Wi-Fi connected successfully.\r\n");
osDelay(800); // 等待约 8 秒,确保网络栈稳定
udp_server_test(UDP_SERVER_PORT);
}
(4)UDP 服务端核心 — udp_server_test
udp_server_test() 实现完整的 UDP 服务端通信流程:
void udp_server_test(unsigned short port)
{
ssize_t ret = 0;
// 1. 创建 UDP 套接字
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd < 0)
{
printf("Failed to create UDP socket! errno=%d\r\n", errno);
return;
}
// 2. 配置服务端地址(绑定所有网卡)
struct sockaddr_in server_addr = {0};
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(port);
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
// 3. 绑定端口
ret = bind(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr));
if (ret < 0)
{
printf("Bind to port %d failed! errno=%d\r\n", port, errno);
closesocket(sockfd);
return;
}
printf("Bind to port %d success! Waiting for client data...\r\n", port);
// 4. 循环接收客户端数据并回复
struct sockaddr_in client_addr = {0};
socklen_t client_addr_len = sizeof(client_addr);
while (running)
{
memset(recv_data, 0, sizeof(recv_data));
ret = recvfrom(sockfd, recv_data, sizeof(recv_data) - 1, 0,
(struct sockaddr *)&client_addr, &client_addr_len);
if (ret <= 0)
{
printf("Recv data failed or connection closed! ret=%ld, errno=%d\r\n", ret, errno);
continue;
}
recv_data[ret] = '\0';
// 打印接收日志和客户端信息
printf("Received data{%s} %ld bytes from client! Peer info: IP=%s, Port=%d\r\n",
recv_data, ret, inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
// 向客户端回复数据
ret = sendto(sockfd, send_data, strlen(send_data), 0,
(struct sockaddr *)&client_addr, client_addr_len);
if (ret < 0)
{
printf("Reply to client failed! errno=%d\r\n", errno);
}
else
{
printf("Sent reply data{%s} %ld bytes to client!\r\n", send_data, ret);
}
osDelay(100); // 防止忙等待
}
// 5. 关闭套接字
closesocket(sockfd);
}
设计要点:
INADDR_ANY表示绑定到所有可用网卡,客户端可通过开发板的任意 IP 地址发送数据报;- UDP 无需
listen()和accept(),绑定端口后直接进入recvfrom()等待数据; recvfrom()返回时,client_addr中保存了发送方的 IP 和端口,可直接用于sendto()回复;- 服务端采用被动响应模式:收到客户端数据后才回复,不会主动发送首包;
running全局变量控制循环,设为 0 可退出服务循环。
(5)WiFi 封装层 — ConnectToHotspot 概要
ConnectToHotspot() 在 wifi_connecter.c 中实现,主要步骤:
- 调用
wifi_sta_enable()启用 STA 模式; - 循环执行
wifi_sta_scan()扫描,get_match_network()匹配目标 SSID; - 调用
wifi_sta_connect()发起关联,等待WIFI_CONNECTED状态; - 在
wlan0接口上启动 DHCP 客户端,获取 IP 地址并打印。
3.4、程序执行流程
4、编译步骤
以下步骤只需在首次编译时完成 4.1~4.3 的配置注册。
4.1、确认案例目录
确认案例已位于 OpenHarmony 源码目录下:
applications/sample/wifi-iot/app/c5_wifi_udp_server/
├── wifi_udp_server_example.c
├── wifi_connecter.c
├── wifi_connecter.h
├── BUILD.gn
└── 实验手册.md
若从外部复制,请将 c5_wifi_udp_server 目录放到上述 app/ 路径下。
4.2、修改 BUILD.gn(注册编译组件)
编辑 applications/sample/wifi-iot/app/BUILD.gn,在 features 列表中添加本案例:
lite_component("app") {
features = [
"startup",
"c5_wifi_udp_server:wifi_udp_server_example", // 添加此行
]
}
4.3、修改 SDK 配置文件
步骤 1:编辑 device/soc/hisilicon/ws63v100/sdk/build/config/target_config/ws63/config.py
找到 'ws63-liteos-app' 配置段,在其 'ram_component' 列表中添加:
"wifi_udp_server_example"
步骤 2:编辑 device/soc/hisilicon/ws63v100/sdk/libs_url/ws63/cmake/ohos.cmake
找到 "ws63-liteos-app" 对应的 set(COMPONENT_LIST 部分,添加:
"wifi_udp_server_example"
4.4、编译固件
在 OpenHarmony 源码根目录下执行编译:
rm -rf out
hb set -root .
# 通过上下方向键选择 ws63 对应的编译分支(如 nearlink_dk_3863 / ws63-liteos-app)
hb build -f
编译成功后,使用开发板配套烧录工具将固件烧写到 LZ3863-星闪开发板。
4.5、修改网络参数(可选)
烧录前,若实际 WiFi 热点与默认值不同,请编辑 wifi_udp_server_example.c 中的宏定义:
#define WIFI_SSID "lzdz" // 改为实际热点名称
#define WIFI_PASSWORD "88888888" // 改为实际热点密码
#define UDP_SERVER_PORT 777 // 改为实际监听端口
修改后需重新编译并烧录。
5、运行结果
5.1、硬件与网络准备
方式一:PC 移动热点 + PC 端 UDP 客户端(推荐)
-
在 PC 上开启移动热点,SSID 设为
lzdz,密码设为88888888; -
开发板上电或复位,烧录本案例固件,通过 USB 连接 PC 打开串口助手(115200,8N1);
-
观察串口日志中打印的开发板 IP 地址(如
STA IP 192.168.137.x); -
在 PC 上使用 UDP 客户端向该 IP 及端口
777发送数据:# 方式 A:使用 netcat(Linux / macOS) echo "Hello UDP" | nc -u 192.168.137.x 777 # 方式 B:使用 Python python3 -c " import socket s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) s.sendto(b'Hello UDP', ('192.168.137.x', 777)) data, addr = s.recvfrom(1024) print(f'Received from {addr}: {data.decode()}') s.close() "将
192.168.137.x替换为串口日志中实际打印的开发板 IP。
方式二:使用 c4_wifi_udp_client 案例联调
- 准备可连接的 WiFi 路由器或手机热点(SSID/密码与代码一致);
- 本开发板烧录
c5_wifi_udp_server固件作为 UDP 服务端; - 另一块开发板烧录
c4_wifi_udp_client固件作为 UDP 客户端; - 查看服务端串口日志中的 IP 地址,将其填入客户端案例的
UDP_SERVER_IP宏定义后重新编译烧录客户端固件。
建议:先确认开发板 WiFi 连接成功并打印 IP 地址,再启动 UDP 客户端发送数据,以提高首次联调成功率。
5.2、串口配置
| 参数 | 值 |
|---|---|
| 波特率 | 115200 |
| 数据位 | 8 |
| 停止位 | 1 |
| 校验位 | 无 |
| 流控 | 无 |
5.3、预期串口输出
烧录固件并复位后,串口助手可观察到完整的 WiFi 连接与 UDP 服务过程:
Starting Wi-Fi connection to SSID: lzdz...
Start Scan !
[WIFI_STA_SAMPLE] Scan done!.
STA try connect.
[WIFI_STA_SAMPLE] Connect succ!.
STA DHCP start.
STA DHCP bound success.
STA IP 192.168.137.x
Connect success.
Wi-Fi connected successfully.
Bind to port 777 success! Waiting for client data...
Received data{Hello UDP} 9 bytes from client! Peer info: IP=192.168.137.1, Port=xxxxx
Sent reply data{test_data} 9 bytes to client!
其中:
Start Scan !/Scan done!表示 WiFi 热点扫描完成;Connect succ!/STA IP 192.168.137.x表示 WiFi 关联成功并获取 IP;Wi-Fi connected successfully.表示应用层确认 WiFi 就绪;Bind to port 777 success!表示 UDP 端口绑定成功,进入等待状态;Received data{...} from client!表示收到客户端发送的 UDP 数据报;Peer info: IP=..., Port=...表示正确解析了客户端地址信息;Sent reply data{test_data}表示向客户端回复成功。
5.4、PC 端 UDP 客户端预期现象
若使用 Python 脚本作为客户端,PC 端可观察到:
Received from ('192.168.137.x', 777): test_data
表示开发板 UDP 服务端已成功接收数据并回复 test_data。
5.5、结果分析
| 现象 | 说明 |
|---|---|
输出 Wi-Fi connected successfully. |
WiFi 连接与 DHCP 获取 IP 成功 |
输出 Bind to port 777 success! |
UDP 端口绑定成功 |
输出 Waiting for client data... |
UDP 服务已进入等待接收状态 |
输出 Received data{...} from client! |
客户端数据报接收成功 |
输出 Peer info: IP=..., Port=... |
正确解析了客户端地址信息 |
输出 Sent reply data{test_data} |
回复数据发送成功 |
输出 Failed to connect to AP. |
WiFi 连接失败,检查 SSID/密码 |
输出 Bind to port 777 failed! |
端口被占用或权限不足 |
输出 Recv data failed |
接收异常,检查网络状态 |
输出 Reply to client failed! |
回复发送失败,检查客户端地址或网络 |
5.6、常见问题排查
| 问题 | 可能原因 | 解决方法 |
|---|---|---|
反复 Can not find AP |
热点未开启或 SSID/密码不匹配 | 确认热点已开启,宏定义与实际一致 |
| WiFi 成功但客户端无响应 | 使用了错误的 IP 地址 | 以串口打印的 STA IP 为准,勿使用网关 IP |
| 客户端发送后服务端无日志 | 服务端尚未完成 bind 或端口错误 | 等待 Bind to port 777 success! 后再发送 |
Bind to port 777 failed! |
端口已被其他程序占用 | 更换 UDP_SERVER_PORT 或重启开发板 |
| PC 端发送成功但收不到回复 | 防火墙拦截 UDP 回复 | 确认 PC 与开发板连接同一热点,关闭防火墙 |
| 编译报错找不到组件 | BUILD.gn 或 config.py 未修改 | 逐步核对 4.2、4.3 节的配置项 |
| 与 c4 联调无响应 | 客户端 UDP_SERVER_IP 配置错误 |
以服务端串口打印的 STA IP 为准填入客户端 |
6、实验扩展
完成基本实验后,可尝试以下扩展练习:
- 修改回复内容:更改
send_data字符串,观察客户端接收到的数据变化; - 自定义响应逻辑:根据接收到的不同数据内容,回复不同的响应报文;
- 配合 UDP 客户端案例:使用
c4_wifi_udp_client案例,实现两块开发板之间的 UDP 通信; - 对比 TCP 与 UDP 服务端:分别运行
c3_wifi_tcp_server与本案例,对比 bind/listen/accept 与 bind/recvfrom 的差异; - 多客户端支持:利用
recvfrom()每次获取不同客户端地址的特性,实现一对多通信; - 设置接收超时:在
recvfrom()前通过setsockopt设置SO_RCVTIMEO,避免无限阻塞; - UDP 广播服务:绑定广播地址或发送广播回复,探索 UDP 广播通信特性。
更多推荐


所有评论(0)