1、实验简介
参考网址:https://gitee.com/Lockzhiner-Electronics/lz3863/tree/master/apps/c3_wifi_tcp_server
1.1、实验目的
本实验旨在帮助学习者掌握 OpenHarmony 轻量系统中WiFi + TCP 服务端的基本使用方法。通过本实验,你将学会:
- 理解TCP 协议的面向连接、可靠传输特性及客户端/服务端通信模型;
- 在开发板上以 STA 模式连接 WiFi 热点,通过 DHCP 获取 IP 地址;
- 使用 lwIPSocket 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(服务端):
structsockaddr_inserver_addr={0};server_addr.sin_family=AF_INET;// IPv4server_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 配置参数
#defineWIFI_SSID"lzdz"#defineWIFI_PASSWORD"88888888"#defineTCP_SERVER_PORT777staticcharsend_data[50]="wifi_tcp_test_date";staticcharrecv_data[100];实验前请根据实际网络环境修改WIFI_SSID和WIFI_PASSWORD,确保与可用热点一致。TCP 监听端口可通过TCP_SERVER_PORT修改。
(2)系统入口 — tcp_server_demo_entry
通过APP_FEATURE_INIT注册应用入口,创建tcp_server_demo_task线程:
staticvoidtcp_server_demo_entry(void){osThreadAttr_tattr={.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 服务端:
staticvoidtcp_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 服务端通信流程:
voidtcp_server_test(unsignedshortport){ssize_tret=0;intbacklog=1;// 1. 创建 TCP 套接字intsockfd=socket(AF_INET,SOCK_STREAM,0);if(sockfd<0){printf("Failed to create socket! errno=%d\r\n",errno);return;}intconnfd=-1;structsockaddr_inclient_addr={0};socklen_tclient_addr_len=sizeof(client_addr);structsockaddr_inserver_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,(structsockaddr*)&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,(structsockaddr*)&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-rfout hbset-root.# 通过上下方向键选择 ws63 对应的编译分支(如 nearlink_dk_3863 / ws63-liteos-app)hb build-f编译成功后,使用开发板配套烧录工具将固件烧写到 LZ3863-星闪开发板。
4.5、修改网络参数(可选)
烧录前,若实际 WiFi 热点与默认值不同,请编辑wifi_tcp_server_example.c中的宏定义:
#defineWIFI_SSID"lzdz"// 改为实际热点名称#defineWIFI_PASSWORD"88888888"// 改为实际热点密码#defineTCP_SERVER_PORT777// 改为实际监听端口修改后需重新编译并烧录。
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)nc192.168.137.x777# 方式 B:使用 Pythonpython3-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 在可靠性、效率上的差异。