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

RT-Thread MQTT(学习)

MQTT背景应用

MQTT是机器对机器(M2M)/物联网(IoT)连接协议,英文全名为“Message Queuing Telemetry Transport”,“消息队列遥测传输”协议。它是专为受限设备和低带宽、高延迟或不可靠的网络而设计的,是一种基于发布/订阅(publish/subscribe)模式的“轻量级”通信协议,该协议构建于TCP/IP协议之上,由IBM在1999年发布。

在这里插入图片描述
名词释义:

  • Publisher:发布者
  • Broker:代理(服务端)
  • Subscriber:订阅者
  • Topic:发布/订阅的主题

流程概述:上图中,各类传感器的角色是发布者(Publisher)。譬如,湿度传感器和温度传感器分别向接入的MQTT Broker中(周期性)发布两个主题名为“Moisture”(湿度)和“Temp”(温度)的主题;伴随着这两个主题共同发布的,还有湿度值和温度值,被称为“消息”。几个客户端的角色是订阅者SubScriber,如手机APP从Broker订阅了“Temp”主题,便能在手机上获取到温度传感器Publish在Broker中的温度值。

发布者和订阅者的角色并非是固定的,而是相对的。
发布者可以同时从Broker订阅主题,同理,订阅者也可以向Broker发布主题;即发布者可以是订阅者,订阅者也可以是发布者。

Broker可以是在线的云服务器,也可以是本地搭建的局域网客户端。
按照需求,实际上Broker自身也会保护一些订阅/发布主题的功能。

MQTT报文结构

任何通用/私有协议都是由事先规定好的、按某种规则约束的各种报文数据包组成的,MQTT也不例外。
在MQTT协议中,所有的数据包都是由最多三部分组成:固定Header+可变Header+有效载荷

在这里插入图片描述
固定Header是必需的,可变Header和有效载荷是非必需的。
因此,理论上来说,MQTT协议数据包的最小长度为2个字节,造就了它身边占用的额外资源消耗最小化特色。

Fixed header
在这里插入图片描述
固定Header由至少两个字节组成,如表2-2所示。
第一个字节的高4位描述了当前数据报文的类型,低四位定义了与报文类型相关的标志位。
第二个及之后的至多4个字节代表着剩余数据的字节长度。
在这里插入图片描述
Remaining Length剩余长度表示当前报文剩余部分的字节数,包括可变header和有效载荷的数据。

uMQTT的实现

uMQTT软件包是RT-Thread自主研发的,基于MQTT3.1.1协议的客户端实现,它提供了设备与MQTT Broker通讯的基本功能。

uMQTT软件包功能如下:

  • 实现基础的连接、订阅、发布功能;
  • 具备多重心跳保活,设备重连机制,保证mqtt在线状态,适应复杂情况;
  • 支持Qos=0,QoS=1,QoS=2三种发送信息质量;
  • 支持多客户端使用;
  • 用户端接口简便,留有多种对外回调函数;
  • 支持多种技术参数可配置,易上手,便于产品化开发;
  • 功能强大,资源占用率低,支持功能可裁剪。

uMQTT的结构框架

uMQTT软件包主要用于在嵌入式设备上实现MQTT协议,软件包的主要工作基于MQTT协议实现。
在这里插入图片描述
软件包实现过程中主要做了:

  1. 根据MQTT3.1.1协议规定,进行软件包数据协议的封包解包。
  2. 传输层函数适配对接SAL(Socket Abstraction Layer)层。
  3. uMQTT客户端层,根据协议包层和传输层编写符合应用层的接口。实现基础连接、断连、订阅、取消订阅、发布消息等功能。支持qoS0/1/2三种发送信息质量。利用uplink timer定时器,实现多重心跳保活机制和设备重连机制,增加设备在线稳定性,适应复杂情况。

uMQTT客户端

想要连接Broker,嵌入式设备需要作为MQTT协议中的客户端来使用。
在uMQTT组件的umqtt.h文件中,抽象出了初始化客户端用到的MQTT配置信息,组成对应的数据结构体。

struct umqtt_info
{rt_size_t send_size,recv_size; //发送接收缓冲区大小const char *uri; //完整的URI(包含URI+URN)const char *client_id; //客户端IDconst char *lwt_topic; //遗嘱主题const char *lwt_message; //遗嘱消息const char *user_name; //用户名const char *password; //密码enum umqtt_qos lwt_qos; //遗嘱QoSumqtt_subsribe_cb lwt_cb; //遗嘱回调函数rt_uint8_t reconnect_max_num; //最大重连次数rt_uint32_t reconnect_interval; //最大重连时间间隔rt_uint8_t keepalive_max_num;                       /* 最大保活次数 */ rt_uint32_t keepalive_interval;                     /* 最大保活时间间隔 */ rt_uint32_t recv_time_ms;                           /* 接收超时时间 */ rt_uint32_t connect_time;                           /* 连接超时时间 */ rt_uint32_t send_timeout;                           /* 上行(发布/订阅/取消订阅)超时时间 */ rt_uint32_t thread_stack_size;                      /* 线程栈大小 */ rt_uint8_t thread_priority;                         /* 线程优先级 */ 
#ifdef PKG_UMQTT_TEST_SHORT_KEEPALIVE_TIMErt_uint16_t connect_keepalive_sec;                  /* 连接信息,保活秒数 */    
#endif
}

这些配置信息一般在创建uMQTT客户端之前需要自行填写指定,譬如Broker的“URI”、“用户名”或“密码”之类的关键信息。
其它的非关键信息,如果没有指定,那么会在创建客户端函数umqtt_create中,调用umqtt_check_def_info函数来赋值为默认值。

static void umqtt_check_def_info(struct umqtt_info *info)
{if(info){if (info->send_size == 0) { info->send_size = PKG_UMQTT_INFO_DEF_SENDSIZE; }if (info->recv_size == 0) { info->recv_size = PKG_UMQTT_INFO_DEF_RECVSIZE; }if (info->reconnect_max_num == 0) { info->reconnect_max_num = PKG_UMQTT_INFO_DEF_RECONNECT_MAX_NUM; }if (info->reconnect_interval == 0) { info->reconnect_interval = PKG_UMQTT_INFO_DEF_RECONNECT_INTERVAL; }if (info->keepalive_max_num == 0) { info->keepalive_max_num = PKG_UMQTT_INFO_DEF_KEEPALIVE_MAX_NUM; }if (info->keepalive_interval == 0) { info->keepalive_interval = PKG_UMQTT_INFO_DEF_HEARTBEAT_INTERVAL; }if (info->connect_time == 0) { info->connect_time = PKG_UMQTT_INFO_DEF_CONNECT_TIMEOUT; }if (info->recv_time_ms == 0) { info->recv_time_ms = PKG_UMQTT_INFO_DEF_RECV_TIMEOUT_MS; }if (info->send_timeout == 0) { info->send_timeout = PKG_UMQTT_INFO_DEF_SEND_TIMEOUT; }if (info->thread_stack_size == 0) { info->thread_stack_size = PKG_UMQTT_INFO_DEF_THREAD_STACK_SIZE; }if (info->thread_priority == 0) { info->thread_priority = PKG_UMQTT_INFO_DEF_THREAD_PRIORITY; }}
}

然而只有上述信息,是无法运行起来一个MQTT客户端的。
故在umqtt.c中,含有umqtt_info的umqtt_client结构体列出了初始化客户端用到的所有数据:

struct umqtt_client
{int sock;                                                   /* 套接字 */ enum umqtt_client_state connect_state;                      /* mqtt客户端状态 */ struct umqtt_info mqtt_info;                                /* mqtt用户配置信息 */ rt_uint8_t reconnect_count;                                 /* mqtt客户端重连计数 */ rt_uint8_t keepalive_count;                                 /* mqtt保活计数 */ rt_uint32_t pingreq_last_tick;                              /* mqtt的PING请求上一次滴答值 */rt_uint32_t uplink_next_tick;                               /* 上行连接的下一次滴答值 */ rt_uint32_t uplink_last_tick;                               /* 上行连接的上一次滴答值 */ rt_uint32_t reconnect_next_tick;                            /* 客户端断开重连时的下一次滴答值 */ rt_uint32_t reconnect_last_tick;                            /* 客户端断开重连时的上一次滴答值 */ rt_uint8_t *send_buf, *recv_buf;                            /* 收发缓冲区指针 */ rt_size_t send_len, recv_len;                               /* 收发数据的长度 */ rt_uint16_t packet_id;                                      /* mqtt报文标识符 */ rt_mutex_t lock_client;                                     /* mqtt客户端互斥锁 */ rt_mq_t msg_queue;                                          /* mqtt客户端消息队列 */ rt_timer_t uplink_timer;                                    /* mqtt保活重连定时器 */ int sub_recv_list_len;                                      /* 接收订阅信息的链表长度 */ rt_list_t sub_recv_list;                                    /* 订阅消息的链表头 */ rt_list_t qos2_msg_list;                                    /* QoS2的消息链表 */struct umqtt_pubrec_msg pubrec_msg[PKG_UMQTT_QOS2_QUE_MAX]; /* 发布收到消息数组(QoS=2) */                   umqtt_user_callback user_handler;                           /* 用户句柄 */ void *user_data;                                            /* 用户数据 */ rt_thread_t task_handle;                                    /* umqtt任务线程 */ rt_list_t list;                                             /* umqtt链表头 */ 
};

部分成员的结构体和枚举类型定义,可自行在umqtt.h文件中查看。
该结构体会在创建客户端函数umqtt_create中,调用umqtt_check_def_info函数之后初始化:

  1. 初始化遗嘱数据结构(如果有的话)
  2. 为收发缓冲区申请内存
  3. 创建互斥锁、消息队列和超时重连定时器(超时回调实现重连+保活)
  4. 初始化各链表
  5. 创建umqtt_thread–mqtt数据收发线程
  6. 返回mqtt_client结构体

当第6步返回的值不为空时,即可调用umqtt_start函数来通过LWIP发送CONNECT报文连接Broker,连接成功后便会启动umqtt_thread线程,开启MQTT的通信。

LWIP

LWIP(轻量级IP)是一种开源、轻量且高效的Internet协议(IP)套件实现。
它主要设计用于嵌入式系统,并经常用于具有有限资源的小型到中型设备,如微控制器、物联网设备和实时操作系统。
LWIP是用C编程语言编写的,旨在为资源受限的环境提供网络功能。

LWIP的主要特点和特性包括:

  1. 小巧的占用空间:LWIP旨在具有最小的内存和代码大小占用,适用于资源受限的设备。
  2. 支持常见网络协议:LWIP支持标准的网络协议,如IPv4、IPv6、TCP(传输控制协议)、UDP(用户数据报协议)、ICMP(Internet控制消息协议)等。
  3. 可移植性:LWIP非常可移植,可以轻松适应各种嵌入式平台和操作系统。
  4. 与实时操作系统(RTOS)集成:LWIP通常与FreeRTOS等RTOS系统一起使用,适用于实时和多任务应用。
  5. BSD套接字API,使熟悉标准套接字编程的开发人员能够更轻松地使用该库。
  6. 可扩展性:LWIP设计为可扩展性,允许开发人员根据需要为其特定应用程序添加自定义功能或协议。
  7. 开源:LWIP以开源许可证分发(通常是MIT许可证),允许免费使用。

uMQTT与LWIP

在umqtt_start函数中,首先会将uMQTT客户端的状态置为UMQTT_CS_LINKING,表示正在连接中,接下来会调用umqtt_connect函数,将本地客户端连接到Broker。

连接到Broker的过程分两步:

  1. 创建套接字,与Broker建立链路连接。
  2. 发送CONNECT报文,创建MQTT协议连接。

在umqtt_connect函数中,通过调用umqtt_trans_connect函数,来完成第一步:

int umqtt_trans_connect(const char *uri, int *sock)
{int _ret = 0;struct addrinfo *addr_res = RT_NULL;*sock = -1;// 域名解析_ret = umqtt_resolve_uri(uri, &addr_res);if((_ret < 0) || (addr_res == RT_NULL)){LOD_E("resolve uri err");_ret = UMQTT_FAILED;goto exit;}//创建套接字if((*sock = socket(addr_res->ai_family, SOCK_STREAM, UMQTT_SOCKET_PROTOCOL))< 0 ){LOG_E("create socket error!");_ret = UMQTT_FAILED;goto exit;}//设置套接字工作在非阻塞模式下_ret = ioctlsocket(*sock, FIONBIO, 0);if (_ret < 0) {LOG_E(" iocontrol socket error!");_ret = UMQTT_FAILED;goto exit;}// 建立连接if( (_ret = connect(*sock, addr_res->ai_addr, addr_res->ai_addrlen)) < 0){LOG_E(" connect err!");closesocket(*sock);*sock = -1;_ret = UMQTT_FAILED;goto exit;}exit:if (addr_res) {freeaddrinfo(addr_res);addr_res = RT_NULL;}return _ret;
}

这个函数,就是uMQTT通过LWIP与Broker建立连接的核心函数。
该函数是通过SAL即套接字抽象层组件,来调用相关接口访问LWIP的。
用到的部分SAL组件封装的函数(getaddrinfo是在umqtt_resolve_uri函数中用来解析域名的)

int getaddrinfo(const char *nodename,const char *servname,const struct addrinfo *hints,struct addrinfo **res)
{return sal_getaddrinfo(nodename, servname, hints, res);
}
---------------------------------------------------------------------------------------------
#define connect(s, name, namelen)                          sal_connect(s, name, namelen)
#define recvfrom(s, mem, len, flags, from, fromlen)        sal_recvfrom(s, mem, len, flags, from, fromlen)
#define send(s, dataptr, size, flags)                      sal_sendto(s, dataptr, size, flags, NULL, NULL)
#define socket(domain, type, protocol)                     sal_socket(domain, type, protocol)
#define closesocket(s)                                     sal_closesocket(s)
#define ioctlsocket(s, cmd, arg)                           sal_ioctlsocket(s, cmd, arg)

uMQTT发送组包

当uMQTT客户端与Broker成功建立链路层连接后,就会立刻发送CONNECT报文,建立MQTT的协议层连接。

uMQTT组件使用了巧妙的结构体+共用体来管理所有的收发报文:

union umqtt_pkgs_msg                                /* mqtt message packet type */ 
{struct umqtt_pkgs_connect     connect;          /* connect */ struct umqtt_pkgs_connack     connack;          /* connack */ struct umqtt_pkgs_publish     publish;          /* publish */ struct umqtt_pkgs_puback      puback;           /* puback */ struct umqtt_pkgs_pubrec      pubrec;           /* publish receive (QoS 2, step_1st) */ struct umqtt_pkgs_pubrel      pubrel;           /* publish release (QoS 2, step_2nd) */ struct umqtt_pkgs_pubcomp     pubcomp;          /* publish complete (QoS 2, step_3rd) */ struct umqtt_pkgs_subscribe   subscribe;        /* subscribe topic */ struct umqtt_pkgs_suback      suback;           /* subscribe ack */ struct umqtt_pkgs_unsubscribe unsubscribe;      /* unsubscribe topic */ struct umqtt_pkgs_unsuback    unsuback;         /* unsubscribe ack */ 
};struct umqtt_msg
{union umqtt_pkgs_fix_header header;             /* fix header */ rt_uint32_t msg_len;                            /* message length */ union umqtt_pkgs_msg msg;                       /* retain payload message */ 
};

union umqtt_pkgs_msg是一个联合体,包含了多种不同类型的MQTT消息包。
每个成员都对应一种MQTT消息类型,如connect、connack、publish等。
这个联合体的作用是可以容纳不同种类的MQTT消息包,但在任何给定时刻只能包含一个有效的消息包。使得在处理MQTT消息时,可以有效地共享内存空间,减小内存占用。

struct umqtt_msg包含了三个成员:

  • header:是一个联合体’umqtt_pkgs_fix_header’,表示MQTT消息的固定头部,它包含了消息的控制标志和其他必要的元数据。
  • msg_len:是一个32位整数,表示MQTT消息的长度。
  • msg:是一个联合体,用于存储具体类型的MQTT消息包。通过header中的控制标志,可以确定在’msg’中使用哪个成员。

通过umqtt_encode函数来调用不同的组包函数,填充对应格式的结构体,然后发送到Broker服务端。

/** * packaging the data according to the format** @param type the input packaging type* @param send_buf the output send buf, result of the package* @param send_len the output send buffer length* @param message the input message** @return <=0: failed or other error*         >0: package data length*/
int umqtt_encode(enum umqtt_type type, rt_uint8_t *send_buf, size_t send_len, struct umqtt_msg *message)
{int _ret = 0;switch (type){case UMQTT_TYPE_CONNECT:_ret = umqtt_connect_encode(send_buf, send_len, &(message->msg.connect));break;case UMQTT_TYPE_PUBLISH:_ret = umqtt_publish_encode(send_buf, send_len, message->header.bits.dup, message->header.bits.qos, &(message->msg.publish));break;case UMQTT_TYPE_PUBACK:_ret = umqtt_puback_encode(send_buf, send_len, message->msg.puback.packet_id);break;case UMQTT_TYPE_PUBREC:// _ret = umqtt_pubrec_encode();break;case UMQTT_TYPE_PUBREL:_ret = umqtt_pubrel_encode(send_buf, send_len, message->header.bits.dup, message->msg.pubrel.packet_id);break;case UMQTT_TYPE_PUBCOMP:_ret = umqtt_pubcomp_encode(send_buf, send_len, message->msg.pubcomp.packet_id);break;case UMQTT_TYPE_SUBSCRIBE:_ret = umqtt_subscribe_encode(send_buf, send_len, &(message->msg.subscribe));break;case UMQTT_TYPE_UNSUBSCRIBE:_ret = umqtt_unsubscribe_encode(send_buf, send_len, &(message->msg.unsubscribe));break;case UMQTT_TYPE_PINGREQ:_ret = umqtt_pingreq_encode(send_buf, send_len);break;case UMQTT_TYPE_DISCONNECT:_ret = umqtt_disconnect_encode(send_buf, send_len);break;default:break;}return _ret;
}

由于报文类型较多,接下来仅以“CONNECT”报文(可变header——“协议名称”、“协议等级”、“连接标志”、“保活间隔(秒),有效载荷——“客户端标识符”、“遗嘱主题”、“遗嘱消息”、“用户名”、“密码”)为例”),来简述uMQTT的组包过程:
1.填充MQTT客户端的默认配置信息
2.调用umqtt_encode -> umqtt_connect_encode编码函数组包:

该函数首先调用MQTTSerialize_connectLength计算可变header和有效载荷的长度,得到的len会被作为参数传递给umqtt_pkgs_len函数,它的作用是计算固定header中的剩余长度字段的字节数并加上固定header第一个字节长度即1,与buflen作比较,判断该包数据的有效性。

if (umqtt_pkgs_len(len = MQTTSerialize_connectLength(options)) > buflen)

希望得到的len长度就是固定header中的剩余长度值,从而方便后面的组包过程,而有效的报文长度buflen = len + 1+ 剩余长度字段的字节数。

组包过程完成之后,会调用umqtt_trans_send函数,通过LWIP将发送缓冲区数据发送到socket连接的Broker。

int umqtt_trans_send(int sock, const rt_uint8_t *send_buf, rt_uint32_t buf_len, int timeout)
{int _ret = 0;rt_uint32_t offset = 0U;while(offset < buflen){_ret = send(sock, send_buf + offset, buf_len - offset, 0);if(_ret < 0)return -errno;offset += _ret;}return _ret;
}

uMQTT接收解包

当uMQTT将CONNECT报文发送完成后,就会调用umqtt_handle_readpacket函数(完成CONNECT过程后,该函数也会在umqtt_thread线程中被循环调用来接收数据)读取Broker的回复,对接收到的数据包进行解包处理:

  1. 读Fixed header的第一个字节,这里会调用umqtt_trans_recv函数读取socket数据,作用就是从对应的sock中读取buf_len长度的数据到recv_buf。
  2. 读Fixed header的Remaining length字段并解析剩余长度。
  3. 读剩余数据——可变header+有效载荷。
  4. 解析数据包,并根据不同报文类型做相应处理。
  5. UMQTT_TYPE_CONNACK:调用set_uplink_recon_tick(client, UPLINK_NEXT_TICK)函数设置下一次重连滴答值,调用set_connect_status(client, UMQTT_CS_LINKED)函数设置uMQTT客户端状态为已连接。

设置重连滴答值

设置下一次重连滴答值(reconnect tick)的目的是为了控制在失去连接后,尝试重新建立连接的时间间隔。

  1. 网络部稳定性:在物联网(IoT)或其他网络应用中,设备可能会面临不稳定的网络条件,如临时的断线或信号干扰。在这种情况下,失去连接后立即进行重连可能会导致频繁的连接尝试,造成网络资源浪费,增加设备的电力消耗。
  2. 避免过早的重连:设置重连滴答值可以防止设备过早地尝试重新连接。重连的时间间隔可以根据设备和网络的特定需求进行调整,以确保在网络稳定之前不会进行不必要的连接尝试。
  3. 节约资源: 预定重连滴答值有助于节约设备资源,因为设备不必持续监视网络状态并进行连接尝试。它可以在预定的时间间隔后尝试连接,以降低设备的功耗。
  4. 逐渐增加重连频率: 通常,设置下一次重连滴答值的方式是逐渐增加重连频率。这意味着如果第一次重连尝试失败,设备可能会等待一段较长的时间,然后在下一次尝试时等待较短的时间。这种策略允许设备在网络稳定性恢复时更频繁地尝试连接,同时避免了过于频繁的连接尝试。

总之,设置下一次重连滴答值是为了在网络不稳定或断开连接的情况下,以一种经济高效的方式管理设备的连接重试,以提高设备的性能和资源利用率。

至此,已经完成了CONNECT报文的收发过程,下一步就是启动umqtt_thread线程,调用umqtt_handle_readpacket函数来处理从Broker服务器端收到的数据报文。

在这里插入图片描述

相关文章:

RT-Thread MQTT(学习)

MQTT背景应用 MQTT是机器对机器&#xff08;M2M&#xff09;/物联网&#xff08;IoT&#xff09;连接协议&#xff0c;英文全名为“Message Queuing Telemetry Transport”&#xff0c;“消息队列遥测传输”协议。它是专为受限设备和低带宽、高延迟或不可靠的网络而设计的&…...

Vue_Bug VUE-ELEMENT-ADMIN默认是英文模式

Bug描述&#xff1a; VUE-ADMIN-TEMPLATE-MASTER VUE-ELEMENT-ADMIN-MASTER 两个项目直接从GitHub上拉取下来 默认是英文模式 其他信息&#xff1a; 这两个项目默认支持中文语言包&#xff0c;无需额外引入&#xff0c;只需删除英文语言包依赖 //import enLang from element-…...

Spark中的Driver、Executor、Stage、TaskSet、DAGScheduler等介绍

工作流程&#xff1a; Driver 创建 SparkSession 并将应用程序转化为执行计划&#xff0c;将作业划分为多个 Stage&#xff0c;并创建相应的 TaskSet。Driver 将 TaskSet 发送给 TaskScheduler 进行调度和执行。TaskScheduler 根据资源情况将任务分发给可用的 Executor 进程执…...

docker的资源限制参数设置错误,导致的clickhouse性能瓶颈

使用场景 我们使用docker作为服务的虚拟化工具&#xff0c;服务都部署在docker里我们使用docker-compose管理所有docker服务的配置文件针对某些服务&#xff0c;我们要限制这个docker占用的资源数量&#xff0c;例如&#xff0c;cpu和内存在进行配置时&#xff0c;网上搜了一些…...

Vue路由守卫有哪些,怎么设置,有哪些使用场景?

Vue 路由守卫是在 Vue Router 中提供的一种功能&#xff0c;它允许您在导航到某个路由前、路由变化时或导航离开某个路由时执行代码。Vue 路由守卫提供了以下几种类型&#xff1a; 1.全局前置守卫 router.beforeEach 在进入路由前执行的钩子函数&#xff0c;它会接收三个参数&a…...

云原生网关可观测性综合实践

作者&#xff1a;钰诚 可观测性 可观测性&#xff08;Observability&#xff09;是指系统、应用程序或服务的运行状态、性能和行为能够被有效地监测、理解和调试的能力。 随着系统架构从单体架构到集群架构再到微服务架构的演进&#xff0c;业务越来越庞大&#xff0c;也越来…...

vue-element-admin—登录页面添加自定义背景

一、效果图 初始效果&#xff1a; 更改背景后效果&#xff1a; 二、操作步骤 1、准备图片 2、更改代码 打开下面路径的 index.vue 文件&#xff1a; vue-element-admin-master\src\views\login\index.vue 也就是登录页面。 对 .login-container 样式代码块内代码做如下…...

软设上午题-错题知识点一

软设上午题-错题知识点一 1、ipconfig 显示信息&#xff1b; ipconfig /all 显示详细信息 &#xff0c;可查看DHCP服务是否已启用&#xff1b; ipconfig /renew 更新所有适配器&#xff1b; ipconfig /release 释放所有匹配的连接。 2、耦合性也叫块间联系。指软件系统结构中各…...

微信小程序(小程序入门)

一&#xff0c;介绍 1、什么是小程序 小程序是一种轻量级的应用程序&#xff0c;可以在移动设备上运行&#xff0c;不需要用户下载和安装。它们通常由企业或开发者开发&#xff0c;用于提供特定功能或服务。 微信小程序&#xff08;wei xin xiao cheng xu&#xff09;&#xf…...

虹科分享 | 想买车无忧?AR为您带来全新体验!

新能源汽车的蓬勃发展&#xff0c;推动着汽车行业加速进行数字化变革。据数据显示&#xff0c;全球新能源汽车销售额持续上升&#xff0c;预计到2025年&#xff0c;新能源汽车市场规模将达到约 4200亿美元&#xff0c;年复合增长率超过 30%。这表明消费者对清洁能源出行的需求不…...

easyUI重新渲染

问题 使用Easyui 时&#xff0c;动态后添加的元素样式无法生效。 解决颁发 全页面重新渲染 $.parser.parse();单一元素重新渲染 var obj $("#div1").append("<input classeasyui-textbox typetext>"); $.parser.parse(obj);...

html和css基础练习

vscode快捷键 alt b 在浏览器中打开 alt shift b 在其他浏览器打开 ctrl / 注释 ctrl y 快捷键删除 参考文章 https://www.bilibili.com/video/BV1m84y1w7Tb 基础html标签 img&#xff1a;图像&#xff0c;title&#xff1a;头部文字&#xff0c;body&#xff1a;主…...

Linux信号 signal()编程

在Linux的进程间通信中可以用signal&#xff08;&#xff09;函数进行信号与信息传递。 1.信号 信号的名字和编号&#xff1a; 每个信号都有一个名字和编号&#xff0c;这些名字都以“SIG”开头&#xff0c;例如“SIGIO ”、“SIGCHLD”等等。 信号定义在signal.h头文件中&am…...

【LeetCode】16.最接近的三数之和

1 问题 给你一个长度为 n 的整数数组 nums 和 一个目标值 target。请你从 nums 中选出三个整数&#xff0c;使它们的和与 target 最接近。 返回这三个数的和。 假定每组输入只存在恰好一个解。 示例 1&#xff1a; 输入&#xff1a;nums [-1,2,1,-4], target 1 输出&…...

嵌入式开发学习之STM32F407点亮LED及J-Link下载(二)

嵌入式开发学习之STM32F407点亮LED及J-Link下载&#xff08;二&#xff09; 开发涉及工具控制端口配置端口的设定与确认端口配置方法实现点亮LED程序下载与仿真 有工程实例&#xff0c;链接在最底部。 开发涉及工具 开发环境&#xff08;IDE&#xff09;&#xff1a;IAR-ARM8…...

智能呼叫中心系统的未来发展趋势:为企业开启全新服务模式

随着人工智能技术的不断发展&#xff0c;智能呼叫中心系统已经成为现代企业服务的重要组成部分。随着客户需求的不断升级&#xff0c;智能呼叫中心系统的未来发展趋势也受到了广泛关注。以下是一些关于未来发展趋势的观点和建议。 1、大数据和人工智能技术 未来的系统将更多地…...

UE5中实现沿样条线创建网格体2-SplineMesh版本

我在之前的一篇文章中写过沿样条线创建网格体的方法&#xff1a; https://blog.csdn.net/grayrail/article/details/130453733 但该方法没有网格变形操作&#xff0c;就会导致每一段网格对象是无法连接的&#xff1a; 后来发现了SplineMesh方法可以比较好的解决这个问题&…...

实现Element Select选择器滚动加载

<template><el-selectpopper-class"more-tag-data"v-model"tagId"filterableplaceholder"请选择"focus"focusTag"><el-optionv-for"(item, index) in taskTagLists":key"index":label"item.n…...

C++ 之 Vector 和 List

Vector vector 是C STL中最常用的容器&#xff0c;支持存储多种类型的数据。 与数组相比&#xff0c;它的大小是可变的&#xff0c;因此也会被称为动态数组。 使用它&#xff0c;需要包含头文件&#xff1a; #include <vector>定义的结构&#xff1a; vector<数据类…...

力扣-448.找到所有数组中消失的数字

Idea 模拟 class Solution { public:vector<int> findDisappearedNumbers(vector<int>& nums) {int n nums.size();vector<int> a(n 1, 0);for(int i : nums) a[i];vector<int> ans;for(int i 1; i < n; i) if(!a[i]) ans.emplace_back(i);r…...

常用gdb调试命令

常见gdb调试命令 命令名 命令缩写 命令说明 backtrace bt 查看函数调用堆栈 frame f 查看栈帧 list l 查看源码 print p 打印内部变量值 info i 查看程序状态 display disp 跟踪某变量,每次停下来则显示值 run r 开始运行程序 continue c 继续程序运行&#xff0c;直到下一个断…...

【动手学深度学习-Pytorch版】BERT预测系列——用于预测的BERT数据集

本小节的主要任务即是将wiki数据集转成BERT输入序列&#xff0c;具体的任务包括&#xff1a; 读取wiki数据集生成下一句预测任务的数据—>主要用于_get_nsp_data_from_paragraph函数从输入paragraph生成用于下一句预测的训练样本&#xff1a;_get_nsp_data_from_paragraph生…...

【数据结构-字符串 三】【栈的应用】字符串解码

废话不多说&#xff0c;喊一句号子鼓励自己&#xff1a;程序员永不失业&#xff0c;程序员走向架构&#xff01;本篇Blog的主题是【字符串转换】&#xff0c;使用【字符串】这个基本的数据结构来实现&#xff0c;这个高频题的站点是&#xff1a;CodeTop&#xff0c;筛选条件为&…...

Stm32_标准库_10_TIM_显示时间日期

利用TIM计数耗费1s,启动中断&#xff0c;秒表加一 时间显示代码&#xff1a; #include "stm32f10x.h" // Device header #include "Delay.h" #include "OLED.h"uint16_t num 0; TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure; NVIC_I…...

10-SRCNN-使用CNN实现超分辨成像

文章目录 utils_dataset.pymodel.pytrain.pyuse.py主要文件 utils_dataset.py 工具文件,主要用来制作dataset,便于加入dataloader,用于实现数据集的加载和并行读取 model.py 主要写入网络(模型) train.py 主要用于训练 use.py 加载训练好的模型,用于测试或使用 utils_dat…...

cmd/bat 输出符,控制台日志输出到文件

前言 略 输出符 A > B将A执行结果覆盖写入B A >> B将A执行结果追加写入B 常用句柄 句柄句柄的数字代号描述STDIN0键盘输入STDOUT1输出到命令提示符窗口STDERR2错误输出到命令提示符窗口 控制台日志输出到文件 1.bat 1>d:\log.log将控制台日志输出到文件 d:…...

ODrive移植keil(七)—— 插值算法和偏置校准

目录 一、角度读取1.1、硬件接线1.2、程序演示1.3、代码说明 二、锁相环和插值算法2.1、锁相环2.2、插值2.3、角度补偿 三、偏置校准3.1、硬件接线3.2、官方代码操作3.3、移植后的代码操作3.4、代码说明3.5、SimpleFOC的偏置校准对比 ODrive、VESC和SimpleFOC 教程链接汇总&…...

【肌电信号】OpenSignals使用方法 --- 肌电信号采集及导入matlab

一、 多通道采集教学 1. 数据线连接 将PLUX设备通过USB或蓝牙与电脑连接&#xff0c;注意确认在几号通道接线。 2.实时数据采集可视化 进行设置。需要在软件中选择你的PLUX设备&#xff0c;并配置相关的参数&#xff0c;如采样率、分辨率、信号类型等 3 支持数据回放和…...

STM32 多功能按键中断

key1 开关实现led1亮灭,key2开关实现蜂鸣器开关,key3开关实现风扇开关 main.c #include "uart.h" #include "key_it.h" #include "led.h" int main() {char c;char *s;uart4_init();//串口初始化all_led_init();key_it_config();fengshan_init…...

Linux-文件管理命令

绝对路径&#xff1a;从根目录开始描述的路径 pwd输入即为绝对路径&#xff0c; 开头一定是“/”&#xff0c;因为一定是从根目录开始走 相对路径&#xff1a;从当前路径开始描述的路径&#xff0c;开头不一定是“/”&#xff0c;因为不一定是从根目录开始走的 .:是当前目录 。…...

JS DataTable中导出PDF右侧列被截断的问题解决

JS DataTable中导出PDF右侧列被截断的问题解决 文章目录 JS DataTable中导出PDF右侧列被截断的问题解决一. 问题二. 解决办法三. 代码四. 参考资料 一. 问题 二. 解决办法 设置PDF大小和版型 orientation: landscape, pageSize: LEGAL,上述代码设置打印的PDF尺寸为LEGAL&…...

学习笔记-MongoDB(复制集,分片集集群搭建)

复制集群搭建 基本介绍 什么是复制集&#xff1f; 复制集是由一组拥有相同数据集的MongoDB实例做组成的集群。 复制集是一个集群&#xff0c;它是2台及2台以上的服务器组成&#xff0c;以及复制集成员包括Primary主节点&#xff0c;Secondary从节点和投票节点。 复制集提供了…...

Servlet与设计模式

1 过滤器和包装器 过滤器可以拦截请求及控制响应&#xff0c;而servlet对此毫无感知。过滤器有如下作用&#xff1a; 1&#xff09;请求过滤器&#xff1a;完成安全检查、重新格式化请求首部或体、建立请求审计日志。 2&#xff09;响应过滤器&#xff1a;压缩响应流、追加或…...

Python学习基础笔记六十五——布尔值

布尔对象&#xff1a; Python中有一种对象类型称之为布尔对象&#xff08;英文叫bool&#xff09;。 布尔对象只有两种取值&#xff0c;True和False。对应的是真和假&#xff0c;或者说是和否。True对应的是&#xff0c;False对应的是否。 我觉得这句话是一个关键&#xff1a…...

ChatGPT生产力|实用指令(prompt)

GPT已经成为一个不可或缺的科研生产力了&#xff0c;但是大多数人只知晓采用直接提问、持续追问以及细节展开的方式来查阅相关资料&#xff0c;本文侧重于探讨“限定场景限定角色限定主题”、“可持续追问细节展开”等多种方式来获取更多信息&#xff0c;帮人们解决更多问题。 …...

【大数据Hive】hive select 语法使用详解

目录 一、前言 二、Hive select 完整语法树 三、Hive select 操作演示 3.1 数据准备 3.1.1 创建一张表 3.1.2 将数据load加载到t_usa_covid19表 3.1.3 再创建一张分区表 3.1.4 使用动态分区插入数据 3.2 select 常用语法 3.2.1 查询所有字段或者指定字段 3.2.2 查询…...

Android---java线程优化 偏向锁、轻量级锁和重量级锁

java 中的线程是映射到操作系统原生线程之上的&#xff0c;如果要阻塞或唤醒一个线程就需要操作系统的帮忙&#xff0c;这就需要从用户态转换到核心态。状态转换需要花费很多时间&#xff0c;如下代码所示&#xff1a; private Object lock new Object();private int value;p…...

处理机调度

目录 处理机调度概述 处理机调度的层次 低级调度 中级调度 高级调度 进程调度 进程调度的时机 进程调度的方式 非抢占式调度方式 抢占式调度方式 调度算法的评价指标 调度算法 先来先服务调度算法&#xff08;FCFS&#xff0c;First Come First Serve&#xff09; …...

Webpack 解决:ReferenceError: dist is not defined 的问题

1、问题描述&#xff1a; 其一、报错为&#xff1a; ReferenceError: dist is not defined 中文为&#xff1a; ReferenceError&#xff1a;dist 未定义 其二、问题描述为&#xff1a; 想在 webpack 的配置中&#xff0c;创建一个 dist 文件夹来存放 npm run build 打包后…...

MySQL的index merge(索引合并)导致数据库死锁分析与解决方案 | 京东云技术团队

背景 在DBS-集群列表-更多-连接查询-死锁中&#xff0c;看到9月22日有数据库死锁日志&#xff0c;后排查发现是因为mysql的优化-index merge&#xff08;索引合并&#xff09;导致数据库死锁。 定义 index merge(索引合并)&#xff1a;该数据库查询优化的一种技术&#xff0…...

第四章 网络层 | 计算机网络(谢希仁 第八版)

文章目录 第四章 网络层4.1 网络层提供的两种服务4.2 网际协议IP4.2.1 虚拟互连网络4.2.2 分类的IP地址4.2.3 IP地址与硬件地址4.2.4 地址解析协议ARP4.2.5 IP数据报的格式4.2.6 IP层转发分组的流程 4.3 划分子网和构造超网4.3.1 划分子网4.3.2 使用子网时分组的转发4.3.3 无分…...

课题学习(八)----卡尔曼滤波动态求解倾角、方位角

一、 卡尔曼滤波 卡尔曼滤波的应用要求系统和底层过程的测量模型都是线性的。离散时间线性状态空间系统的描述为: x k Φ k , k − 1 x k − 1 G k − 1 w k − 1 x_k\Phi_{k,k-1}x_{k-1}G_{k-1}w_{k-1} xk​Φk,k−1​xk−1​Gk−1​wk−1​    式中 Φ k , k − 1 \Phi_{…...

仿真软件Proteus8.9 SP2 Pro 下载、安装、汉化详细图文教程

Proteus8.9 安装教程 视频教程一、安装软件解压二、软件安装常见问题及解决方法&#xff1a;三、汉化 Proteus8.9 SP2 Pro 安装教程 本破解教程仅供个人及 proteus 8.9粉丝们交流学习之用&#xff0c;请勿用于商业用途&#xff0c; 谢谢支持。此版本为Proteus8.9 SP2 Pro。其他…...

振弦传感器和无线振弦采集仪在隧道安全监测的解决方案

振弦传感器和无线振弦采集仪在隧道安全监测的解决方案 隧道作为交通工程的重要组成部分&#xff0c;具有极高的安全风险&#xff0c;因此隧道安全监测是必不可少的。振弦传感器和无线振弦采集仪作为隧道安全监测的两种重要设备&#xff0c;能够有效地监测隧道的振动情况&#…...

c# xml 参数读取的复杂使用

完整使用2 生产厂家里面包含很多规格型号,一个规格型号里面包含很多出厂序列号,点击下一步如果检测到填充的和保存的不一样 就新增一条(如检测到生产厂家相同,但是规格型号不同,就新增一组规格型号)。 界面一:新增界面 界面2 删除界面 界面一:新增界面 load 其中…...

在Mac中使用 brew services start redis 命令启动、停止Redis服务报错

一、问题现象 启动Redis服务命令&#xff1a; brew services start redis异常信息如下&#xff1a; Error: uninitialized constant Homebrew::Service::System /opt/homebrew/Library/Homebrew/macos_version.rb:150:in const_missing /opt/homebrew/Library/Taps/homebrew…...

iapp源码-----比较经典

2.0底部菜单导航栏.rar: https://url18.ctfile.com/f/7715018-958700751-6096bd?p6511 (访问密码: 6511) 2.0涟漪_拖动条控制音乐播放.rar: https://url18.ctfile.com/f/7715018-958700754-4cec13?p6511 (访问密码: 6511) 2.0手电筒.rar: https://url18.ctfile.com/f/7715018…...

为什么手机会莫名多出许多软件?

许多手机用户都曾遭遇过这样的问题&#xff0c;他们在使用手机的过程中&#xff0c;突然发现手机屏幕上出现了一些未知的软件。这些软件并非他们主动下载的&#xff0c;但它们却显现在屏幕上。这些软件从何而来&#xff1f; 其实&#xff0c;这些软件往往是在浏览网页、阅读小…...

测试自动化的边缘:DevTestOps 和 DevSecOps

什么是 DevOps&#xff1f; DevOps 允许企业通过自动化基础设施、工作流程和持续测量应用程序的性能来提高开发人员和运营团队之间的协作和生产力。通过 DevOps&#xff0c;开发人员可以以小块的形式编写代码&#xff0c;以便在几个小时内集成、测试、监控和部署代码&#xff…...

fatal:Could not read from remote repository解决方法

Linux服务器如何连接GitHub&#xff1f; 生成SSH密钥 ssh-keygen -C “邮箱” -t rsa 存放位置一般是/root/.ssh/id_rsa 登录个人github&#xff0c;添加客户端生成的公钥 打开Settings&#xff0c;点击SSH and GPG keys&#xff0c;点击New SSH Key。Key中粘贴id_rsa.pub…...