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




所有评论(0)