1、实验简介

参考网址:https://gitee.com/Lockzhiner-Electronics/lz3863/tree/master/apps/c3_wifi_tcp_server

1.1、实验目的

本实验旨在帮助学习者掌握 OpenHarmony 轻量系统中 WiFi + TCP 服务端 的基本使用方法。通过本实验,你将学会:

  1. 理解 TCP 协议 的面向连接、可靠传输特性及客户端/服务端通信模型;
  2. 在开发板上以 STA 模式连接 WiFi 热点,通过 DHCP 获取 IP 地址;
  3. 使用 lwIP Socket API 创建 TCP 服务端,完成绑定、监听、接受连接与数据收发;
  4. 掌握 TCP 服务端与客户端的完整联调流程;
  5. 完成案例代码的编译、烧录与串口现象观察。

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 封装,实现 ConnectToHotspotStartHotspotDisconnectWithHotspot
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_SSIDWIFI_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 中实现,主要步骤:

  1. 调用 wifi_sta_enable() 启用 STA 模式;
  2. 循环执行 wifi_sta_scan() 扫描,get_match_network() 匹配目标 SSID;
  3. 调用 wifi_sta_connect() 发起关联,等待 WIFI_CONNECTED 状态;
  4. wlan0 接口上启动 DHCP 客户端,获取 IP 地址并打印。

3.4、程序执行流程

TCP 客户端 (PC/开发板) WiFi 热点 开发板 (TCP Server) TCP 客户端 (PC/开发板) WiFi 热点 开发板 (TCP Server) 连接关闭或出错时 closesocket() APP_FEATURE_INIT → tcp_server_demo_entry osThreadNew(tcp_server_demo_task) ConnectToHotspot("lzdz") 关联成功 + DHCP 分配 IP osDelay(800) 等待网络稳定 bind(:777) + listen() connect(开发板IP:777) accept() 三次握手完成 send("wifi_tcp_test_date") 客户端发送数据 recv() 循环打印接收内容

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 客户端(推荐)

  1. 在 PC 上开启移动热点,SSID 设为 lzdz,密码设为 88888888
  2. 开发板上电或复位,烧录本案例固件,通过 USB 连接 PC 打开串口助手(115200,8N1);
  3. 观察串口日志中打印的开发板 IP 地址(如 STA IP 192.168.137.x);
  4. 在 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 案例联调

  1. 准备可连接的 WiFi 路由器或手机热点(SSID/密码与代码一致);
  2. 本开发板烧录 c3_wifi_tcp_server 固件作为 TCP 服务端;
  3. 另一块开发板烧录 c2_wifi_tcp_client 固件作为 TCP 客户端;
  4. 查看服务端串口日志中的 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、实验扩展

完成基本实验后,可尝试以下扩展练习:

  1. 修改通信内容:更改 send_data 字符串,观察客户端接收到的数据变化;
  2. 双向通信:在 recv() 循环中增加 send() 回发逻辑,实现简单的请求-响应交互;
  3. 配合 TCP 客户端案例:使用 c2_wifi_tcp_client 案例,实现两块开发板之间的 TCP 通信;
  4. 多客户端支持:使用 select() 或循环 accept() 支持多个客户端同时连接;
  5. 断线重连:客户端断开后重新进入 accept() 等待新连接,实现服务端持续运行;
  6. 设置 SO_REUSEADDR:在 bind() 前设置地址复用选项,避免端口占用问题;
  7. 改用 UDP 通信:参考后续 UDP 案例,对比 TCP 服务端与 UDP 在可靠性、效率上的差异。
Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐