C语言模拟QT的信号与槽功能

news2024/11/28 23:48:43

文章目录

  • 前言
  • 一、Qt信号与槽的实现机理
  • 二、简化后的实现步骤
    • 1. 定义一些必要的宏
    • 2. 实现声明信号的宏
    • 3. 实现发射信号的宏
    • 4. 取代QObject类
    • 5. 实现connect函数
    • 6. 可有可无的slots
  • 三、完整的代码实现
  • 四、使用方法与QT中的区别
    • 1. SIG_SLOT_OBJ取代QObject
    • 2. 定义信号不同
    • 3. 发射信号不同
    • 4. 定义槽不同
    • 5. 连接信号与槽
  • 五、信号与槽使用示例


前言

使用过QT的朋友,应该都对QT的信号与槽机制深有体会,它可以非常方便的实现类与类之间的解耦合、实现对象与对象之间的解耦合、实现两个cpp文件之间的解耦合。

既然信号槽如此好用,嵌入式开发也想拥有,下边就开始用C语言一步步实现它吧…


一、Qt信号与槽的实现机理

在Qt中实现信号与槽最重要的就是通过元对象系统(MOS)的元对象编译器(MOC)将我们定义的需要使用到信号与槽的类中的信号及信号调用槽函数的方法进行定义(这一步就会生成与源文件对应的moc_xx.cpp文件),然后通过系统提供的关联方法(connect)将信号与槽建立一一对应关系,当发射信号(其实就是调用信号函数)时就会通过信号与槽的对应关系找到对应槽函数进行调用。

这样的好处就是对于使用者而言不必去关心函数指针回调函数这些对于初学者比较不太容易搞清晰的东西,简化了使用者的操作。当然就像我们在享受幸福生活的时候,就一定有人在我们背后默默付出砥砺前行!这里也一样,对于我们使用者简化了操作,那为了实现这样的效果就需要在后台提供更多的支持。

QT Creator官方帮助文档对信号槽使用方法做了详细的介绍,接下来我们就依照官方的使用方法,依葫芦画瓢,用C语言的宏模拟出山寨版的信号和槽。

在这里插入图片描述

二、简化后的实现步骤

1. 定义一些必要的宏

先无脑定义一些与QT中一模一样的宏,然后再思考如何实现它的功能。


#define signals //定义信号

#define emit   //发射信号

#define slots  //定义槽

#define connect //链接信号与槽

#define SIGNAL(x) 

#define SLOT(x) 


信号槽的核心机制是当发射信号时会通过信号与槽的对应关系找到对应槽函数进行调用。
我们所要模拟的正是这个核心机制。任务明确了,就开始实现这些宏吧!

2. 实现声明信号的宏

QT中定义信号是在类中使用signals声明一个函数,不需要自己实现信号函数,在生成的moc文件中,代替你实现了你声明的信号函数,所以发射信号的本质就是通过调用信号函数,再调用槽函数

既然调用发射信号,就是调用槽函数,那么理论上,只需要把槽函数的地址赋值给一个发射信号的函数指针,就完成偷梁换柱了。

想要定义函数指针,首先需要知道函数类型,那么使用signals宏声明信号,就可以用来声明一个函数类型,然后用这个函数类型,去定义函数指针。

实现定义信号的宏:

#define signals(__NAME,...)               \
          typedef void __NAME( __VA_ARGS__);

3. 实现发射信号的宏

发射信号就是,利用声明好的函数类型,定义一个函数指针,然后把槽函数地址赋给这个函数指针,然后执行这个函数指针的一段代码:

#define emit(__NAME,__OBJ,...)                     \
           do{                                          \
               sig_slot_t *ptObj = &((__OBJ)->tObject);                \
		       __NAME *__RecFun = ptObj->ptRecFun;   \
		      执行__RecFun((__OBJ)->ptRecObj,__ARG1,__ARG2, __ARG3,...) \
           }while(0)              \

其中__NAME是信号名称,__OBJ是信号所在对象的地址,…是可变参数。
这里重点来了,由于执行__RecFun这个函数的时候,我们并不知道有多少个参数,所以无法直接如上边代码那样带入参数,我们必须知道…代表的是几个参数,那么就可以知道使用几个__ARG了。

这个时候,如果看过上篇文章C语言变参函数和可变参数宏,应该就能立马想到我们其实已经实现了一个可以获得可变参数宏中参数数量的宏了:#define VA_NUM_ARGS(...) ,利用它,宏的重载就可以信手捏来了。先来它8个参数的,应该够用了

#define __RecFun_0(__OBJ)                                     \
            __RecFun((__OBJ))

#define __RecFun_1(__OBJ, __ARG1)                         \
            __RecFun((__OBJ),(__ARG1))

#define __RecFun_2(__OBJ, __ARG1, __ARG2)                         \
            __RecFun((__OBJ),(__ARG1), (__ARG2))

#define __RecFun_3(__OBJ, __ARG1, __ARG2, __ARG3)                 \
            __RecFun((__OBJ),(__ARG1), (__ARG2), (__ARG3))                         

#define __RecFun_4(__OBJ, __ARG1, __ARG2, __ARG3, __ARG4)                 \
            __RecFun((__OBJ),(__ARG1), (__ARG2), (__ARG3), (__ARG4))    
            
#define __RecFun_5(__OBJ, __ARG1, __ARG2, __ARG3, __ARG4, __ARG5)                 \
            __RecFun((__OBJ),(__ARG1), (__ARG2), (__ARG3), (__ARG4), (__ARG5))  

#define __RecFun_6(__OBJ, __ARG1, __ARG2, __ARG3, __ARG4, __ARG5, __ARG6)                 \
            __RecFun((__OBJ),(__ARG1), (__ARG2), (__ARG3), (__ARG4), (__ARG5), (__ARG6)) 

#define __RecFun_7(__OBJ, __ARG1, __ARG2, __ARG3, __ARG4, __ARG5, __ARG6, __ARG7)                 \
            __RecFun((__OBJ),(__ARG1), (__ARG2), (__ARG3), (__ARG4), (__ARG5), (__ARG6), (__ARG7)) 

#define __RecFun_8(__OBJ, __ARG1, __ARG2, __ARG3, __ARG4, __ARG5, __ARG6, __ARG7, __ARG8)                 \
            __RecFun((__OBJ),(__ARG1), (__ARG2), (__ARG3), (__ARG4), (__ARG5), (__ARG6), (__ARG7), (__ARG8)) 
 

直接使用开源的面向对象模块PLOOC中已经封装好的__PLOOC_EVAL,来自动选择正确的函数即可,重新实现emit

#define __emit(__OBJ,...) \
   		   __PLOOC_EVAL(__RecFun_,##__VA_ARGS__)              \
                   ((__OBJ)->ptRecObj,##__VA_ARGS__); 

#define emit(__NAME,__OBJ,...)                     \
           do{                                          \
               sig_slot_t *ptObj = &((__OBJ)->tObject);                \
		       __NAME *__RecFun = ptObj->ptRecFun;   \
		      __emit(ptObj, __VA_ARGS__) \
           }while(0)  
           

这样发射信号的时候,执行的就是槽函数了,槽函数的第一个参数始终为槽所在的对象地址,从而就完成了对象与对象之间的解耦。

4. 取代QObject类

实现发射信号的宏时候,我们需要知道槽函数的地址,和槽函数所在对象的地址,我们定义一个类:

typedef struct sig_slot_t sig_slot_t;
typedef struct sig_slot_t{
	char   chSenderName[SIG_NAME_MAX];//信号名称
    void * ptSenderObj;  //信号所在对象的地址
	void * ptRecObj;    //槽所在对象的地址
	void * ptRecFun;   //槽函数的地址
}sig_slot_t;

然后重新定义一个宏,来取代QObject的地位:

#define SIG_SLOT_OBJ  sig_slot_t tObject;

然后用法就和QT中一样了,在需要信号的地方,给结构体中,加入SIG_SLOT_OBJ 。

5. 实现connect函数

接下来就只剩一个把信号和槽连接起来的宏了

void connect(void *SenderObj, const char *ptSender,void *RecObj,void *RecFun)
{
    if(SenderObj == NULL || ptSender == NULL || RecObj == NULL || RecFun == NULL){
        return;
    }
    sig_slot_t * ptMetaObj = SenderObj;
    ptMetaObj->ptRecFun = RecFun;
    ptMetaObj->ptRecObj = RecObj;
    memcpy(ptMetaObj->chSenderName,ptSender,strlen(ptSender));          
    }while(0);
}

6. 可有可无的slots

因为槽函数实际上就只是个函数而已,为了与信号遥相呼应,也实现一下:

#define __slots(__NAME,...)             \
            void __NAME(__VA_ARGS__);
            
#define slots(__NAME,__OBJ,...)  \
            __slots(__NAME,_args(__OBJ,##__VA_ARGS__))

三、完整的代码实现

以上代码只是展示核心部分,并且仅实现了一个信号对应一个槽,不能一个信号对应多个信号和槽,还有诸多类型检查,空指针检查等需要优化的地方,以下是完整的代码实现:

signals_slots.h文件

#ifndef __SIGNALS_SLOTS_H_
#define __SIGNALS_SLOTS_H_
#include ".\app_cfg.h"
#if USE_SERVICE_SIGNALS_SLOTS == ENABLED
#include <stdint.h>
#include <string.h>
#include <stdbool.h>

#define __PLOOC_CLASS_USE_STRICT_TEMPLATE__

#if     defined(__SIGNALS_SLOTS_CLASS_IMPLEMENT__)
    #define __PLOOC_CLASS_IMPLEMENT__
#elif   defined(__SIGNALS_SLOTS_CLASS_INHERIT__)
    #define __PLOOC_CLASS_INHERIT__
#endif

#include "plooc_class.h"

#define SIG_NAME_MAX 20

#define SIGNAL(x) "sig_"#x

#define SLOT(x) x

#define SIG_SLOT_OBJ  sig_slot_t tObject;


#define  args(...)            ,__VA_ARGS__

#define _args(...)            __VA_ARGS__

#define __RecFun_0(__OBJ)                                     \
            __RecFun((__OBJ))

#define __RecFun_1(__OBJ, __ARG1)                         \
            __RecFun((__OBJ),(__ARG1))

#define __RecFun_2(__OBJ, __ARG1, __ARG2)                         \
            __RecFun((__OBJ),(__ARG1), (__ARG2))

#define __RecFun_3(__OBJ, __ARG1, __ARG2, __ARG3)                 \
            __RecFun((__OBJ),(__ARG1), (__ARG2), (__ARG3))                         

#define __RecFun_4(__OBJ, __ARG1, __ARG2, __ARG3, __ARG4)                 \
            __RecFun((__OBJ),(__ARG1), (__ARG2), (__ARG3), (__ARG4))    
            
#define __RecFun_5(__OBJ, __ARG1, __ARG2, __ARG3, __ARG4, __ARG5)                 \
            __RecFun((__OBJ),(__ARG1), (__ARG2), (__ARG3), (__ARG4), (__ARG5))  

#define __RecFun_6(__OBJ, __ARG1, __ARG2, __ARG3, __ARG4, __ARG5, __ARG6)                 \
            __RecFun((__OBJ),(__ARG1), (__ARG2), (__ARG3), (__ARG4), (__ARG5), (__ARG6)) 

#define __RecFun_7(__OBJ, __ARG1, __ARG2, __ARG3, __ARG4, __ARG5, __ARG6, __ARG7)                 \
            __RecFun((__OBJ),(__ARG1), (__ARG2), (__ARG3), (__ARG4), (__ARG5), (__ARG6), (__ARG7)) 

#define __RecFun_8(__OBJ, __ARG1, __ARG2, __ARG3, __ARG4, __ARG5, __ARG6, __ARG7, __ARG8)                 \
            __RecFun((__OBJ),(__ARG1), (__ARG2), (__ARG3), (__ARG4), (__ARG5), (__ARG6), (__ARG7), (__ARG8)) 
 
            
#define __signals(__NAME,...)                                    \
             typedef void PLOOC_CONNECT2(__NAME,_fun_t)( __VA_ARGS__);  

#define signals(__NAME,__OBJ,...)               \
          __signals(__NAME,_args(__OBJ __VA_ARGS__))

#define __emit(__OBJ,...) \
   		   __PLOOC_EVAL(__RecFun_,##__VA_ARGS__)              \
                   ((__OBJ)->ptRecObj,##__VA_ARGS__); 

#define emit(__NAME,__OBJ,...)                     \
           do {sig_slot_t *ptObj = &((__OBJ)->tObject);                \
               do{if(__OBJ == NULL || ptObj == NULL ) break;              \
		           PLOOC_CONNECT2(__NAME,_fun_t) *__RecFun = ptObj->ptRecFun;   \
                   if(__RecFun != NULL) __emit(ptObj __VA_ARGS__);                           \
                   ptObj = ptObj->ptNext;             \
               }while(ptObj != NULL);              \
           }while(0)

#define __slots(__NAME,...)             \
            void __NAME(__VA_ARGS__);
            
#define slots(__NAME,__OBJ,...)  \
            __slots(__NAME,_args(__OBJ,##__VA_ARGS__))

#define connect(__SIG_OBJ,__SIG_NAME,__SLOT_OBJ,__SLOT_FUN)    \
            direct_connect(__SIG_OBJ.tObject,__SIG_NAME,__SLOT_OBJ,__SLOT_FUN)

#define disconnect(__SIG_OBJ,__SIG_NAME)    \
            auto_disconnect(__SIG_OBJ.tObject,__SIG_NAME)


typedef struct sig_slot_t sig_slot_t;
typedef struct sig_slot_t{
	char   chSenderName[SIG_NAME_MAX];
    void * ptSenderObj;  
	void * ptRecObj;
	void * ptRecFun;
    sig_slot_t *ptNext;
    sig_slot_t *ptPrev;
}sig_slot_t;

void direct_connect(sig_slot_t *ptSenderObj, const char *ptSender,void *ptRecObj,void *ptRecFun);
void auto_disconnect(sig_slot_t *ptSenderObj, const char *ptSender);


#undef __SIGNALS_SLOTS_CLASS_INHERIT__
#undef __SIGNALS_SLOTS_CLASS_IMPLEMENT__

#endif
#endif /* QUEUE_QUEUE_H_ */

signals_slots.c文件

#include "signals_slots.h"

void direct_connect(sig_slot_t *SenderObj, const char *ptSender,void *RecObj,void *RecFun)
{
    if(SenderObj == NULL || ptSender == NULL || RecObj == NULL || RecFun == NULL){
        return;
    }
    sig_slot_t * ptMetaObj = SenderObj;
    do{
        if(strstr(RecFun,"sig_")){
            memcpy(ptMetaObj->chSenderName,ptSender,strlen(ptSender)); 
            while(ptMetaObj->ptNext != NULL){
                ptMetaObj = ptMetaObj->ptNext;
            }
            ptMetaObj->ptNext = RecObj; 
            ptMetaObj->ptNext->ptPrev = ptMetaObj;          
            ptMetaObj = ptMetaObj->ptNext;
           
            memcpy(ptMetaObj->chSenderName,RecFun,strlen(RecFun));  
            break;            
        }
        
        if(strcmp(ptMetaObj->chSenderName,ptSender) == 0){
           sig_slot_t * ptSenderObj = malloc(sizeof(sig_slot_t));
           while(ptMetaObj->ptNext != NULL){
               ptMetaObj = ptMetaObj->ptNext;
           }
           ptMetaObj->ptNext = ptSenderObj;
           ptMetaObj->ptNext->ptPrev = ptMetaObj;  
           ptMetaObj = ptMetaObj->ptNext;
        }
        ptMetaObj->ptRecFun = RecFun;
        ptMetaObj->ptRecObj = RecObj;
        memcpy(ptMetaObj->chSenderName,ptSender,strlen(ptSender));  
        
    }while(0);
}

void auto_disconnect(sig_slot_t *ptSenderObj, const char *ptSender)
{
    if(ptSenderObj == NULL || ptSender == NULL){
        return;
    }
    sig_slot_t * ptMetaObj = ptSenderObj;
	if(strcmp(ptMetaObj->chSenderName,ptSender) == 0){

	   while(ptMetaObj->ptNext != NULL){
		   ptMetaObj = ptMetaObj->ptNext;
	   }
 
       while(ptMetaObj != NULL){
           ptMetaObj->ptNext = NULL;
           memset(ptMetaObj->chSenderName,0,sizeof(ptMetaObj->chSenderName));
           if(ptMetaObj->ptRecFun != NULL){
               ptMetaObj->ptRecObj = NULL;
               ptMetaObj->ptRecFun = NULL;
               sig_slot_t * ptObj = ptMetaObj;
               free(ptObj);
           }
           ptMetaObj = ptMetaObj->ptPrev;
       }
	}
}


代码下载地址:https://gitee.com/Aladdin-Wang/signals_slots

四、使用方法与QT中的区别

1. SIG_SLOT_OBJ取代QObject

SIG_SLOT_OBJ取代QObject,且只需要在信号所在的类中定义。

2. 定义信号不同

QT在类里面声明信号,signals宏是在结构体外声明信号,并且要指定信号名称,信号所在的对象地址,和一些自定义的参数:

  signals(__NAME,__OBJ,...) 
  example:
  signals(send_sig,can_data_msg_t *ptThis,
      args(              
            uint8_t *pchByte,
            uint16_t hwLen
          ));

3. 发射信号不同

emit宏的括号内需要指定信号名称,信号所在的对象地址,和自定义的参数的数据:

   emit(__NAME,__OBJ,...) 
   example:  
   emit(send_sig,&tCanMsgObj,
      args(
            tCanMsgObj.CanDATA.B,
            tCanMsgObj.CanDLC
         ));

4. 定义槽不同

与定义信号语法类似

   slots(__NAME,__OBJ,...) 
   example:
   slots(enqueue_bytes,byte_queue_t *ptObj,
      args(
            void *pchByte,
            uint16_t hwLength
         ));

5. 连接信号与槽

与QT一样一个信号可以连接多个信号或者槽,但是QT支持五种连接属性,目前仅实现了其中的Qt::DirectConnection属性,也就是同步调用方式,异步方式正在持续完善中。
在这里插入图片描述

#define connect(__SIG_OBJ,__SIG_NAME,__SLOT_OBJ,__SLOT_FUN)    \
            direct_connect(__SIG_OBJ.tObject,__SIG_NAME,__SLOT_OBJ,__SLOT_FUN)

  example:
  connect(&tCanMsgObj,SIGNAL(send_sig),&s_tFIFOin,SLOT(enqueue_bytes));

#define disconnect(__SIG_OBJ,__SIG_NAME)    \
            auto_disconnect(__SIG_OBJ.tObject,__SIG_NAME)
  example:
  connect(&tCanMsgObj,SIGNAL(send_sig));

五、信号与槽使用示例

玩信号与槽,少不了要与面向对象打交道,众所周知,C语言不是面向对象的语言,对于面向对象的特性不是很友好,不过不用担心,福利来了,裸机思维公众号作者开源了一套面向对象的C语言框架,可以轻松助你在C语言中零代价的愉快玩耍面向对象。

信号与槽的实现正是依赖了PLOOC中诸多的宏,所以首先需要下载安装PLOOC,具体方法参考:OOPC开发从未如此简单

接下来实现一个将CAN接收的数据,存储到环形队列ringbuf的例子:

can.h文件

#include "signals_slots/signals_slots.h"
typedef struct
{
    SIG_SLOT_OBJ;
    uint8_t  CanDLC;
    union {
        uint8_t B[8];
        uint16_t H[4];
        uint32_t W[2];
    } CanDATA;
}can_data_msg_t;

signals(send_sig,can_data_msg_t *ptThis,
      args(              
            uint8_t *pchByte,
            uint16_t hwLen
          ));
          
extern can_data_msg_t tCanMsgObj;          

can.c文件

#include "can.h"
can_data_msg_t tCanMsgObj;
void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan)
{
	HAL_StatusTypeDef status;
	CAN_RxHeaderTypeDef rxheader = {0};

	status = HAL_CAN_GetRxMessage(hcan, CAN_RX_FIFO0, &rxheader, (uint8_t *)tCanMsgObj.CanDATA.B);
	if (HAL_OK != status)
			return ;
	/* get id */
	if (CAN_ID_STD == rxheader.IDE)
	{
		tCanMsgObj.CanID_t.CanID = rxheader.StdId;
	}
	else
	{
		tCanMsgObj.CanID_t.CanID = rxheader.ExtId;
	}
	/* get len */
	tCanMsgObj.CanDLC = rxheader.DLC;	

    emit(send_sig,&tCanMsgObj,
        args(
            tCanMsgObj.CanDATA.B,
            tCanMsgObj.CanDLC
         ));
}

main.c文件

#include "./signals_slots/signals_slots.h"
#include "can.h"

static uint8_t s_cFIFOinBuffer[1024];
static byte_queue_t s_tFIFOin;

MX_CAN1_Init();
HAL_CAN_Start(&hcan1);
HAL_CAN_ActivateNotification(&hcan1, CAN_IT_RX_FIFO0_MSG_PENDING);
QUEUE_INIT(&s_tFIFOin, s_cFIFOinBuffer, sizeof(s_cFIFOinBuffer));
int main(void)
{
    connect(&tCanMsgObj,SIGNAL(send_sig),&s_tFIFOin,SLOT(enqueue_bytes));
    while(1){
     //do something
    }
}

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

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

相关文章

【NI Multisim 14.0原理图环境设置——原理图的组成】

目录 序言 一、原理图的组成 &#x1f46c; 1. 元器件 &#x1f46c;2. 仪表 &#x1f46c;3.导线 &#x1f46c;4.丝印层 &#x1f46c;5. 端口 &#x1f46c;6.网络标号 &#x1f46c;7.电源符号 序言 NI Multisim最突出的特点之一就是用户界面友好。它可以使电路设…

为iframe正名,你可能并不需要微前端

作者&#xff1a;刘显安(码怪) 任何新技术、新产品都是有一定适用场景的&#xff0c;它可能在当下很流行&#xff0c;但它不一定在任何时候都是最优解。 前言 最近几年微前端很火&#xff0c;火到有时候项目里面用到了iframe还要偷偷摸摸地藏起来生怕被别人知道了&#xff0c;…

Linux学习笔记——Tomcat安装部署

5.2、Tomcat安装部署 5.2.1、简介 Tomcat是由Apache开发的一个Servlet容器&#xff0c;实现了对Servlet和JSP的支持&#xff0c;并提供了作为Web服务器的一些特有功能&#xff0c;如Tomcat管理和控制平台、安全域管理和Tomcat阀等。 简单来说&#xff0c;Tomcat是一个WEB应用…

内核解读之内存管理(3)内存管理三级架构之内存区域zone

文章目录1、zone类型2、zone结构体3、zone的初始化流程1、zone类型 NUMA结构下, 每个处理器CPU与一个本地内存直接相连, 而不同处理器之前则通过总线进行进一步的连接, 因此相对于任何一个CPU访问本地内存的速度比访问远程内存的速度要快, 而Linux为了兼容NUMA结构, 把物理内存…

Flink数据流类型之间的转换(WindowedStream、DataStream、KeyedStream、AllWindowStream之间的转换)

Flink提供了一些流API&#xff0c;其中包括WindowedStream、DataStream、KeyedStream和AllWindowStream。 &#x1f34a;WindowedStream是一种特殊的流&#xff0c;其中数据已按时间或数据元素的键进行分组&#xff0c;并且每个分组的数据都在窗口中按时间划分。这意味着&…

2023年出入境政策-喜忧参半

2023年已经到来&#xff0c;随着卫健委公布中国防控新冠措施调整优化以后&#xff0c;出入境政策相应也有了很大变化&#xff0c;知识人网小编概括为喜忧参半。喜的是从国外入境中国不再需要集中隔离&#xff1b;忧的是有些国家对于中国人入境增加了核酸检测要求。下面我们就这…

第一章 Java入门开发

第一章 Java入门开发 目录一&#xff0e; 概述二&#xff0e; JDK1. 概述2. 安装3. JDK目录一&#xff0e; 概述 Java是一门高级程序设计语言&#xff0c;是支持跨平台和完成面向对象的程序设计语言。针对不同的开发市场&#xff0c;sun公司将Java分为Java SE&#xff08;标准版…

关于clip通信架构设计的调研

网络上大部分关于clip-as-service的描述都是关于它如何使用&#xff0c;基于它的编码功能上去计算文本相似度&#xff0c;根据文字推荐图片等等&#xff0c;只有作者的创作思路里面提及通信架构的设计。 作者博客&#xff1a; 链接: link 如何解决多个客户端同时请求服务端的场…

STS4中MVC项目中把log4j从1.x升级到2.x中遇到的两个问题

文章目录问题一 升级后看Maven Dependencies中还是有依赖1.x的log4j问题二 web.xml配置不对项目原来的log4j版本是1.2.14&#xff0c;有漏洞需要升级到2.18.0.问题一 升级后看Maven Dependencies中还是有依赖1.x的log4j 原因是有关联依赖&#xff0c; 项目中别的jar库有依赖低…

【算法笔记】【专题】RMQ 问题:ST表/树状数组/线段树

0. 前言 好久没更算法笔记专栏了&#xff0c;正好学了新算法来更新…… 这也是本专栏的第一个专题问题&#xff0c;涉及到三种数据结构&#xff0c;如果写得有问题请各位大佬多多指教&#xff0c;谢谢&#xff01; 1. 关于 RMQ 问题 RMQ 的全称是 Range Minimum/Maximum Que…

《Linux运维实战:Centos7.6基于docker-compose一键离线部署单节点redis6.2.8 》

一、部署背景 由于业务系统的特殊性&#xff0c;我们需要面向不通的客户安装我们的业务系统&#xff0c;而作为基础组件中的redis针对不同的客户环境需要多次部署&#xff0c;作为一个运维工程师&#xff0c;提升工作效率也是工作中的重要一环。所以我觉得有必要针对redis6.2.8…

使用 .NET 标记游戏地图关键坐标点

本文以天涯明月刀 OL 游戏的云上之城探索玩法为例&#xff0c;介绍如何使用 .NET 在游戏地图中标记大量关键坐标点。 1. 背景 大概很多程序员都是喜欢玩游戏的吧&#xff0c;我也不例外。我们经常会看到电视剧中的各路游戏大神&#xff0c;要么是有只有他一个人会的骚操作&…

Linux--信号--信号的产生方式--核心转储--0104

1. 什么是信号 生活中的信号&#xff1a;红绿灯&#xff0c;狼烟&#xff0c;撤退、集合...。 我们认识这些信号&#xff0c;首先是因为自己记住了对应场景下的信号后续需要执行的动作。如果信号没有产生&#xff0c;我们依旧知道如何处理这个信号。收到信号&#xff0c;我们…

springboot学习(七十八) springboot中通过自定义注解实现数据脱敏的功能

文章目录前言一、引入hutools工具类二、定义常用需要脱敏的数据类型的枚举三、定义脱敏方式枚举四、自定义脱敏的注解五、自定义Jackson的序列化方式六、使用七、脱敏效果前言 对于某些接口返回的信息&#xff0c;涉及到敏感数据的必须进行脱敏操作&#xff0c;例如银行卡号、…

带你了解ssh服务过程

远程连接服务 1、什么是远程连接服务器 远程连接服务器通过文字或图形接口方式来远程登录系统&#xff0c;让你在远程终端前登录linux主机以取得可操作主机接口&#xff08;shell&#xff09;&#xff0c;而登录后的操作感觉就像是坐在系统前面一样。 2、远程连接服务器的功…

【C++】函数重载的使用及原理

概述 在学校里&#xff0c;我们都会有班里同学被起外号的经历&#xff0c;而且同一个人可能还会有好几个外号。 在自然语言中&#xff0c;一个词可以有多重含义&#xff0c;人们可以通过上下文来判断该词真实的含义&#xff0c;即该词被重载了。 目录 概述 什么是函数重载 …

项目管理:如何制作项目进度计划表?

项目进度管理是根据项目目标&#xff0c;编制合理的进度计划&#xff0c;并在项目推进过程中随时检查项目执行情况。 项目进度管理的目的就是为了实现最优工期&#xff0c;多快好省地完成任务。 而甘特图&#xff0c;就是用表格图形的方式来展示项目的进展&#xff0c;是一个比…

赛狐ERP:优秀的亚马逊运营具备的五项能力!

我们都知道&#xff0c;亚马逊运营是整个店铺的主导&#xff0c;很大程度上会影响着一个店铺经营的好坏&#xff0c;那么一个好的亚马逊运营&#xff0c;应该具备哪些能力呢&#xff1f;今天赛狐ERP就来给和大家聊一聊&#xff0c;希望对各位亚马逊运营们会有启发&#xff01;1…

ORB-SLAM2 --- LocalMapping::Run 局部建图线程解析

目录 一、线程作用 二、局部建图线程主要流程 三、局部建图线程主函数 四、调用函数解析 4.1 设置"允许接受关键帧"的状态标志LocalMapping::SetAcceptKeyFrames函数解析 4.2 查看列表中是否有等待被插入的关键帧LocalMapping::CheckNewKeyFrames函数 4.3 …

十分钟学会在linux上部署chrony服务器(再见 NTP,是时候拥抱下一代时间同步服务 Chrony 了)

chrony服务器 Chrony 相较于 NTPD 服务的优势 安装与配置&#xff08;Chrony的配置文件是/etc/chrony.conf&#xff09; 同步网络时间服务器 设置开机启动&#xff0c;重启服务 chronyc sources 输出结果解析 练习 实验模型图如下 实验a如下 实验b如下 再见 NTP&#x…