【普中Hi3861开发攻略--基于鸿蒙OS】-- 第 29 章 WIFI 实验-TCP 通信
(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.1 实验简介
29.1.1.1 TCP 协议概述
29.1.1.2 TCP 通信实现
29.1.2 实验目的
29.1.3 WIFI 函数使用
29.1.3.1 connect 函数
29.1.3.2 send 函数
29.1.3.3 recv 函数
29.2 硬件设计
29.3 软件设计
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 连接。 函数原型:
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 协议栈中非常核心的一个函数, 用于实现数据的可靠传输。 函数原型:
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 连接的另一端接收数据。 函数原型:
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 文件, 代码如下:
/******************************************************************************************************* @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任务IDvoid LED_Task(void)
{led_init();//LED初始化while (1) {LED(1); usleep(200*1000); //200msLED(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); // 创建任务1if (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 8000void WIFI_Task(void)
{int socket_fd = 0;char buff[256];int re = 0;// 连接WifiWiFi_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 通信, 开发板作为客户端, 电脑作为服务端。


