#盲盒+码#【FFH】学习设备开发之Hi3861-TCPclient-开关灯 原创 精华

Z·y
发布于 2022-11-23 17:05
浏览
7收藏

@toc

【本文正在参加「盲盒」+码有奖征文活动】:https://ost.51cto.com/posts/19288

一、前言

学习OpenHarmony南向设备开发中的网络通信,它可以将底层开发板获得的数据传输到上层的服务器,服务器亦可通过网络通信控制底层开发板。

二、TCP简介

传输控制协议(TCP,Transmission Control Protocol)是一种面向连接的、可靠的、基于字节流的传输层通信协议,由IETF的RFC 79 定义。
TCP旨在适应支持多网络应用的分层协议层次结构。 连接到不同但互连的计算机通信网络的主计算机中的成对进程之间依靠TCP提供可靠的通信服务。TCP假设它可以从较低级别的协议获得简单的,可能不可靠的数据报服务。 原则上,TCP应该能够在从硬线连接到分组交换或电路交换网络的各种通信系统之上操作。

网络编程开发绕不开socket(套接字)的使用,socket就是整合好TCP/IP协议的一个工具。让我们无需过度关注于底层协议的实现,直接用封装好的socket就行了
::: hljs-center

TCP服务器端与TCP客户端进行通信的流程👇

:::

#盲盒+码#【FFH】学习设备开发之Hi3861-TCPclient-开关灯-鸿蒙开发者社区

三、分析代码

本次实验使用的是OpenHarmony1.0.0的源码:源码压缩包地址
参考HiSpark WiFi-IoT OpenHarmony套件样例开发–网络编程(tcpclient)

1.导入样例

将润和提供的21_tcpclient开发样例文件夹复制到源码applications/sample/wifi-iot/app路径下
#盲盒+码#【FFH】学习设备开发之Hi3861-TCPclient-开关灯-鸿蒙开发者社区
在app路径下的BUILD.gn添加需要编译的静态库名称:==tcpclient:net_demo==


import("//build/lite/config/component/lite_component.gni")

lite_component("app") {
    features = [
        "startup",
        "tcpclient:net_demo",
    ]
}

静态库名称可在21_tcpclient文件夹下的BUILD.gn里查看
#盲盒+码#【FFH】学习设备开发之Hi3861-TCPclient-开关灯-鸿蒙开发者社区
::: hljs-center

==!!!踩坑:一开始直接写静态库名net_demo是会报错的!!!==

:::

#盲盒+码#【FFH】学习设备开发之Hi3861-TCPclient-开关灯-鸿蒙开发者社区
报错内容👇一般都是BUILD.gn文件出现问题:
#盲盒+码#【FFH】学习设备开发之Hi3861-TCPclient-开关灯-鸿蒙开发者社区

2.分析代码

  • demo_entry_cmsis.c : OpenHarmonyliteos-m程序入口,支持Hi3861
  • demo_entry_posix.c :OpenHarmonyliteos-a和Unix系统程序入口,Hi3516、Hi3518、PC
  • net_common.h :系统网络接口头文件
  • net_demo.h :demo脚手架头文件
  • net_params.h :网络参数,包括WiFi热点信息,服务器IP、端口信息
  • tcp_client_test.c :TCP客户端
  • wifi_connecter.c :OpenHarmonyWiFi STA模式API的封装实现文件,比OpenHarmony原始接口更容易使用
  • wifi_connecter.h :OpenHarmonyWiFi STA模式API的封装头文件,比OpenHarmony原始接口更容易使用

事先在net_params.h文件里修改WiFi的配置
#盲盒+码#【FFH】学习设备开发之Hi3861-TCPclient-开关灯-鸿蒙开发者社区

程序入口:demo_entry_cmsis.c文件👇

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include "iot_gpio.h"

#include "ohos_init.h"
#include "cmsis_os2.h"

#include "net_demo.h"
#include "net_params.h"
#include "wifi_connecter.h"

#define LED_TASK_GPIO 9
static void NetDemoTask(void *arg) //一开始线程入口函数
{
    (void)arg;
    WifiDeviceConfig config = {0};                  //表示用于连接到指定 Wi-Fi 设备的 Wi-Fi 站配置。
    IoTGpioInit(LED_TASK_GPIO);                     //初始化IO口,为后文点灯做准备
    IoTGpioSetDir(LED_TASK_GPIO, IOT_GPIO_DIR_OUT); //设置GPIO为输出模式
    // 准备AP的配置参数
    strcpy(config.ssid, PARAM_HOTSPOT_SSID); //从net_params.h拷贝WiFi的参数
    strcpy(config.preSharedKey, PARAM_HOTSPOT_PSK);
    config.securityType = PARAM_HOTSPOT_TYPE; //配置WiFi的安全模式

    osDelay(10);

    int netId = ConnectToHotspot(&config); //连接热点

    int timeout = 10;
    while (timeout--) //等待10秒后开始执行NetDemoTest
    {
        printf("After %d seconds, I will start %s test!\r\n", timeout, GetNetDemoName());
        osDelay(100);
    }

    while (1)
    {
        NetDemoTest(PARAM_SERVER_PORT, PARAM_SERVER_ADDR); //开始TCP连接,输入端口号,ip地址
    }

    printf("disconnect to AP ...\r\n");
    // DisconnectWithHotspot(netId);
    printf("disconnect to AP done!\r\n");
}

static void NetDemoEntry(void)
{
    osThreadAttr_t attr;

    attr.name = "NetDemoTask";
    attr.attr_bits = 0U;
    attr.cb_mem = NULL;
    attr.cb_size = 0U;
    attr.stack_mem = NULL;
    attr.stack_size = 10240;
    attr.priority = osPriorityNormal;

    if (osThreadNew(NetDemoTask, NULL, &attr) == NULL)
    {
        printf("[NetDemoEntry] Falied to create NetDemoTask!\n");
    }
}
SYS_RUN(NetDemoEntry);

①成功连接wifi后,接下来就是创建socket套接字准备进行TCP连接

int sockfd = socket(AF_INET, SOCK_STREAM, 0); // AF_INET:IP 协议系列。SOCK_STREAM=1:TCP协议

跳转到socket的定义
#盲盒+码#【FFH】学习设备开发之Hi3861-TCPclient-开关灯-鸿蒙开发者社区

  • domain:协议族(family),常用的协议族有 AFL INET(ipv4 )、AF INET6、AF LOCAL(或称AF UNIX, Unix成socket) AF ROUTE 等。协议族决定了 socket 的地址类型,在通信中必须采用对应的地址。
  • type:指定 Socket 类型。
    #盲盒+码#【FFH】学习设备开发之Hi3861-TCPclient-开关灯-鸿蒙开发者社区
    流式 socket (SOCK STREAM)是一种面向连接的 Socket, 针对于面向连接的 TCP 服务应用。数据报式 socket(SOCK DGRAM) 是一种无连接的 Socket,对应于 无连接的 UDP 服务应用。
  • protocol: 表示传输协议,常用的有 IPPROTO_TCP 和 IPPTOTO_UDP,分别表示 TCP 传输协议和 UDP 传输协议。protocol 的值设为 0,系统会自动推演出应该使用什么协议
    ②配置
    struct sockaddr_in serverAddr = {0}; //描述互联网套接字地址的结构体
    serverAddr.sin_family = AF_INET;     // AF_INET表示IPv4协议
    serverAddr.sin_port = htons(port);   // 端口号,从主机字节序转为网络字节序
    if (inet_pton(AF_INET, host, &serverAddr.sin_addr) <= 0)
    { // 将主机IP地址从“点分十进制”字符串 转化为 标准格式(32位整数)
        printf("inet_pton failed!\r\n");
        goto do_cleanup;
    }

③与主机连接

// 尝试和目标主机建立连接,连接成功会返回0 ,失败返回 -1
    if (connect(sockfd, (struct sockaddr *)&serverAddr, sizeof(serverAddr)) < 0)
    {
        printf("connect failed!\r\n");
        goto do_cleanup;
    }
    printf("connect to server %s success!\r\n", host);

④连接成功后,发送数据给目标主机测试是否发送成功

    // 建立连接成功之后,这个TCP socket描述符 —— sockfd 就具有了 “连接状态”,发送、接收 对端都是 connect 参数指定的目标主机和端口
    retval = send(sockfd, request, sizeof(request), 0); //发送request给目标主机,成功会返回字符串长度 ,失败返回 -1
    if (retval < 0)
    {
        printf("send request failed!\r\n");
        goto do_cleanup;
    }
    printf("send request{%s} %ld to server done!\r\n", request, retval);

⑤接收服务器发送过来的数据

 retval = recv(sockfd, &response, sizeof(response), 0);//接收目标主机的消息存入response,成功会返回字符串长度 ,失败返回 -1
    if (retval <= 0) {
        printf("send response from server failed or done, %ld!\r\n", retval);
        goto do_cleanup;
    }
    response[retval] = '\0';
    printf("recv response{%s} %ld from server done!\r\n", response, retval);

3.修改代码,实现开关灯操作

①在入口demo_entry_cmsis.c 文件中初始化LED灯的io口
代码在上文已贴出
②tcp_client_test.c文件
由上文分析原始的代码可知:开发板(客户端)与主机(服务器)完成一次消息交互后就会关闭socket套接字,再关闭WiFi。
所以可以把关闭套接字的函数(close(sockfd))注释掉,再加个while死循环即可


#include <stdio.h>
#include <string.h>
#include <unistd.h>

#include "net_demo.h"
#include "net_common.h"
#define LED_TASK_GPIO 9
static char request[] = "Hello";
static char response[128] = "";

void TcpClientTest(const char *host, unsigned short port)
{
    ssize_t retval = 0;
    int sockfd = socket(AF_INET, SOCK_STREAM, 0); // AF_INET:IP 协议系列。SOCK_STREAM=1:TCP协议

    struct sockaddr_in serverAddr = {0}; //描述互联网套接字地址的结构体
    serverAddr.sin_family = AF_INET;     // AF_INET表示IPv4协议
    serverAddr.sin_port = htons(port);   // 端口号,从主机字节序转为网络字节序
    if (inet_pton(AF_INET, host, &serverAddr.sin_addr) <= 0)
    { // 将主机IP地址从“点分十进制”字符串 转化为 标准格式(32位整数)
        printf("inet_pton failed!\r\n");
        goto do_cleanup;
    }

    // 尝试和目标主机建立连接,连接成功会返回0 ,失败返回 -1
    if (connect(sockfd, (struct sockaddr *)&serverAddr, sizeof(serverAddr)) < 0)
    {
        printf("connect failed!\r\n");
        goto do_cleanup;
    }
    printf("connect to server %s success!\r\n", host);

    // 建立连接成功之后,这个TCP socket描述符 —— sockfd 就具有了 “连接状态”,发送、接收 对端都是 connect 参数指定的目标主机和端口
    retval = send(sockfd, request, sizeof(request), 0); //发送request给目标主机,成功会返回字符串长度 ,失败返回 -1
    if (retval < 0)
    {
        printf("send request failed!\r\n");
        goto do_cleanup;
    }
    printf("send request{%s} %ld to server done!\r\n", request, retval);
    while (1)
    {
        retval = recv(sockfd, &response, sizeof(response), 0); //接收目标主机的消息存入response,成功会返回字符串长度 ,失败返回 -1
        if (retval <= 0)
        {
            printf("send response from server failed or done, %ld!\r\n", retval);
            goto do_cleanup;
        }
        response[retval] = '\0';
        printf("recv response{%s} %ld from server done!\r\n", response, retval);

        if (response[0] == 'o' && response[1] == 'n')
        {
            IoTGpioSetOutputVal(LED_TASK_GPIO, 0); //开灯
            printf("The led is on\n");
        }
        if (response[0] == 'o' && response[1] == 'f' && response[2] == 'f')
        {
            IoTGpioSetOutputVal(LED_TASK_GPIO, 1); //关灯
            printf("The led is off\n");
        }
    }

do_cleanup:
    printf("do_cleanup...\r\n");
    //     close(sockfd);//关闭套接字
}
CLIENT_TEST_DEMO(TcpClientTest);

四、测试

1.安装netcat(一个非常强大的网络实用工具,可以用它来调试TCP/UDP应用程序)
二选一:

  • Linux上:sudo apt-get install netcat

  • Windows上:Windows版netcat
    #盲盒+码#【FFH】学习设备开发之Hi3861-TCPclient-开关灯-鸿蒙开发者社区
    将解压出来的文件全部复制到C:\Windows\System32的文件夹下。
    #盲盒+码#【FFH】学习设备开发之Hi3861-TCPclient-开关灯-鸿蒙开发者社区
    Windows+R cmd 打开命令行。输入nc 命令即可.
    #盲盒+码#【FFH】学习设备开发之Hi3861-TCPclient-开关灯-鸿蒙开发者社区
    2.开始测试
    先是PC机开启TCP服务端监听(我选择的是Windows启动netcat)
    #盲盒+码#【FFH】学习设备开发之Hi3861-TCPclient-开关灯-鸿蒙开发者社区
    -l: 开始监听
    -p:指定端口 (端口号必须保持一致,可在net_params.h文件配置)
    #盲盒+码#【FFH】学习设备开发之Hi3861-TCPclient-开关灯-鸿蒙开发者社区
    开发板烧录新的固件后rest启动后可观察到服务端接收到了客户端传输过来的数据"hello"
    开发板👇一开始灯是亮的状态
    #盲盒+码#【FFH】学习设备开发之Hi3861-TCPclient-开关灯-鸿蒙开发者社区

#盲盒+码#【FFH】学习设备开发之Hi3861-TCPclient-开关灯-鸿蒙开发者社区
PC服务端👇
#盲盒+码#【FFH】学习设备开发之Hi3861-TCPclient-开关灯-鸿蒙开发者社区

服务端输入"off",可让开发板关灯,完成交互。
#盲盒+码#【FFH】学习设备开发之Hi3861-TCPclient-开关灯-鸿蒙开发者社区

#盲盒+码#【FFH】学习设备开发之Hi3861-TCPclient-开关灯-鸿蒙开发者社区
继续开灯
#盲盒+码#【FFH】学习设备开发之Hi3861-TCPclient-开关灯-鸿蒙开发者社区

五、总结

这次实践中还有一些地方不能完全理解,在net_demo.h文件中
#盲盒+码#【FFH】学习设备开发之Hi3861-TCPclient-开关灯-鸿蒙开发者社区
为什么有这么多斜杠?
testFun是什么?它又是怎样跳转到tcp_client_test.c文件执行TcpClientTest()函数的呢?

©著作权归作者所有,如需转载,请注明出处,否则将追究法律责任
已于2022-12-8 14:48:24修改
9
收藏 7
回复
举报
3条回复
按时间正序
/
按时间倒序
红叶亦知秋
红叶亦知秋

同好奇,有大佬能说下为啥文件会有这么多斜杠吗

回复
2022-11-23 18:31:12
liurick
liurick

这个点灯很牛

回复
2022-11-24 14:33:35
笨笨的婧婧
笨笨的婧婧

直接用封装好的确实方便,造轮子太痛苦了

回复
2022-11-28 10:43:36
回复
    相关推荐