当前位置: 首页 > news >正文

鸿蒙 WiFi 连接 流程

那当界面上显示扫描到的所有Ap时,我们选择其中的一个Ap发起连接,看下代码流程是怎样的。

// applications/standard/settings/product/phone/src/main/ets/model/wifiImpl/WifiModel.tsconnectWiFi(password: string) {let apInfo = this.userSelectedAp.getApInfo();let ret = false;let connectParam: any = {"ssid": apInfo.ssid,"bssid": apInfo.bssid,"preSharedKey": password,"isHiddenSsid": false, // we don't support connect to hidden ap yet"securityType": apInfo.securityType};LogUtil.info(MODULE_TAG + 'disconnect WiFi isConnected is ' + wifi.isConnected());     ---》当前如果没有连接就是falseif (wifi.isConnected() === true) {ret = wifi.disconnect();LogUtil.info(MODULE_TAG + 'disconnect WiFi ret is ' + ret);this.registerWiFiConnectionObserver((code: Number) => {if (code === 0) {ret = wifi.connectToDevice(connectParam);this.unregisterWiFiConnectionObserver();}})}else{ret = wifi.connectToDevice(connectParam);                        ---> 就会走这里发起连接LogUtil.info(MODULE_TAG + 'connect WiFi ret is ' + ret);}return ret;}
// 有了前面的基础我们知道Wifi_Device的实现是wifi_device_impl, 然后在通过代理和服务端交互,所以我们直接看服务端的实现即可,调用流程这里就省略掉
// foundation/communication/wifi/wifi/services/wifi_standard/wifi_framework/wifi_manage/wifi_device_service_impl.cpp
ErrCode WifiDeviceServiceImpl::ConnectToDevice(const WifiDeviceConfig &config)
{if (!WifiAuthCenter::IsSystemAppByToken()) {WIFI_LOGE("ConnectToDevice:NOT System APP, PERMISSION_DENIED!");return WIFI_OPT_NON_SYSTEMAPP;}if (WifiPermissionUtils::VerifySetWifiInfoPermission() == PERMISSION_DENIED) {WIFI_LOGE("ConnectToDevice:VerifySetWifiInfoPermission PERMISSION_DENIED!");return WIFI_OPT_PERMISSION_DENIED;}if (WifiPermissionUtils::VerifyWifiConnectionPermission() == PERMISSION_DENIED) {WIFI_LOGE("ConnectToDevice:VerifyWifiConnectionPermission PERMISSION_DENIED!");return WIFI_OPT_PERMISSION_DENIED;}if (WifiPermissionUtils::VerifySetWifiConfigPermission() == PERMISSION_DENIED) {WIFI_LOGE("ConnectToDevice:VerifySetWifiConfigPermission PERMISSION_DENIED!");return WIFI_OPT_PERMISSION_DENIED;}if (!CheckConfigPwd(config)) {WIFI_LOGE("CheckConfigPwd failed!");return WIFI_OPT_INVALID_PARAM;}if (!IsStaServiceRunning()) {WIFI_LOGE("ConnectToDevice: sta service is not running!");return WIFI_OPT_STA_NOT_OPENED;}IStaService *pService = WifiServiceManager::GetInstance().GetStaServiceInst();if (pService == nullptr) {WIFI_LOGE("ConnectToNetwork: pService is nullptr!");return WIFI_OPT_STA_NOT_OPENED;}return pService->ConnectToDevice(config);
}// foundation/communication/wifi/wifi/services/wifi_standard/wifi_framework/wifi_manage/wifi_sta/sta_interface.cpp
ErrCode StaInterface::ConnectToDevice(const WifiDeviceConfig &config)
{LOGD("Enter StaInterface::Connect.\n");CHECK_NULL_AND_RETURN(pStaService, WIFI_OPT_FAILED);if (pStaService->ConnectToDevice(config) != WIFI_OPT_SUCCESS) {LOGD("ConnectTo failed.\n");return WIFI_OPT_FAILED;}return WIFI_OPT_SUCCESS;
}// foundation/communication/wifi/wifi/services/wifi_standard/wifi_framework/wifi_manage/wifi_sta/sta_service.cpp
ErrCode StaService::ConnectToDevice(const WifiDeviceConfig &config) const
{LOGI("Enter StaService::ConnectToDevice, ssid = %{public}s.\n", SsidAnonymize(config.ssid).c_str());CHECK_NULL_AND_RETURN(pStaStateMachine, WIFI_OPT_FAILED);int netWorkId = AddDeviceConfig(config);       ---》 对于新的AP,先添加,和Android都是一样的if(netWorkId == INVALID_NETWORK_ID) {LOGD("StaService::ConnectToDevice, AddDeviceConfig failed!");return WIFI_OPT_FAILED;}LOGI("StaService::ConnectToDevice, netWorkId: %{public}d", netWorkId);pStaStateMachine->SendMessage(WIFI_SVR_CMD_STA_CONNECT_NETWORK, netWorkId, NETWORK_SELECTED_BY_USER);    ---> 让状态机处理连接return WIFI_OPT_SUCCESS;
}

这里主要是两个函数:
一个是 AddDeviceConfig ,
一个是SendMessage(WIFI_SVR_CMD_STA_CONNECT_NETWORK, netWorkId, NETWORK_SELECTED_BY_USER)
流程都是一样的,通过,通过RPC访问HAL层,然后再把命令发给wpa,wpa收到命令后触发相应动作,这里我们看后者,状态机处理消息
// foundation/communication/wifi/wifi/services/wifi_standard/wifi_framework/wifi_manage/wifi_sta/sta_state_machine.cpp

// 调用的这个函数:
void StaStateMachine::DealConnectToUserSelectedNetwork(InternalMessage *msg)
{LOGI("enter DealConnectToUserSelectedNetwork.\n");if (msg == nullptr) {LOGE("msg is null.\n");return;}int networkId = msg->GetParam1();int connTriggerMode = msg->GetParam2();if (connTriggerMode != NETWORK_SELECTED_BY_RETRY) {linkedInfo.retryedConnCount = 0;}if (networkId == linkedInfo.networkId) {if (linkedInfo.connState == ConnState::CONNECTED) {staCallback.OnStaConnChanged(OperateResState::CONNECT_AP_CONNECTED, linkedInfo);WIFI_LOGI("This network is in use and does not need to be reconnected.\n");return;}if (linkedInfo.connState == ConnState::CONNECTING &&linkedInfo.detailedState == DetailedState::OBTAINING_IPADDR) {WIFI_LOGI("This network is connecting and does not need to be reconnected.\n");return;}}/* Save connection information. */SaveDiscReason(DisconnectedReason::DISC_REASON_DEFAULT);SaveLinkstate(ConnState::CONNECTING, DetailedState::CONNECTING);/* Callback result to InterfaceService. */staCallback.OnStaConnChanged(OperateResState::CONNECT_CONNECTING, linkedInfo);if (StartConnectToNetwork(networkId) != WIFI_OPT_SUCCESS) {     ---》 继续看这个流程OnConnectFailed(networkId);return;}/* Sets network status. */WifiSettings::GetInstance().EnableNetwork(networkId, connTriggerMode == NETWORK_SELECTED_BY_USER);WifiSettings::GetInstance().SetDeviceAfterConnect(networkId);WifiSettings::GetInstance().SetDeviceState(networkId, (int)WifiDeviceConfigStatus::ENABLED, false);
}//
ErrCode StaStateMachine::StartConnectToNetwork(int networkId)
{targetNetworkId = networkId;SetRandomMac(targetNetworkId);     ---> 随机mac地址WifiDeviceConfig deviceConfig;if (WifiSettings::GetInstance().GetDeviceConfig(networkId, deviceConfig) != 0) {LOGE("StartConnectToNetwork get GetDeviceConfig failed!");return WIFI_OPT_FAILED;}WifiStaHalInterface::GetInstance().SetBssid(networkId, deviceConfig.userSelectBssid.c_str());// 使能apif (WifiStaHalInterface::GetInstance().EnableNetwork(targetNetworkId) != WIFI_IDL_OPT_OK) {LOGE("EnableNetwork() failed!");return WIFI_OPT_FAILED;}// 连接if (WifiStaHalInterface::GetInstance().Connect(targetNetworkId) != WIFI_IDL_OPT_OK) {LOGE("Connect failed!");staCallback.OnStaConnChanged(OperateResState::CONNECT_SELECT_NETWORK_FAILED, linkedInfo);return WIFI_OPT_FAILED;}// 保存if (WifiStaHalInterface::GetInstance().SaveDeviceConfig() != WIFI_IDL_OPT_OK) {/* OHOS's wpa don't support save command, so don't judge as failure */LOGE("SaveDeviceConfig() failed!");}StopTimer(static_cast<int>(CMD_NETWORK_CONNECT_TIMEOUT));StartTimer(static_cast<int>(CMD_NETWORK_CONNECT_TIMEOUT), STA_NETWORK_CONNECTTING_DELAY);return WIFI_OPT_SUCCESS;// 上面的三步骤和Android都差不多,基本上大家都熟悉
// foundation/communication/wifi/wifi/services/wifi_standard/wifi_framework/wifi_manage/idl_client/wifi_sta_hal_interface.cpp
WifiErrorNo WifiStaHalInterface::Connect(int networkId)
{CHECK_NULL_AND_RETURN(mIdlClient, WIFI_IDL_OPT_FAILED);return mIdlClient->ReqConnect(networkId);
}
// idl_client , 前面几篇都讲过了,这里直接贴HAL层的代码了
// foundation/communication/wifi/wifi/services/wifi_standard/wifi_hal/wifi_hal_crpc_sta.c
int RpcConnect(RpcServer *server, Context *context)
{if (server == NULL || context == NULL) {return HAL_FAILURE;}int networkId = 0;if (ReadInt(context, &networkId) < 0) {return HAL_FAILURE;}WifiErrorNo err = Connect(networkId);    ---> 看这个的实现WriteBegin(context, 0);WriteInt(context, err);WriteEnd(context);return HAL_SUCCESS;
}// foundation/communication/wifi/wifi/services/wifi_standard/wifi_hal/wifi_hal_sta_interface.c
WifiErrorNo Connect(int networkId)
{LOGD("Connect() networkid %{public}d", networkId);WifiWpaStaInterface *pStaIfc = GetWifiStaInterface(0);if (pStaIfc == NULL) {return WIFI_HAL_SUPPLICANT_NOT_INIT;}int ret = pStaIfc->wpaCliCmdSelectNetwork(pStaIfc, networkId);if (ret < 0) {LOGE("WpaCliCmdSelectNetwork failed! ret=%{public}d", ret);return WIFI_HAL_FAILED;}return WIFI_HAL_SUCCESS;
}

继续看 pStaIfc->wpaCliCmdSelectNetwork 的调用,

//foundation/communication/wifi/wifi/services/wifi_standard/wifi_hal/wifi_hal_module/wpa_supplicant_hal/wpa_sta_hal/wifi_supplicant_hal.c 
static int WpaCliCmdSelectNetwork(WifiWpaStaInterface *this, int networkId)
{if (this == NULL) {return -1;}char cmd[CMD_BUFFER_SIZE] = {0};char buf[REPLY_BUF_SMALL_LENGTH] = {0};if (snprintf_s(cmd, sizeof(cmd), sizeof(cmd) - 1, "IFNAME=%s SELECT_NETWORK %d", this->ifname, networkId) < 0) {LOGE("snprintf err");return -1;}return WpaCliCmd(cmd, buf, sizeof(buf));
}// foundation/communication/wifi/wifi/services/wifi_standard/wifi_hal/wifi_hal_module/wpa_supplicant_hal/wifi_wpa_common.cint WpaCliCmd(const char *cmd, char *buf, size_t bufLen)
{if (cmd == NULL || buf == NULL || bufLen <= 0) {LOGE("WpaCliCmd, invalid parameters!");return -1;}WpaCtrl *ctrl = GetWpaCtrl();if (ctrl == NULL || ctrl->pSend == NULL) {LOGE("WpaCliCmd, ctrl/ctrl->pSend is NULL!");return -1;}size_t len = bufLen - 1;LOGI("wpa_ctrl_request -> cmd: %{private}s", cmd);int ret = wpa_ctrl_request(ctrl->pSend, cmd, strlen(cmd), buf, &len, NULL);    ---》 发送给wpaif (ret == WPA_CMD_RETURN_TIMEOUT) {LOGE("[%{private}s] command timed out.", cmd);return WPA_CMD_RETURN_TIMEOUT;} else if (ret < 0) {LOGE("[%{private}s] command failed.", cmd);return -1;}buf[len] = '\0';LOGI("wpa_ctrl_request -> buf: %{private}s", buf);if (strncmp(buf, "FAIL\n", strlen("FAIL\n")) == 0 ||strncmp(buf, "UNKNOWN COMMAND\n", strlen("UNKNOWN COMMAND\n")) == 0) {LOGE("%{private}s request success, but response %{public}s", cmd, buf);return -1;}return 0;
}

至此,连接的命令就发给wpa了,接下来就是协议上的连接了(四次握手),由wpa去交互完成,连接成功后,wpa上报 CTRL-EVENT-CONNECTED 事件,上层就收到,就开始走DHCP/或者是静态ip的流程,下一篇继续梳理这个。

相关文章:

鸿蒙 WiFi 连接 流程

那当界面上显示扫描到的所有Ap时&#xff0c;我们选择其中的一个Ap发起连接&#xff0c;看下代码流程是怎样的。 // applications/standard/settings/product/phone/src/main/ets/model/wifiImpl/WifiModel.tsconnectWiFi(password: string) {let apInfo this.userSelectedAp…...

golang 创建unix socket http服务端

服务端 package mainimport ("fmt""net""net/http""os" )func main() {http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {w.Write([]byte("hello"))})http.HandleFunc("/world", …...

annaconda如何切换当前python环境

annaconda默认的python环境是base&#xff1a; 把各种项目的依赖都安装到base环境中不是一个好的习惯&#xff0c;比如说我们做爬虫项目和做自动化测试项目等所需要的依赖是不一样的&#xff0c;我们可以将为每个项目创建自己的环境&#xff0c;在各自的环境中安装自己的依赖&…...

gtkmm 与 Cambalache 与 Gtk::Builder (新手向)_

文章目录 前言Cambalache检查Xml.cpp文件如何写才能显示UI首先creat获取ui里的对象显示 前言 新手刚刚使用时的笔记 Cambalache检查Xml 窗口右键inspect UI Definition切换到Xml视图, 可以全选复制粘贴到你的ui文件里, Cambalache 只能保存为.cmb工程文件, 导出也不知道导出…...

uniapp小程序端使用计算属性动态绑定style样式踩坑

踩坑点: 使用uniapp编译小程序端动态绑定复杂style使用计算属性方式&#xff0c;return必须返回json字符串格式&#xff0c;不能返回object&#xff0c;否则会不起作用。 代码总览 视图层 逻辑层&#xff08;注意这里是使用的计算属性哈&#xff09; 这里我封装成了一个个性化…...

计算机网络概念、组成、功能和分类

文章目录 概要1.怎么学习计算机网络2.概念3.功能、组成4.工作方式、功能组成5.分类 概要 概念、组成、功能和分类 1.怎么学习计算机网络 2.概念 通信设备&#xff1a;比如路由器、路由器 线路&#xff1a;将系统和通信设备两者联系的介质之类的 计算机网络是互连的、自治的的计…...

MyBatisPlus基础操作之增删改查

目录 一、基本使用 1.1 插入数据 1.2 删除操作 1.3 更新操作 二、条件构造器Wrapper 2.1 常用AbstractWrapper方法 2.1.1 示例一 2.2.2 示例二 2.2.3 示例三 2.2 常用QueryWrapper方法 2.2.1 示例一 2.2.2 示例二 2.2.3 示例三&#xff08;常用&#xff09; 2.3 常…...

视频处理学习笔记1:YUYV422、NV12和h264

最近因为工作关系在恶补视频相关知识点&#xff0c;在此做一记录便于日后复习。 以下均是个人学习经验总结&#xff0c;可能存在错误和坑&#xff0c;欢迎大佬指教。 工作中用到的是YUYV422存储格式。存储的就是裸流YUYV422格式文件。 YUYV422是两个像素点共用一个UV分量&am…...

CTFshow web(命令执行29-36)

?ceval($_GET[shy]);&shypassthru(cat flag.php); #逃逸过滤 ?cinclude%09$_GET[shy]?>&shyphp://filter/readconvert.base64-encode/resourceflag.php #文件包含 ?cinclude%0a$_GET[cmd]?>&cmdphp://filter/readconvert.base64-encode/…...

PyTorch深度学习实战(23)——从零开始实现SSD目标检测

PyTorch深度学习实战&#xff08;23&#xff09;——从零开始实现SSD目标检测 0. 前言1. SSD 目标检测模型1.1 SSD 网络架构1.2 利用不同网络层执行边界框和类别预测1.3 不同网络层中默认框的尺寸和宽高比1.4 数据准备1.5 模型训练 2. 实现 SSD 目标检测2.1 SSD300 架构2.2 Mul…...

【附代码】NumPy加速库NumExpr(大数据)

文章目录 相关文献测试电脑配置数组加减乘除数组乘方Pandas加减乘除总结 作者&#xff1a;小猪快跑 基础数学&计算数学&#xff0c;从事优化领域5年&#xff0c;主要研究方向&#xff1a;MIP求解器、整数规划、随机规划、智能优化算法 如有错误&#xff0c;欢迎指正。如有…...

4、安全开发-Python-蓝队项目流量攻击分析文件动态监控图片隐写技术

用途&#xff1a;个人学习笔记&#xff0c;有所借鉴&#xff0c;欢迎指正&#xff01; 总结&#xff1a; &#xff08;1&#xff09;使用python脚本Scapy库实现指定网卡的流量抓包分析 &#xff08;2&#xff09;使用python脚本Watchdog实现指定目录文件行为监控 &#xff08;…...

MySQL 日志管理

4.6&#xff09;日志管理 MySQL 支持丰富的日志类型&#xff0c;如下&#xff1a; 事务日志&#xff1a;transaction log 事务日志的写入类型为 "追加"&#xff0c;因此其操作为 "顺序IO"&#xff1b; 通常也被称为&#xff1a;预写式日志 write ahead…...

Python CSV文件读取和写入

本文主要为Python 实现CSV文件读取和写入操作。 CSV文件写入和读取 因为没有现成的csv文件&#xff0c;所以csv的顺序为先写入后读取。 写入 创建csv文件并把数据写入&#xff0c;有两种实现方式&#xff1a;直接插入所有行和插入单行。 示例如下&#xff1a; import csv i…...

如何使用C#调用LabVIEW算法

新建一个工程 这是必须的&#xff1b; 创建项目 项目 点击完成&#xff1b; 将项目另存为&#xff1b;方便后续的使用&#xff1b; 创建 一个测试VI 功能很简单&#xff0c;用的一个加法&#xff1b;将加数A&#xff0c;B设置为输入&#xff0c;和C设置为输出&#xff0c;…...

调用百度文心AI作画API实现中文-图像跨模态生成

作者介绍 乔冠华&#xff0c;女&#xff0c;西安工程大学电子信息学院&#xff0c;2020级硕士研究生&#xff0c;张宏伟人工智能课题组。 研究方向&#xff1a;机器视觉与人工智能。 电子邮件&#xff1a;1078914066qq.com 一&#xff0e;文心AI作画API介绍 1. 文心AI作画 文…...

JAVA SpringBoot中使用redis的事务

目录 一、Java语言介绍 二、SpringBoot框架介绍 三、Redis缓存介绍 四、什么是redis的事务 一、Java语言介绍 Java是一种广泛使用的高级编程语言,由Sun Microsystems公司于1995年推出。它的设计目标是要求“一次编写,到处运行”&#xff08;Write Once, Run Anywhere, WOR…...

docker部署自己的网站wordpress

目录 安装 1.创建目录 2.创建并启动mysql 3.创建并启动wordpress 使用 1.设置语言 2.设置基础信息 3.首页 安装 1.创建目录 mkdir -p /opt/wordpress/{db,data} 2.创建并启动mysql docker run -d --name my_mysql --restart always -e MYSQL_ROOT_PASSWORD123456 -e …...

基于ISO13400 (DoIP) 实现车辆刷写

近年来&#xff0c;在整车研发中基于以太网实现车辆高带宽通讯无疑是人们热议的话题。无论是车内基于车载以太网来减少线束成本&#xff0c;实现ADAS、信息娱乐系统等技术&#xff0c;还是基于新的电子电气架构以及远程诊断需求来实现以太网诊断&#xff08;DoIP&#xff09;&a…...

Chrome 沙箱逃逸 -- Plaid CTF 2020 mojo

文章目录 前置知识参考文章环境搭建题目环境调试环境 题目分析附件分析漏洞分析OOBUAF 漏洞利用总结 前置知识 Mojo & Services 简介 chromium mojo 快速入门 Mojo docs Intro to Mojo & Services 译文&#xff1a;利用Mojo IPC的UAF漏洞实现Chrome浏览器沙箱逃逸原文…...

网络编程(Modbus进阶)

思维导图 Modbus RTU&#xff08;先学一点理论&#xff09; 概念 Modbus RTU 是工业自动化领域 最广泛应用的串行通信协议&#xff0c;由 Modicon 公司&#xff08;现施耐德电气&#xff09;于 1979 年推出。它以 高效率、强健性、易实现的特点成为工业控制系统的通信标准。 包…...

label-studio的使用教程(导入本地路径)

文章目录 1. 准备环境2. 脚本启动2.1 Windows2.2 Linux 3. 安装label-studio机器学习后端3.1 pip安装(推荐)3.2 GitHub仓库安装 4. 后端配置4.1 yolo环境4.2 引入后端模型4.3 修改脚本4.4 启动后端 5. 标注工程5.1 创建工程5.2 配置图片路径5.3 配置工程类型标签5.4 配置模型5.…...

java调用dll出现unsatisfiedLinkError以及JNA和JNI的区别

UnsatisfiedLinkError 在对接硬件设备中&#xff0c;我们会遇到使用 java 调用 dll文件 的情况&#xff0c;此时大概率出现UnsatisfiedLinkError链接错误&#xff0c;原因可能有如下几种 类名错误包名错误方法名参数错误使用 JNI 协议调用&#xff0c;结果 dll 未实现 JNI 协…...

关于iview组件中使用 table , 绑定序号分页后序号从1开始的解决方案

问题描述&#xff1a;iview使用table 中type: "index",分页之后 &#xff0c;索引还是从1开始&#xff0c;试过绑定后台返回数据的id, 这种方法可行&#xff0c;就是后台返回数据的每个页面id都不完全是按照从1开始的升序&#xff0c;因此百度了下&#xff0c;找到了…...

2.Vue编写一个app

1.src中重要的组成 1.1main.ts // 引入createApp用于创建应用 import { createApp } from "vue"; // 引用App根组件 import App from ./App.vue;createApp(App).mount(#app)1.2 App.vue 其中要写三种标签 <template> <!--html--> </template>…...

[ICLR 2022]How Much Can CLIP Benefit Vision-and-Language Tasks?

论文网址&#xff1a;pdf 英文是纯手打的&#xff01;论文原文的summarizing and paraphrasing。可能会出现难以避免的拼写错误和语法错误&#xff0c;若有发现欢迎评论指正&#xff01;文章偏向于笔记&#xff0c;谨慎食用 目录 1. 心得 2. 论文逐段精读 2.1. Abstract 2…...

MVC 数据库

MVC 数据库 引言 在软件开发领域,Model-View-Controller(MVC)是一种流行的软件架构模式,它将应用程序分为三个核心组件:模型(Model)、视图(View)和控制器(Controller)。这种模式有助于提高代码的可维护性和可扩展性。本文将深入探讨MVC架构与数据库之间的关系,以…...

1.3 VSCode安装与环境配置

进入网址Visual Studio Code - Code Editing. Redefined下载.deb文件&#xff0c;然后打开终端&#xff0c;进入下载文件夹&#xff0c;键入命令 sudo dpkg -i code_1.100.3-1748872405_amd64.deb 在终端键入命令code即启动vscode 需要安装插件列表 1.Chinese简化 2.ros …...

视觉slam十四讲实践部分记录——ch2、ch3

ch2 一、使用g++编译.cpp为可执行文件并运行(P30) g++ helloSLAM.cpp ./a.out运行 二、使用cmake编译 mkdir build cd build cmake .. makeCMakeCache.txt 文件仍然指向旧的目录。这表明在源代码目录中可能还存在旧的 CMakeCache.txt 文件,或者在构建过程中仍然引用了旧的路…...

现有的 Redis 分布式锁库(如 Redisson)提供了哪些便利?

现有的 Redis 分布式锁库&#xff08;如 Redisson&#xff09;相比于开发者自己基于 Redis 命令&#xff08;如 SETNX, EXPIRE, DEL&#xff09;手动实现分布式锁&#xff0c;提供了巨大的便利性和健壮性。主要体现在以下几个方面&#xff1a; 原子性保证 (Atomicity)&#xff…...