鸿蒙Hi3861学习十六-Huawei LiteOS-M(Socket服务端)

news2024/11/21 1:48:02

一、简介

        具体概念可以参考上一章内容:鸿蒙Hi3861学习十五-Huawei LiteOS-M(Socket客户端)_t_guest的博客-CSDN博客

 WIFI学习一(socket介绍)_wifi socket_t_guest的博客-CSDN博客

 

 二、API介绍

      bind

        函数功能:

        将socket和输入参数的地址属性进行绑定

        函数原型:

int bind(int fd, const struct sockaddr *addr, socklen_t len)

        参数:

        fd:套接字描述符,socket()函数返回值

        addr:要绑定的属性值。包括端口IP地址

struct sockaddr_in {
  u8_t            sin_len; //长度
  sa_family_t     sin_family; //地址族(address family),也就是地址类型
  in_port_t       sin_port; //16位端口号
  struct in_addr  sin_addr; //32位IP地址
#define SIN_ZERO_LEN 8
  char            sin_zero[SIN_ZERO_LEN]; //不使用,一般用0填充
 }

         这里需要注意的是,bind函数的第二个参数,会将sockaddr_in类型强转为socketaddr

        sockaddr结构体定义如下:

struct sockaddr {
  u8_t        sa_len; //长度
  sa_family_t sa_family; //地址族(address family),也就是地址类型
  char        sa_data[14]; //IP地址和端口号
};

        sockaddr sockaddr_in 长度相同,都是16字节,只是将IP地址和端口号合并到一起,用一个成员 sa_data 表示。要想给 sa_data 赋值,必须同时指明IP地址和端口号,例如”127.0.0.1:80“,遗憾的是,没有相关函数将这个字符串转换成需要的形式,也就很难给 sockaddr 类型的变量赋值,所以使用 sockaddr_in 来代替。这两个结构体的长度相同,强制转换类型时不会丢失字节,也没有多余的字节。

        可以认为,sockaddr 是一种通用的结构体,可以用来保存多种类型的IP地址和端口号,而 sockaddr_in 是专门用来保存 IPv4 地址的结构体

         len:属性值长度

        返回值:

        0:成功

        其他值:失败

        实例:

    struct sockaddr_in server_sock;

	bzero(&server_sock, sizeof(server_sock));
	server_sock.sin_family = AF_INET;   //IPV4
	server_sock.sin_addr.s_addr = htonl(INADDR_ANY);    //地质自动分配
	server_sock.sin_port = htons(_PROT_);   //端口

	//调用bind函数绑定socket和地址
	if (bind(sock_fd, (struct sockaddr *)&server_sock, sizeof(struct sockaddr)) == -1)
    {}

      listen

        函数功能:

        让套接字进入被动监听状态,如果客户端此时调用lwip_connect发送连接请求,服务器端就会收到这个请求

        函数原型:

int listen(int fd, int backlog)

        参数:

        fd:套接字描述符,socket()函数返回值

        backlog:请求队列的最大长度。当套接字正在处理客户端请求时,如果有新的请求进来,套接字是没办法处理的,只能先放入缓冲区,待当前请求处理完毕后,再从缓冲区中读取出来处理。如果不断有新的请求进来,就将他们按照先后顺序存放在缓冲区队列中,直到缓冲区满。这个缓冲区,称为请求队列(Request Queue)。

        返回值:

        0:成功

        其他值:失败

        实例:

int sock_fd;
if (listen(sock_fd, 10) == -1)    //失败
{}

      accept

        函数功能:

        当套接字处于监听状态时,可以通过accept()函数来接收客户端的请求

        函数原型:

int accept(int fd, struct sockaddr *restrict addr, socklen_t *restrict len)

        参数:

        fd:套接字描述符,lwip_accept()函数返回值

        addr:客户端的IP和端口(输出

        len:客户端信息最大长度(输入

        返回值:

        -1:失败

        其他值:监听到的客户端套接字描述符

        实例:

int new_fd;
struct sockaddr_in client_sock;
int sin_size;

sin_size = sizeof(struct sockaddr_in);
if ((new_fd = accept(sock_fd, (struct sockaddr *)&client_sock, (socklen_t *)&sin_size)) == -1)    //失败
{}

      setsockopt

        函数功能:

        设置套接字描述符选项

        函数原型:

int setsockopt(int fd, int level, int optname, const void *optval, socklen_t optlen)

        参数:

        fd:套接字描述符,socket()函数返回值

        level:选项定义的层次。

SOL_SOCKET,套接字层
IPPROTO_TCP,TCP层
IPPROTO_IP,IP层
IPPROTO_IPV6,IPV6层

        optname:需要设置的选项

        level为SOL_SOCKET(套接字层),optname可以取以下值:

#define SO_DEBUG        0x0001 /* turn on debugging info recording */
#define SO_DONTROUTE    0x0010 /* just use interface addresses */
 
#define SO_USELOOPBACK  0x0040 /* bypass hardware when possible */
 
#define SO_LINGER       0x0080 /* linger on close if data present */
#define SO_DONTLINGER   ((int)(~SO_LINGER))
#define SO_OOBINLINE    0x0100 /* leave received OOB data in line */
 
#define SO_REUSEPORT    0x0200 /* allow local address & port reuse */
 
#define SO_SNDBUF       0x1001 /* send buffer size */
#define SO_RCVBUF       0x1002 /* receive buffer size */
 
#define SO_CONTIMEO     0x1009 /* connect timeout */
 
#define SO_NO_CHECK     0x100a /* don't create UDP checksum */
#define SO_BINDTODEVICE 0x100b /* bind to device */
#define SO_REUSEADDR   0x0004 /* Allow local address reuse */
#define SO_KEEPALIVE   0x0008 /* keep connections alive */
#define SO_BROADCAST   0x0020 /* permit to send and to receive broadcast messages (see IP_SOF_BROADCAST option) */
#define SO_ACCEPTCONN   0x0002 /* socket has had listen() */
#define SO_ERROR        0x1007 /* get error status and clear */
#define SO_SNDLOWAT     0x1003 /* send low-water mark */
#define SO_SNDTIMEO     0x1005 /* send timeout */
#define SO_RCVLOWAT     0x1004 /* receive low-water mark */
#define SO_RCVTIMEO     0x1006 /* receive timeout */
#define SO_TYPE         0x1008 /* get socket type */

        SO_DEBUG打开或关闭调试信息。BOOL

当option_value不等于0时,打开调试信息,否则,关闭调试信息。它实际所做的工作是在sock->sk->sk_flag中置 SOCK_DBG(第10)位,或清SOCK_DBG位。

        SO_DONTROUTE打开或关闭路由查找功能。BOOL

当option_value不等于0时,打开,否则,关闭。它实际所做的工作是在sock->sk->sk_flag中置或清SOCK_LOCALROUTE位。

        SO_LINGER延缓关闭。struct linger

如果选择此选项, close或 shutdown将等到所有套接字里排队的消息成功发送或到达延迟时间后>才会返回. 否则, 调用将立即返回。

该选项的参数(option_value)是一个linger结构:

        struct linger {
            int   l_onoff;   //开关
            int   l_linger;  //延迟时间
        };

如果linger.l_onoff值为0(关闭),则清 sock->sk->sk_flag中的SOCK_LINGER位;否则,置该位,并赋sk->sk_lingertime值为 linger.l_linger。

        SO_DONTLINER ,不延缓关闭。BOOL

不要因为数据未发送就阻塞关闭操作。设置本选项相当于将SO_LINGER的l_onoff元素置为零。

        SO_OOBINLINE,紧急数据放入普通数据流。BOOL

该操作根据option_value的值置或清sock->sk->sk_flag中的SOCK_URGINLINE位。

        SO_SNDBUF设置发送缓冲区的大小。INT

发送缓冲区的大小是有上下限的,其上限为256 * (sizeof(struct sk_buff) + 256),下限为2048字节。该操作将sock->sk->sk_sndbuf设置为val * 2,之所以要乘以2,是防止大数据量的发送,突然导致缓冲区溢出。最后,该操作完成后,因为对发送缓冲的大小作了改变,要检查sleep队列,如果有进程正在等待写,将它们唤醒。

        SO_RCVBUF设置接收缓冲区的大小。INT

接收缓冲区大小的上下限分别是:256 * (sizeof(struct sk_buff) + 256)和256字节。该操作将sock->sk->sk_rcvbuf设置为val * 2。

        SO_NO_CHECK打开或关闭校验和。BOOL

该操作根据option_value的值,设置sock->sk->sk_no_check。

        SO_BINDTODEVICE,将套接字绑定到一个特定的设备上。BOOL

该选项最终将设备赋给sock->sk->sk_bound_dev_if。

        SO_REUSEADDR打开或关闭地址复用功能。BOOL

当option_value不等于0时,打开,否则,关闭。它实际所做的工作是置sock->sk->sk_reuse为1或0。

        SO_KEEPALIVE,套接字保活。BOOL

如果协议是TCP,并且当前的套接字状态不是侦听(listen)或关闭(close),那么,当option_value不是零时,启用TCP保活定时 器,否则关闭保活定时器。对于所有协议,该操作都会根据option_value置或清 sock->sk->sk_flag中的 SOCK_KEEPOPEN位。

        SO_BROADCAST允许或禁止发送广播数据。BOOL

当option_value不等于0时,允许,否则,禁止。它实际所做的工作是在sock->sk->sk_flag中置或清SOCK_BROADCAST位。

        SO_RCVTIMEO,设置接收超时时间。INT

该选项最终将接收超时时间赋给sock->sk->sk_rcvtimeo。

         SO_SNDTIMEO,设置发送超时时间。INT

int keepAlive = 1;    // 非0值,开启keepalive属性

//设置保活模式
if(setsockopt(*client, SOL_SOCKET, SO_KEEPALIVE, &keepAlive, sizeof(int)) < 0)
{
} //失败

        level为IPPROTO_TCP(TCP层),optname可以取一下值:

#define TCP_NODELAY    0x01    /* don't delay send to coalesce packets */
#define TCP_KEEPALIVE  0x02    /* send KEEPALIVE probes when idle for pcb->keep_idle milliseconds */
#define TCP_KEEPIDLE   0x03    /* set pcb->keep_idle  - Same as TCP_KEEPALIVE, but use seconds for get/setsockopt */
#define TCP_KEEPINTVL  0x04    /* set pcb->keep_intvl - Use seconds for get/setsockopt */
#define TCP_KEEPCNT    0x05    /* set pcb->keep_cnt   - Use number of probes sent for get/setsockopt */
#define TCP_MAXSEG     0x06    /* set maximum segment size */

        TCP_NODELAY不延迟发送。BOOL

        TCP_KEEPALIVE,当在空闲时,发送keepalive探测包

        TCP_KEEPIDLE,设置连接上如果没有数据发送时,多久发送keepalive探测分组,单位为秒。

        TCP_KEEPINTVL,前后两次探测之间的时间间隔,单位是秒

        TCP_KEEPCNT最大重试次数

        返回值:

        -1:失败

        其他值:成功

        实例:

int keepAlive = 1;    //开启keepalive属性
int keepIdle = 5;    //如果连接在5秒内没有任何数据来往,则进行此TCP层的探测
int keepInterval = 5;    //探测发包间隔为5秒
int keepCount = 2;    //尝试探测的最多次数
if(setsockopt(*client, SOL_SOCKET, SO_KEEPALIVE, &keepAlive, sizeof(int)) < 0)
{
    //失败
} 
if(setsockopt(*client, IPPROTO_TCP, TCP_KEEPIDLE, &keepIdle, sizeof(int)) < 0)
{
    //失败
} 
if(setsockopt(*client, IPPROTO_TCP, TCP_KEEPINTVL, &keepInterval, sizeof(int)) < 0)
{
    //失败
} 
if(setsockopt(*client, IPPROTO_TCP, TCP_KEEPCNT, &keepCount, sizeof(int)) < 0)
{
    //失败
} 

三、实例

        这里创建一个热点,并且再创建一个socket服务端,等待设备连接。

        在BUILD.gn文件中添加如下代码:

    include_dirs = [
        "//utild/native/lite/include",
        "//base/iot_hardware/interfaces/kits/wifiiot_lite",
        "//utils/native/lite/include",
        "//kernel/liteos_m/components/cmsis/2.0",
        "//foundation/communication/interfaces/kits/wifi_lite/wifiservice",
        "//vendor/hisi/hi3861/hi3861/third_party/lwip_sack/include/",
        "src",
    ]
#include <stdio.h>
#include <unistd.h>

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

#include "lwip/sockets.h"
#include "lwip/netifapi.h"

#define LOG_I(fmt, args...)   printf("<%8ld> - [SOCKET_SERVER]:"fmt"\r\n",osKernelGetTickCount(),##args);
#define LOG_E(fmt, args...)   printf("<%8ld>-[SOCKET_SERVER_ERR]>>>>>>>>>>>>:"fmt"\r\n",osKernelGetTickCount(), ##args);

#define _PROT_ 8888
#define TCP_BACKLOG 10

//在sock_fd 进行监听,在 new_fd 接收新的链接
static int sock_fd, new_fd;

static char recvbuf[512] = {0};
static char *buf = "Hello! This is a socket server test";

static void TCPServerTask(void)
{
	//服务端地址信息
	struct sockaddr_in server_sock;

	//客户端地址信息
	struct sockaddr_in client_sock;
	int sin_size;

	struct sockaddr_in *cli_addr;

	//连接Wifi
	extern BOOL drv_wifi_create_ap(const char *ssid, const char *psk);
    if(drv_wifi_create_ap("Harmony_test_ap", "123123123") != 0)
    {
        LOG_E("AP create error\r\n");
		exit(1);
    }

	//创建socket
	if ((sock_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
	{
		LOG_E("socket is error\r\n");
		exit(1);
	}

    LOG_I("socket create success");

	bzero(&server_sock, sizeof(server_sock));
	server_sock.sin_family = AF_INET;   //IPV4
	server_sock.sin_addr.s_addr = htonl(INADDR_ANY);    //地址自动分配
	server_sock.sin_port = htons(_PROT_);   //端口

	//调用bind函数绑定socket和地址
	if (bind(sock_fd, (struct sockaddr *)&server_sock, sizeof(struct sockaddr)) == -1)
	{
		LOG_E("bind is error\r\n");
		exit(1);
	}

    LOG_I("socket bind success");

	//调用listen函数监听(指定port监听)
	if (listen(sock_fd, TCP_BACKLOG) == -1)
	{
		LOG_E("listen is error\r\n");
		exit(1);
	}

	LOG_I("there is a client has connected");

    struct netif *lwip_netif = NULL;

    lwip_netif = netifapi_netif_find("ap0");  //获取网络借口,用于IP操作
    LOG_I("ip addr:%s",ipaddr_ntoa(&lwip_netif->ip_addr));
    LOG_I("netmask:%s",ipaddr_ntoa(&lwip_netif->netmask));
    LOG_I("gw:%s",ipaddr_ntoa(&lwip_netif->gw));

	//调用accept函数从队列中
	while (1)
	{
		sin_size = sizeof(struct sockaddr_in);

        LOG_I("socket start accepte");

		if ((new_fd = accept(sock_fd, (struct sockaddr *)&client_sock, (socklen_t *)&sin_size)) == -1)
		{
			LOG_E("accept");
			continue;
		}

		cli_addr = malloc(sizeof(struct sockaddr));

		LOG_I("accept client,ip:%s\r\n",inet_ntoa(client_sock.sin_addr));

		if (cli_addr != NULL)
		{
			memcpy(cli_addr, &client_sock, sizeof(struct sockaddr));
		}

		//处理目标
		ssize_t ret;
        uint8_t error_cnt = 0;

		while (1)
		{
            ret = -1;
            memset((void *)recvbuf,0,sizeof(recvbuf));
			if ((ret = recv(new_fd, recvbuf, sizeof(recvbuf), 0)) == -1)
			{
				LOG_I("recv error \r\n");
                error_cnt++;
                if(error_cnt > 3) break;
			}
			LOG_I("recv :%s\r\n", recvbuf);
			sleep(2);
            ret = -1;
			if ((ret = send(new_fd, buf, strlen(buf) + 1, 0)) == -1)
			{
				LOG_E("send error ");
                error_cnt++;
                if(error_cnt > 3) break;
			}
            else
            {
                LOG_I("socket send success");
            }

			sleep(2);
		}

		lwip_close(new_fd);
	}
}

void app_socket_service_init(void)
{
	osThreadAttr_t attr;

	attr.name = "TCPServerTask";
	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((osThreadFunc_t)TCPServerTask, NULL, &attr) == NULL)
	{
		LOG_I("[TCPServerDemo] Falied to create TCPServerTask!\n");
	}
}

        看结果:

 

 

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/540932.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

pdf怎么转换成jpg图片?转换方式说明

将PDF转换为JPG图片是一种常见的文件格式转换操作&#xff0c;通常用于在不同设备上查看和共享PDF文档。但是&#xff0c;大多数情况下&#xff0c;将PDF转换为JPG图片可能并不总是可行或有效的。在本文中&#xff0c;我们将讨论为什么不能将PDF转换为JPG图片&#xff0c;以及如…

了解设备健康报表的关键指标与分析方法

在现代工业领域中&#xff0c;设备健康报表是一项关键工具&#xff0c;通过收集和分析设备的运行数据&#xff0c;它提供了关于设备状态和性能的重要指标。设备健康报表对于企业的设备管理和维护决策至关重要。本文将介绍设备健康报表的关键指标&#xff0c;并提供解读和分析方…

苹果手机、电脑如何进行屏幕录制?苹果录屏功能在哪?

随着人们生活水平的提高&#xff0c;不少小伙伴都会选择苹果手机、苹果电脑作为主要的设备。因为使用苹果电脑进行办公&#xff0c;不仅仅能够提升效率&#xff0c;对于文件的安全性也是有一些保障的。那么&#xff0c;在使用苹果电脑的时候&#xff0c;如果需要有录屏的需求该…

【JAVA】this关键字和static关键字

目录 1.this关键字 2.static关键字 容易混淆的问题 1.this关键字 一个对象一个this。this是一个变量&#xff0c;是一个关键字&#xff0c;是一个引用。this保存当前对象的内存地址&#xff0c;指向自身。所以&#xff0c;严格意义上来说&#xff0c;this代表的就是“当前对象…

Avada学习之-如何加入谷歌地图

如何正确设置谷歌地图 在给Wordpress网站配置谷歌地图时&#xff0c;常常会出现谷歌地图无法显示的问题。具体提示为Oops! something went wrong&#xff0c;如下图。出现这种问题有如下原因。 内容 隐藏 一、地址不正确 二、API 没填 三、插件冲突 一、地址不正确 很多…

iOS正确获取图片参数深入探究及CGImageRef的使用

一 图片参数的正确获取 先拿一张图片作为测试使用 图片参数如下&#xff1a; 图片的尺寸为&#xff1a; -宽1236个像素点 -高748个像素点 -色彩空间为RGB -描述文件为彩色LCD -带有Alpha通道 请记住这几个参数&#xff0c;待会儿我们演示如何正确获取。 将这张图片分别放在…

光纤衰减器作用及使用说明

在光纤通信中&#xff0c;光信号的强度过大或过小都会对信号的传输和接收产生不良的影响&#xff0c;因此光衰减器在光通信系统中起到了重要的作用。那什么是光衰减器呢&#xff1f;它又有什么作用呢&#xff1f;下面跟着小易一起来了解一下吧&#xff01; 一、什么是光纤衰减…

APP自动化测试工具的作用和特点

随着移动应用的迅速发展&#xff0c;APP的自动化测试工具变得越来越重要。APP自动化测试工具可以帮助开发者在应用程序的不同平台上执行测试&#xff0c;从而检测和确保应用程序的完整性、稳定性和可靠性。下面将介绍APP自动化测试工具的作用和特点。 一、作用 提高测试效率&am…

Android自定义控件——控件的测量和绘制

控件的测量和绘制 控件架构setContentView()MeasureSpecViewView的测量View的绘制 ViewGroupViewGroup的测量ViewGroup的绘制 自定义View对现有控件进行扩展案例一&#xff1a;添加背景案例二&#xff1a;文字闪动 创建复合控件重写View实现全新控件案例一&#xff1a;弧线展示…

FLEXPART--空气块轨迹-扩散模式

模式简介&#xff1a; FLEXPART(Flexible Particle Dispersion Model)模式是由挪威大气研究所(Norwegian Institute for Air Research)和德国慕尼黑工业大学(Technical University of Munich)联合开发的空气块轨迹&#xff0d;扩散模式, 其通过计算区域内所有气块的运动轨迹进…

PLE - 多任务模型(腾讯)

文章目录 1、动机2、模型结构3、代码实现细节&#xff1a; Progressive Layered Extraction (PLE): A Novel Multi-Task Learning (MTL) Model for Personalized Recommendations论文发表在 RecSys-2020&#xff0c; best paper。ple主要缓解了mtl里两大问题&#xff1a;负迁移…

如何在海量、庞杂、混合的数据中发现价值?

数字时代&#xff0c;数据上升为国家战略&#xff0c;数据成为重要的生产要素和资产&#xff0c;得到了越来越多企业的重视&#xff0c;也成为企业数字化转型的重要抓手。据IDC中国预测&#xff0c;2025年中国大数据生产量有望增长至48.6ZB。 随着越来越大的数据量&#xff0c…

【C++从0到王者】第六站:类和对象(下)

文章目录 一、再谈构造函数1.构造函数体赋值2.初始化列表1>初始化列表的使用2>初始化列表的注意事项 3.explicit关键词 二、static成员1.如何统计当前程序中变量的个数2.static的特性3.从1加到n4.设计一个类&#xff0c;只能在栈或者堆上开辟空间 三、友元1.友元函数2.友…

一个简化、落地的实时数据仓库解决方案

从传统的经验来讲&#xff0c;数据仓库有一个很重要的功能是记录数据变化历史。通常&#xff0c;数据仓库都希望从业务上线的第一天开始有数据&#xff0c;然后一直记录到现在。但实时处理技术&#xff0c;又是强调当前处理状态的一门技术&#xff0c;所以当这两个相互对立的方…

聚合数据证件识别接口-基于PHP示例代码

1、开通接口 以下代码示例基于聚合数据提供的证件识别接口&#xff0c;使用前需要先注册申请本接口&#xff0c;获得请求密钥key。 接口文档地址&#xff1a;https://www.juhe.cn/docs/api/id/153 2、功能介绍 通过自动识别的方式获取常规证件文字内容&#xff0c;免去用户…

react函数式组件转化为string---renderToString

需求 使用aggrid的过程中&#xff0c;某个自定义的图标需要传dom的字符串。 但在react的开发中&#xff0c;一般都是组件的概念&#xff08;ReactNode&#xff0c;JSX.Element&#xff09; 因此需要一个方法将dom组件转化为字符串 收获 找到了官网的API——renderToString 作…

虚拟主机部署ssl证书(https)流程

注意事项&#xff1a; 1、域名要做别名解析指向二级域名 2、证书已经申请完成&#xff0c;其他公司的证书要下载导入到西部数码。 虚拟主机部署教程如下&#xff1a; 部署证书 首先要将域名绑定到主机上&#xff0c;在主机控制面板找到【SSL部署】按钮。 在西部数码申请过证…

FFmpeg 媒体文件播放 格式变化流程简述

例如&#xff0c;要播放一个 MP4 文件&#xff0c;这个文件要经过啥呢&#xff1f; 一个 MP4 文件中包含了&#xff1a;视频压缩数据流&#xff08;如H.264、H.265&#xff09;和音频压缩数据&#xff08;如aac、MP3&#xff09; 首先需要经过解码成为原始数据&#xff0c;视频…

数据库原理及应用上机实验一

✨作者&#xff1a;命运之光 ✨专栏&#xff1a;数据库原理及应用上机实验报告整理 目录 ✨一、实验目的和要求 ✨二、实验内容与步骤 &#x1f353;&#x1f353;前言&#xff1a; 数据库原理及应用上机实验报告的一个简单整理后期还会不断完善&#x1f353;&#x1f353;…

HarmonyOS低代码开发-创建新工程方式

使用低代码开发应用或服务有以下两种开发方式&#xff1a; 创建一个支持低代码开发的新工程&#xff0c;开发应用或服务的UI界面。在已有工程中&#xff0c;创建Visual文件来开发应用或服务的UI界面。ArkTS工程和JS工程使用低代码的步骤相同&#xff0c;接下来以JS工程为例讲解…