(1)实验平台:
普中Hi3861鸿蒙物联网WIFI套件
https://item.taobao.com/item.htm?id=829136021914(2)资料下载:普中科技-各型号产品资料下载链接
如今物联网市场异常火爆, WIFI 是物联网中非常重要的角色, 现在基本上家家户户都有 WIFI 网络, 通过 WIFI 接入到互联网, 成了智能家居产品普遍的选择。 Hi3861 内部已集成 WIFI 功能, 可以说它就是为 WIFI 无线连接而生的。 本章来学习 WIFI 的 TCP 网络通信, 使用 Hi3861 开发 WIFI 是非常简单而美妙的,让大家在学习物联网中变的简单有趣。 本章分为如下几部分内容:
[29.1 实验介绍](#29.1 实验介绍)
[29.1.1 实验简介](#29.1.1 实验简介)
[29.1.1.1 TCP 协议概述](#29.1.1.1 TCP 协议概述)
[29.1.1.2 TCP 通信实现](#29.1.1.2 TCP 通信实现)
[29.1.2 实验目的](#29.1.2 实验目的)
[29.1.3 WIFI 函数使用](#29.1.3 WIFI 函数使用)
[29.1.3.1 connect 函数](#29.1.3.1 connect 函数)
[29.1.3.2 send 函数](#29.1.3.2 send 函数)
[29.1.3.3 recv 函数](#29.1.3.3 recv 函数)
[29.2 硬件设计](#29.2 硬件设计)
[29.3 软件设计](#29.3 软件设计)
[29.4 实验现象](#29.4 实验现象)
29.1 实验介绍
29.1.1 实验简介
TCP 通信是基于 TCP/IP 协议栈实现的, 主要用于在设备与其他网络设备之间建立可靠的、 面向连接的通信。
29.1.1.1 TCP 协议概述
定义: TCP(Transmission Control Protocol, 传输控制协议) 是一种面向连接的、 可靠的、 基于字节流的传输层通信协议。
特点:
●面向连接: 通信双方必须先建立连接才能进行数据的传输, 类似于打电话需要先拨号建立连接。
●可靠性: TCP 通过校验和、 重传控制、 序号标识、 滑动窗口、 确认应答等机制实现可靠传输, 确保数据无差错、 不丢失、 不重复, 且按序到达。
●字节流服务: TCP 把应用程序交下来的数据仅仅看成是一连串的无结构的字节流。
●全双工通信: TCP 允许通信双方的应用程序在任何时候都能发送数据, TCP连接的两端都设有发送缓存和接收缓存, 用来临时存放双方通信的数据。
29.1.1.2 TCP 通信实现
在 Hi3861 上实现 TCP 通信, 通常需要以下几个步骤:
1、 TCP 服务端实现步骤
●创建 socket: 使用 socket()函数创建一个 socket, 指定使用 IPv4 地址(AF_INET) 和 TCP 协议(SOCK_STREAM) 。
●绑定地址: 使用 bind()函数将 socket 与特定的 IP 地址和端口号绑定起来。
●监听连接: 通过 listen()函数使 socket 进入被动监听状态, 等待客户端的连接请求。
●接受连接: 使用 accept()函数接受客户端的连接请求, 建立连接。
●接收和发送数据: 通过 recv()和 send()函数接收和发送数据。
●关闭连接: 数据传输完成后, 使用 close()函数关闭 socket 连接。
2、 TCP 客户端实现步骤
●创建 socket: 同样使用 socket()函数创建一个 socket。
●配置服务端地址: 创建 sockaddr_in 结构体, 配置服务端的 IP 地址和端口号。
●连接服务端: 使用 connect()函数向服务端发起连接请求。
●发送和接收数据: 连接建立后, 通过 send()和 recv()函数发送和接收数据。
●关闭连接: 数据传输完成后, 使用 close()函数关闭 socket 连接。
29.1.2 实验目的
连接路由器热点, 打开网络调试助手, 选择 TCP 协议连接, 即可与开发板进行 TCP 通信, 开发板作为客户端, 电脑作为服务端。
29.1.3 WIFI 函数使用
29.1.3.1 connect 函数
用于客户端主动连接服务器, 建立 TCP 连接。 函数原型:
cpp
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
参数:
sockfd: 由 socket 函数返回的套接字描述符。 这个描述符标识了一个网络端点, 客户端将使用这个端点来与服务器进行通信。
addr: 指向 sockaddr 结构的指针, 该结构包含了服务器的地址信息, 包括IP 地址和端口号。 在实际使用中, 通常会使用 sockaddr_in( 对于 IPv4) 或sockaddr_in6(对于 IPv6) 结构来填充这个参数。
addrlen: addr 参数所指向的地址结构的长度。 这个长度确保了 connect 函数能够正确地解析地址信息。
返回值:
0: 连接成功。
-1: 连接失败。 此时, 全局变量 errno 会被设置为一个错误码, 以指示连接失败的具体原因。 常见的错误码包括 ECONNREFUSED(连接被拒绝) 、 ETIMEDOUT (连接超时) 等。
29.1.3.2 send 函数
用于 TCP 在已连接的套接字(socket) 上发送数据。 这是 TCP/IP 协议栈中非常核心的一个函数, 用于实现数据的可靠传输。 函数原型:
cpp
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
参数:
sockfd: 已连接套接字的文件描述符或句柄。
buf: 指向包含要发送数据的缓冲区的指针。
len: 要发送的字节数。
flags: 控制函数行为的标志位, 通常设置为 0。
返回值:
成功时, 返回实际发送的字节数。
失败时, 返回-1, 并设置全局变量 errno 以指示错误原因;
29.1.3.3 recv 函数
用于从 TCP 连接的另一端接收数据。 函数原型:
cpp
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
参数:
sockfd: 指定接收数据的套接字描述符(socket descriptor) , 它唯一标识了一个套接字。
buf: 指向一个缓冲区的指针, 该缓冲区用于存放 recv 函数接收到的数据。
len: 指定缓冲区 buf 的长度, 即最多可以接收多少字节的数据。
flags: 调用操作标志位, 用于控制 recv 函数的行为。 在大多数情况下, 这个参数被设置为 0。
返回值: 返回接收到字节数, 如果出现错误返回-1;
当应用程序调用 recv 函数时, 它执行以下操作:
等待数据: 如果套接字的发送缓冲区中没有数据或者数据正在被协议层传送, recv 函数会等待, 直到有数据到达并被协议层接收完毕。
复制数据: 当数据到达并被接收完毕后, recv 函数会从套接字的接收缓冲区中复制数据到用户指定的缓冲区(buf) 中。 注意, 如果接收到的数据量大于缓冲区的长度, 可能需要多次调用 recv 函数来接收所有数据。
返回接收到的字节数: recv 函数返回实际复制到缓冲区中的字节数。 如果接收过程中出现错误, 则返回-1(或 SOCKET_ERROR) , 并设置相应的错误码(通过 errno 或 WSAGetLastError 获取) 。 如果连接被对方正常关闭, 则返回 0。
29.2 硬件设计
由于 Hi3861 内置 WIFI 功能, 所以直接在开发板上使用即可, 无需额外连接。
29.3 软件设计
将前面章节创建好的工程模板, 复制一份, 重命名为 25_wifi_tcp, 如下所示:

(1) 修改 demo 文件夹下的 BUILD.gn 文件, 如下所示:

(2) 添加工程编译文件路径, 如下所示

(3) 修改 template.c 文件, 代码如下:
cpp
/**
****************************************************************************************************
* @file template.c
* @author 普中科技
* @version V1.0
* @date 2024-06-05
* @brief WIFI TCP实验
* @license Copyright (c) 2024-2034, 深圳市普中科技有限公司
****************************************************************************************************
* @attention
*
* 实验平台:普中-Hi3861
* 在线视频:https://space.bilibili.com/2146492485
* 公司网址:www.prechin.cn
* 购买地址:
*
****************************************************************************************************
* 实验现象:开发板连接路由器热点,打开网络调试助手,选择TCP协议连接,即可与开发板进行TCP通信
*
****************************************************************************************************
*/
#include <stdio.h>
#include <unistd.h>
#include "ohos_init.h"
#include "cmsis_os2.h"
#include "bsp_led.h"
#include "bsp_wifi.h"
#include "lwip/netifapi.h"
#include "lwip/sockets.h"
#include "lwip/api_shell.h"
//LED任务
osThreadId_t LED_Task_ID; //led任务ID
void LED_Task(void)
{
led_init();//LED初始化
while (1)
{
LED(1);
usleep(200*1000); //200ms
LED(0);
usleep(200*1000); //200ms
}
}
//LED任务创建
void led_task_create(void)
{
osThreadAttr_t taskOptions;
taskOptions.name = "LEDTask"; // 任务的名字
taskOptions.attr_bits = 0; // 属性位
taskOptions.cb_mem = NULL; // 堆空间地址
taskOptions.cb_size = 0; // 堆空间大小
taskOptions.stack_mem = NULL; // 栈空间地址
taskOptions.stack_size = 1024; // 栈空间大小 单位:字节
taskOptions.priority = osPriorityNormal; // 任务的优先级
LED_Task_ID = osThreadNew((osThreadFunc_t)LED_Task, NULL, &taskOptions); // 创建任务1
if (LED_Task_ID != NULL)
{
printf("ID = %d, Create LED_Task_ID is OK!\n", LED_Task_ID);
}
}
//控制任务
osThreadId_t WIFI_Task_ID; //任务ID
#define WIFI_SSID "普中科技"
#define WIFI_PAWD "88888888.@"
#define TCP_SERVER_IP "192.168.101.16"
#define TCP_SERVER_PORT 8000
void WIFI_Task(void)
{
int socket_fd = 0;
char buff[256];
int re = 0;
// 连接Wifi
WiFi_connectHotspots(WIFI_SSID, WIFI_PAWD);
socket_fd = socket(AF_INET, SOCK_STREAM, 0); // 创建套接字(TCP)
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(TCP_SERVER_PORT);
addr.sin_addr.s_addr = inet_addr(TCP_SERVER_IP); // 填写服务器的IP地址
re = connect(socket_fd, (struct sockaddr *)&addr, sizeof(addr)); // 连接服务器
if (re == -1)
{
printf("Failed to connect to the server\r\n");
return;
}
printf("Connection to server successful\r\n");
// 发送第一条数据
send(socket_fd, "Connection to server successful.", strlen("Connection to server successful."), 0);
while (1)
{
memset_s(buff, sizeof(buff), 0, sizeof(buff));
re = recv(socket_fd, buff, sizeof(buff), 0); // 接收客户端发送过来的消息
if (re <= 0)
{
break;
}
else
{
printf("Receive data received by the server: %s\r\n", buff);
send(socket_fd, buff, sizeof(buff), 0);
}
}
close(socket_fd);
}
//任务创建
void wifi_task_create(void)
{
osThreadAttr_t taskOptions;
taskOptions.name = "wifiTask"; // 任务的名字
taskOptions.attr_bits = 0; // 属性位
taskOptions.cb_mem = NULL; // 堆空间地址
taskOptions.cb_size = 0; // 堆空间大小
taskOptions.stack_mem = NULL; // 栈空间地址
taskOptions.stack_size = 1024*10; // 栈空间大小 单位:字节
taskOptions.priority = osPriorityNormal; // 任务的优先级
WIFI_Task_ID = osThreadNew((osThreadFunc_t)WIFI_Task, NULL, &taskOptions); // 创建任务
if (WIFI_Task_ID != NULL)
{
printf("ID = %d, WIFI_Task_ID Create OK!\n", WIFI_Task_ID);
}
}
/**
* @description: 初始化并创建任务
* @param {*}
* @return {*}
*/
static void template_demo(void)
{
printf("普中-Hi3861开发板--WIFI TCP实验\r\n");
led_task_create();
wifi_task_create();//任务创建
}
SYS_RUN(template_demo);
29.4 实验现象
将程序下载到开发板内(可参考"2.2.5 程序下载运行"章节) , 打开串口调试助手"\5--开发工具\4-串口调试助手\UartAssist.exe" , 波特率设置为115200, 实验现象: 连接路由器热点, 打开网络调试助手, 选择 TCP 协议连接,即可与开发板进行 TCP 通信, 开发板作为客户端, 电脑作为服务端。

