单片机串口实现字符串命令解析---使用函数指针(类似哈希表)

2023-11-15

 通常情况下串口通信用的大多数都是用十六进制数据来传输指令,比如最常见的modbus的通信,如读保持寄存器指令:01 03 00 00 00 01 84 0A,这种十六进制的指令在这里就不讨论了。想要详细了解可以看往期的文章。串口相关文章链接如下:

STM32F103单片机modbus通信示例

STM32单片机串口空闲中断+DMA接收不定长数据

STM32单片机串口空闲中断接收不定长数据

STM8学习笔记---串口通信中如何自己定义通信协议

STM8学习笔记---Modbus通信协议简单移植

STM8学习笔记---串口printf函数的实现

有时候串口也需要通过字符串命令来实现某些功能, 如果使用AT指令的话,通信就是以字符串格式进行。

 

有时候自己做产品的时候,需要通过指令来控制设备,使用字符串的话更方便。比如发送一条开LED灯的指令"led on",关灯指令"led off"。这样通过字符串命令来控制设备,比直接通过16进制数字控制设备看起来更方便直观。比如今天要实现的功能。

        那么如何解析字符串命令呢?通常第一个想法就是,将读取到的字符串和程序中存储的字符串进行比较,如果字符串相等的话,就调用某个函数去执行相应的功能。这种办法是最简单也就是最容易实现的。通常用一个switch语句就可以搞定,switch的每个分支代表一个指令,然后去调用相关的函数执行。相关代码可以参考 C语言中字符串比较 这篇文章。

         还有没有其他方法呢?如果了解数据结构的话就可以想到哈希表。可以使用类似于哈希表的原理来实现。在一个表中每一个键就对应一个值。通过键就可以找到值。就好像学生的学号和姓名一样,将学号和姓名连接起来,放在一起,通过学号就可以找到姓名。将字符串和要执行的函数对应起来,放在哈希表中。在表中找到字符串后,就可以直接找到对应执行的函数。增加或者删除串口命令时,只需要操作这张表就行了。就不用每次指令或者函数名发生变化时,都要去switch语句中修改。
       在单片机中没有类似哈希表的这种数据结构,那要怎么实现呢?于是想到了用结构体去实现,在一个结构体里面有两个元素,一个是字符串,一个是需要执行的函数。这样字符串和函数就被绑定在了一起。在初始化命令的时候,将字符串和对应的函数,都写在一个结构体中。那么只有找到了这个字符串,就自然会找到对应的执行函数。这样就实现了类似哈希表的功能。 

        首先定义一个结构体,用来存储字符串命令和对应功能的函数   

typedef void ( *functions )( void );                   // 函数指针类型
//命令结构体
typedef struct
{
    char cmd_name[MAX_CMD_LENGTH + 1];                //字符数组存储字符串命令
    functions cmd_functions;                          //通过指针传递函数名
}CMD_Name_Func;

        在结构体里面有两个元素,一个字符数组用来存储字符串命令。一个指针用来存储函数的入口地址,该函数没有返回值,没有参数。

       如果每个命令都对应一个这样的结构体,命令比较多的时候,如何能方便快速去找到这些结构体呢?最简单的就是将这些结构体存储在数组中,这样数组的每一个元素就是一个结构体,通过数组的下标就能很方便的访问到每一个结构体。

// 命令列表结构体类型
typedef struct
{
    CMD_Name_Func cmdNames[MAX_CMDS_COUNT];                     //结构体数组字符串命令 和对应函数
    int num;	                                                //统计结构体个数
}CMD_LIST;

        在另一个结构体中用一个数组来存储命令结构体,每个结构体的数组元素都代表一个字符串命令和对应的函数,同时用一个计数器来统计,共存储了多少条命令。当串口接收到一个字符串后,就遍历一次结构体数组,对比里面是否有相同的字符串,如果有,就执行该字符串对应的函数。通过这种方式来处理字符串和命令的话,只需要在初始化的时候将字符串命令添加到这个列表中就可以了,而程序的其他地方就不需要修改了。

     要实现上面所说的功能,还需要再实现两个函数,一个函数实现将命令添加到结构体,一个函数实现遍历结构体数组,寻找匹配的字符串并执行相应的函数。

static CMD_LIST command_list = {NULL, 0};  // 全局命令列表,保存已注册命令集合
//注册命令
void register_cmds( CMD_Name_Func reg_cmds[], int length )
{
    int i;
 
    if ( length > MAX_CMDS_COUNT )
    {
        return;
    }
 
    for ( i = 0; i < length; i++ )
    {
        if ( command_list.num < MAX_CMDS_COUNT ) // 命令列表未满
        {
            strcpy( command_list.cmdNames[command_list.num].cmd_name, reg_cmds[i].cmd_name );       //将字符串命令拷贝到列表中
            command_list.cmdNames[command_list.num].cmd_functions = reg_cmds[i].cmd_functions;          //将命令对应的函数存储在列表中
            command_list.num++;                                                                     // 数量值默认为0,每添加一个命令,数量加1.             
        }
    }
}

        这个命令注册函数实现将命令结构体添加到命令列表中。用户新增加一条指令,就调用一次注册函数,将字符串命令添加到命令列表字符串中,同时将字符串命令对应的函数也添加到列表函数中。如果有新增加的子模块,只需要在子模块中调用一次注册命令,就完成了字符串命令的增加。其他代码不需要修改。

        比如现在led模块需要添加命令

//注册led命令
void led_register( void )
{
    //初始化 字符串命令和对应函数
    CMD_Name_Func led_cmds[] =
    {
        {"led1 on", led1_on},                       // 添加字符串命令 和 对应的函数
        {"led1 off", led1_off},                     
        {"led2 on", led2_on},
        {"led2 off", led2_off},
        {"led3 on", led3_on},
        {"led3 off", led3_off}
    };
    //将命令添加到列表中
    register_cmds( led_cmds, ARRAY_SIZE( led_cmds ) );	// ARRAY_SIZE 用来计算结构体数组中,数组的个数。个数=结构体总长度/单个数组长度
}

        在led模块中创建命令结构体,并将创建的结构体添加到命令列表中。通过代码可以看到增加了6条关于led的字符串命令,每个字符串命令都对应一个需要执行的函数。

       假如现在还需要添加一个蜂鸣器的子模块,那么就可以直接在蜂鸣器的子文件内直接注册命令。

//注册 beep命令
void beep_register( void )
{
    //初始化 字符串命令和对应函数
    CMD_Name_Func beep_cmds[] =
    {
        {"beep on", beep_on},                       
        {"beep off", beep_off} 
    };
    //将命令添加到列表中
    register_cmds( beep_cmds, ARRAY_SIZE( beep_cmds ) );	// ARRAY_SIZE 用来计算结构体数组中,数组的个数。个数=结构体总长度/单个数组长度
}

        在蜂鸣器的模块中添加了两条命令,然后通过注册函数将蜂鸣器相关的命令就添加到了命令列表中。

        通过一个注册命令就实现了命令的添加,而不需要修改其他的代码,实现了代码的"高内聚低耦合"上面实现了命令的注册,还需要实现一个命令的解析。

void match_cmd( char *cmdStr )
{
    int i;
    if ( strlen( cmdStr ) > MAX_CMD_LENGTH )
    {
        return;
    }
    for ( i = 0; i < command_list.num; i++ )     // 遍历命令列表
    {
        if ( strcmp( command_list.cmdNames[i].cmd_name, cmdStr ) == 0 )   //比较接收到的命令字符串 和 列表中存储的命令字符串是否相等,如果相等就调用命令字符串对应的函数。
        {
            command_list.cmdNames[i].cmd_functions();
        }
    }
}

        每次注册命令的时候,会有个计数器统计注册命令的数量。在命令解析函数中就循环去判断接收到的命令是否在命令列表中,如果命令列表中存在相等的字符串,就去执行对应的函数。而命令解析函数是不关心接收到的具体字符串命令是什么,需要执行的相应函数是什么。所以每次命令添加或者删除的时候,对命令解析和函数没有任何的影响。

       这个命令解析函数比较类似于设计模式中的"工厂模式",所谓的工厂模式百度百科解释如下:

 

        如果不了解面向对象编程的话,可能上面的这个解释看的不太明白。举个简单的例子就是,工厂生产东西的时候不关心具体生产的是什么东西,客户将需要生产东西的大小尺寸颜色特征告诉工厂,工厂就按照要求去执行。比如客户要求做一个直径5cm的玻璃透明圆柱体,圆柱体只需要底面,不需要顶面。工厂就按照客户的要求去生产这样一个东西,虽然这个东西按照一般经验来看就是一个透明的玻璃杯。但是工厂不用关心这个东西的名称和用途,只需要按照客户的要求去实现。

       而上面的命令解析函数,实际上也就是一个工厂,客户将一个字符串和一个函数送来。工厂就按照指定的字符串去执行指定函数。而工厂本身不去关心这个字符串具体是什么?函数具体是什么?这样的话,只要客户在命令列表中注册了字符串命令和相应的执行动作。命令解析函数就可以实现想要的功能。

       通过这种模式去解析字符串命令的话,就可以移植到到任何需要命令解析的单片机上,而不用去关心单片机的IO、中断、寄存器等等其他东西。下面就贴出完整的代码

命令解析头文件 cmd.h :

#ifndef __CMD_H_
#define __CMD_H_
#include "iostm8s103F3.h"
#define ARRAY_SIZE(x)	(sizeof(x) / (sizeof((x)[0])))        //用来计算结构体数组中,数组的个数。个数=结构体总长度/单个数组长度
#define		MAX_CMD_LENGTH		15	                // 最大命令名长度
#define		MAX_CMDS_COUNT		20	                // 最大命令数
typedef void ( *functions )( void );  	                        // 命令操作函数指针类型
//命令结构体类型 用于存储字符串命令和对应函数
typedef struct
{
    char cmd_name[MAX_CMD_LENGTH + 1];                          // 命令名 字符串末尾系统会自动添加结束符'/0'       sizeof("name")大小为 10
    functions cmd_functions;			  	        // 命令操作函数     sizeof(func) 大小为 2
}CMD_Name_Func;
// 命令列表结构体类型  用于存储字符串命令数组
typedef struct
{
    CMD_Name_Func cmdNames[MAX_CMDS_COUNT];                     // 存储字符串命令 和对应函数
    int num;	                                                // 命令数组个数
}CMD_LIST;
 
 
void register_cmds( CMD_Name_Func reg_cmds[], int num );
void match_cmd( char *str );
 
#endif
 
 

命令解析代码cmd.c

#include <string.h>
#include "cmd.h"
#include "uart.h"
static CMD_LIST command_list = {NULL, 0};  // 全局命令列表,保存已注册命令集合
/*
* 函数介绍: 命令注册函数 每新添加一个命令,就添加到命令列表中
* 输入参数: reg_cmds 待注册命令结构体数组
*            length   数组个数
* 输出参数: 无
* 返回值 :  无
* 备    注: length 不得超过 MAX_CMDS_COUNT  
*/
void register_cmds( CMD_Name_Func reg_cmds[], int length )
{
    int i;
 
    if ( length > MAX_CMDS_COUNT )
    {
        return;
    }
 
    for ( i = 0; i < length; i++ )
    {
        if ( command_list.num < MAX_CMDS_COUNT ) // 命令列表未满
        {
            strcpy( command_list.cmdNames[command_list.num].cmd_name, reg_cmds[i].cmd_name );       //将字符串命令拷贝到列表中
            command_list.cmdNames[command_list.num].cmd_functions = reg_cmds[i].cmd_functions;          //将命令对应的函数存储在列表中
            command_list.num++;                                                                     // 数量值默认为0,每添加一个命令,数量加1.             
        }
    }
}
 
/*
* 函数介绍: 命令匹配执行函数
* 输入参数: cmdStr 待匹配命令字符串
* 输出参数: 无
* 返回值 :  无
* 备    注: cmdStr 长度不得超过 MAX_CMD_NAME_LENGTH
*/
void match_cmd( char *cmdStr )
{
    int i;
 
    if ( strlen( cmdStr ) > MAX_CMD_LENGTH )
    {
        return;
    }
 
    for ( i = 0; i < command_list.num; i++ )	                                                    // 遍历命令列表
    {
        if ( strcmp( command_list.cmdNames[i].cmd_name, cmdStr ) == 0 )                             //比较接收到的命令字符串 和 列表中存储的命令字符串是否相等,如果相等就调用命令字符串对应的函数。
        {
            command_list.cmdNames[i].cmd_functions();
        }
    }
}

ed头文件led.h

#ifndef __LED_H
#define __LED_H
#include "iostm8s103F3.h"
 
#define  LED1  PD_ODR_ODR4                       //蓝
#define  LED2  PA_ODR_ODR1                       //绿
#define  LED3  PA_ODR_ODR2                       //红
#define BLUE    {LED1=1;LED2=0;LED3=0;}
#define GREEN   {LED1=0;LED2=1;LED3=0;}
#define RED     {LED1=0;LED2=0;LED3=1;}
#define CYAN    {LED1=1;LED2=1;LED3=0;}          //青
#define PURPLE  {LED1=1;LED2=0;LED3=1;}        //紫
#define YELLOW  {LED1=0;LED2=1;LED3=1;}         //黄
#define ONALL   {LED2=1;LED3=1;LED1=1;}
#define OFFALL  {LED1=0;LED2=0;LED3=0;}
void LED_GPIO_Init( void );
void led1_on(void);
void led1_off(void);
void led2_on(void);
void led2_off(void);
void led3_on(void);
void led3_off(void);
void led_register(void);
#endif

led.c

#include "led.h"
#include "cmd.h"
 
//3色LED
void LED_GPIO_Init( void )
{
    PD_DDR |= ( 1 << 4 );        //PD4 输出 led
    PD_CR1 |= ( 1 << 4 );        //PD4 推挽输出
    PD_CR2 |= ( 1 << 4 );
 
    PA_DDR |= ( 1 << 1 );        //PA1 输出 led
    PA_CR1 |= ( 1 << 1 );        //PA1 推挽输出
    PA_CR2 |= ( 1 << 1 );
 
    PA_DDR |= ( 1 << 2 );        //PA2 输出 led
    PA_CR1 |= ( 1 << 2 );        //PA2 推挽输出
    PA_CR2 |= ( 1 << 2 );
}
 
void led1_on( void )
{
    LED1 = 1;
}
 
void led1_off( void )
{
    LED1 = 0;
}
void led2_on( void )
{
    LED2 = 1;
}
 
void led2_off( void )
{
    LED2 = 0;
}
void led3_on( void )
{
    LED3 = 1;
}
 
void led3_off( void )
{
    LED3 = 0;
}
 
//注册led命令
void led_register( void )
{
    //初始化 字符串命令和对应函数
    CMD_Name_Func led_cmds[] =
    {
        {"led1 on", led1_on},                       // 一个结构体变量大小为 12 (字符串大小10 + 函数名大小2)
        {"led1 off", led1_off},                     // 一个结构体变量大小为 12
        {"led2 on", led2_on},
        {"led2 off", led2_off},
        {"led3 on", led3_on},
        {"led3 off", led3_off}
    };
 
    //将命令添加到列表中
    register_cmds( led_cmds, ARRAY_SIZE( led_cmds ) );	// ARRAY_SIZE 用来计算结构体数组中,数组的个数。个数=结构体总长度/单个数组长度
}

beep.h

#ifndef __BEEP_H
#define __BEEP_H
#include "iostm8s103F3.h"
#define  BEEP  PB_ODR_ODR4                   
void BEEP_GPIO_Init( void );
void beep_register( void );
#endif

beep.c

#include "beep.h"
#include "cmd.h"
 
void BEEP_GPIO_Init( void )
{
    PB_DDR |= ( 1 << 4 );        //PB4 
    PB_CR1 |= ( 1 << 4 );        //PB4 推挽输出
    PB_CR2 |= ( 1 << 4 );
}
void beep_on( void )
{
    BEEP = 1;
}
void beep_off( void )
{
    BEEP = 0;
}
//注册 beep命令
void beep_register( void )
{
    //初始化 字符串命令和对应函数
    CMD_Name_Func beep_cmds[] =
    {
        {"beep on", beep_on},                       // 一个结构体变量大小为 12 (字符串大小10 + 函数名大小2)
        {"beep off", beep_off}                     // 一个结构体变量大小为 12
       
    };
    //将命令添加到列表中
    register_cmds( beep_cmds, ARRAY_SIZE( beep_cmds ) );	// ARRAY_SIZE 用来计算结构体数组中,数组的个数。个数=结构体总长度/单个数组长度
}

uart.h

#ifndef __UART_H
#define __UART_H
#include "iostm8s103F3.h"
 
extern char uartRecStr[20];             //串口接收字符串存储
extern unsigned char uartRecCnt;                   //接收数据个数
extern _Bool rec_ok;
 
void Uart1_IO_Init( void );
void Uart1_Init( unsigned int baudrate );
void SendChar( unsigned char dat );
void SendString( unsigned char* s );
 
#endif

uart.c

#include "uart.h"
#include "main.h"
 
char uartRecStr[20] = {0};             //串口接收字符串存储
unsigned char uartRecCnt = 0;                   //接收数据个数
_Bool rec_ok = 0;                                //接收完成标志位
 
//在Library Options中将Printf formatter改成Large
//重新定向putchar函数,使支持printf函数
int putchar( int ch )
{
    while( !( UART1_SR & 0X80 ) );              //循环发送,直到发送完毕
    UART1_DR = ( u8 ) ch;
    return ch;
}
//串口只用发送口,不用接收口
void Uart1_IO_Init( void )
{
    PD_DDR |= ( 1 << 5 );                       //输出模式 TXD
    PD_CR1 |= ( 1 << 5 );                       //推挽输出
 
    PD_DDR &= ~( 1 << 6 );                      //输入模式 RXD
    PD_CR1 &= ~( 1 << 6 );                      //浮空输入
}
 
//波特率最大可以设置为38400
void Uart1_Init( unsigned int baudrate )
{
    unsigned int baud;
    baud = 16000000 / baudrate;
    Uart1_IO_Init();
    UART1_CR1 = 0;      //禁止发送和接收
    UART1_CR2 = 0;      //8 bit
    UART1_CR3 = 0;      //1 stop
    UART1_BRR2 = ( unsigned char )( ( baud & 0xf000 ) >> 8 ) | ( ( unsigned char )( baud & 0x000f ) );
    UART1_BRR1 = ( ( unsigned char )( ( baud & 0x0ff0 ) >> 4 ) );
    UART1_CR2_bit.REN = 1;                      //接收使能
    UART1_CR2_bit.TEN = 1;                      //发送使能
    UART1_CR2_bit.RIEN = 1;                     //接收中断使能
}
 
//阻塞式发送函数
void SendChar( unsigned char dat )
{
    while( ( UART1_SR & 0x80 ) == 0x00 );       //发送数据寄存器空
    UART1_DR = dat;
}
//发送字符串
void SendString( unsigned char* s )
{
    while( 0 != *s )
    {
        SendChar( *s );
        s++;
    }
}
 
//接收中断函数 中断号18
#pragma vector = 20                             // IAR中的中断号,要在STVD中的中断号上加2
__interrupt void UART1_Handle( void )
{
    unsigned char res = 0;
 
    res = UART1_DR;
    UART1_SR &= ~( 1 << 5 );                    //RXNE 清零
    //SendChar(res);                            //test
    if( ( res != '\r' ) && ( res != '\n' ) )    //字符串以回车换行符结束
    {
        uartRecStr[uartRecCnt++] = res;
    }
    else
    {
        rec_ok = 1;                             //置接收完成标志
    }
}

主程序main.c

/*
*函数功能,实现串口字符串命令解析
*/
#include "iostm8s103F3.h"
#include "main.h"
#include "stdio.h"
#include "delay.h"
#include "stdlib.h"
#include "uart.h"
#include "string.h"
#include "cmd.h"
#include "led.h"
#include "beep.h"
 
void SysClkInit( void )
{
    CLK_SWR = 0xe1;                             //HSI为主时钟源  16MHz CPU时钟频率
    CLK_CKDIVR = 0x00;                          //CPU时钟0分频,系统时钟0分频
}
 
void main( void )
{
    __asm( "sim" );                             //禁止中断
    SysClkInit();
    delay_init( 16 );
    LED_GPIO_Init();
    BEEP_GPIO_Init();
    Uart1_Init( 9600 );
    __asm( "rim" );                             //开启中断
    
    //注册命令
    led_register();
    beep_register();
 
    while( 1 )
    {
        if( rec_ok )
        {
            rec_ok = 0;
            uartRecCnt = 0;
            SendString( uartRecStr );
            SendString( "\r\n" );
            match_cmd( uartRecStr );
            memset( uartRecStr, 0, sizeof( uartRecStr ) );		//清空备份数组 需要添加头文件 string.h
        }
    }
}

        在主函数中检测串口是否接收到了字符串,串口接收字符串以回车换行结束。若串口接收到了字符串,将接收到的字符串通过串口发送出去,并检查一次接收到的字符串是否和命令列表中的字符串匹配?如果接收到的字符串和命令列表中的字符串匹配,就中执行一次相关的函数。最后将串口缓冲区清空,继续等待下一次命令。

       测试效果如下

        这样通过字符串命令就可以直接控制LED灯和蜂鸣器了,如果下次需要增加一个继电器控制模块,就只需要编写继电器模块的c代码,在进入main函数时,注册继电器命令。继电器的模块就会被添加进来了。而不需要修改其他的模块和串口任何代码。

        通过上面的例子可以看到这种模式是相当的好用,难道这种方法就没有一点缺点吗?如果在单片机上用的话,这种模式有一个致命的缺点,那就是太占内存了。

首先看一张图

        这个是新建了4个led命令结构体,可以看出来每个命令的字符串数组长度都是16,函数指针默认为int型,占两个字节。一个结构体总共占18个字节。 4个led命令占4*18=72个字节的空间。虽然led命令最长的字符串只占8个字节,但是系统依然会分配16个字节空间。

        这个是命令列表,默认的最多命令数是20个,系统初始化的时候就一次性将这20个命令的空间分配好了。虽然代码中只用了4个命令,但是系统空间的占用却一点也没有少。

        这样的话对于空间比较小的单片机来说,虽然这种方法好用,但是太浪费空间。如果指令比较多,或者指令名比较长的话,可能光是指令列表就会把单片机的内存占满。这样的话还不如直接在switch语句中去比较字符串,直接比较字符串的话,只需要开辟一个字符串的存储空间就可以满足需求了。

        所以根据不同的情况选择合适的方法,适合自己的方法就是好方法。

完整工程代码下载地址:https://download.csdn.net/download/qq_20222919/13077567

本文代码参考资料地址:https://www.shaoguoji.cn/2017/11/18/c-object-oriented-command-parser/
————————————————
版权声明:本文为CSDN博主「qq_511386807」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_20222919/article/details/109383817

https://blog.csdn.net/qq_20222919/article/details/109383817

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

单片机串口实现字符串命令解析---使用函数指针(类似哈希表) 的相关文章

随机推荐

  • D0006-进制,位运算,原反补码

    十进制转其他进制 公式 除基取余 直到商为0 把余数反转拼接 十进制10转换二进制 商 余数 10 2 5 0 5 2 2 1 2 2 1 0 1 2 0 1 把余数反转拼接 1010 原码反码和补码 原码 一个二进制数据 最高位符号位 0
  • 数据存储---整形数据在内存中的存储

    我们每一次写代码的时候 都会创建变量 那么所创建的整形变量是如何在内存中存储的呢 目录 1 数据类型介绍 2 整形数据在内存中的存储形式 3 大小端字节序的介绍 1 数据类型介绍 本文重点介绍内置类型中的整形家族 1 内置类型 C语言本身具
  • JDK安装配置(Windows10)

    环境 windows10 官方链接 https www oracle com technetwork java javase downloads jdk8 downloads 2133151 html 点击Accept License Ag
  • 由于找不到msvcp120.dll无法继续执行代码怎么办?

    msvcp120 dll是微软软件包的一部分 它是一个库文件 可用于支持软件运行时 msvcp120 dll的作用是提供计算机程序所需的标准库 msvcp120 dll还负责管理堆内存 线程和异常处理函数等 在使用windows编写的应用程
  • indows npm ERR! gyp ERR! find Python Python is not set from command line or npm configuration npm ER

    问题描述 windows npm ERR gyp ERR find Python Python is not set from command line or npm configuration npm ERR gyp ERR find P
  • SSL工作原理

    转自 https www wosign com Basic howsslwork htm SSL 是一个安全协议 它提供使用 TCP IP 的通信应用程序间的隐私与完整性 因特网的 超文本传输协议 HTTP 使用 SSL 来实现安全的通信
  • 一个函数名后面加const表示什么意思

    const用在成员函数后 主要是针对类的const 对象 如 class Text public void printconst void const cout lt lt hello lt
  • java匿名对象的使用

    package object oriented04 import object oriented03 Person public class InstanceTest public static void main String args
  • 力扣OJ(1601-2000)

    目录 1602 找到二叉树中最近的右侧节点 1611 使整数变为 0 的最少操作次数 1612 检查两棵二叉表达式树是否等价 1631 最小体力消耗路径 1632 矩阵转换后的秩 1634 求两个多项式链表的和 1644 二叉树的最近公共祖
  • 【KnowledgeBase】目标追踪模型MOTR论文简要理解

    系列文章目录 文章目录 系列文章目录 前言 一 主要思想 二 整体架构 二 细节 1 Detect Query和Track Query 2 Tracklet Aware Label Assignment TALA 3 QIM模块 总结 前言
  • linux搭建主备负载均衡

    1 原理图 底层原理 2 负载集合的功能 1 客户端传过来的请求 在负载均衡那里 根据算法 把用户的请求给指定的服务器 2 如果负载均衡主机宕机了 备机马上接手 如果主机恢复了 备机马上退后 3 如果某个服务器挂了 该服务器马上被踢出去 负
  • mac 打开网页慢_苹果笔记本打开网页很慢是什么原因

    有时候我们找资料会发现网页打开很慢 这是怎么回事呢 为什么网页打开会很慢呢 以下就是小编给你做的整理 希望对你有用 的原因 一 网络最小带宽这是最主要的因素 也就是网友经常说的宽带不够 同样的网站 如果宽带高 访问速度就会明显变快 网络的带
  • ubantu18.04安装Opencv4.0.0

    1 安装依赖 sudo apt get install build essential sudo apt get install cmake git libgtk2 0 dev pkg config libavcodec dev libav
  • 使用z-file和七牛云对象存储构建个人网盘

    最近想构建一个个人网盘玩玩 用来存储些资源 这里使用云服务器 zfile 七牛云对象存储进行搭建 租用云服务器 首先需要在常用的云服务网站买一个云服务器 如阿里云 腾讯云等 这里不说该怎么租用和搭建了 使用七牛云对象存储 这里使用七牛云对象
  • 02功能之读写文件流操作(C语言实现读取文件指定一行)

    02功能之读写文件流操作 C语言实现读取文件指定一行 1 C语言读取文件指定一行 读取文件指定一行 int ReadLine1 const char fileName char outBuf int n int whichLine n 指定
  • sql查询一个字段包含另一个字段内容

    SELECT FROM tbl name WHERE a like CONCAT b 字段a包含字段b 例如 Find the capital and the name where the capital includes the name
  • java libusb_libusb中断传输

    我需要对定制的HID USB设备 控制面板上的一些按钮和LED 进行反向工程 该驱动程序仅在Windows上可用 我们需要 nix实现 该设备显然是HID设备 但不是特定类 它提供两个接口 每个接口都有一个中断 endpoints 我的设置
  • 思考:如何保证服务稳定性?

    最近一直在忙618大促的全链路压测 稳定性保障相关工作 结果618还未开始 生产环境就出了几次生产故障 且大多都是和系统稳定性 性能相关的bad case 生产全链路压测终于告一段落 抽出时间将个人收集的稳定性相关资料整理review了一遍
  • 初学者必读的Linux入门到精通

    课程介绍 本套课程是从入门开始的Linux学习课程 适合初学者阅读 由浅入深案例丰富 通俗易懂 主要涉及基础的系统操作以及工作中常用的各种服务软件的应用 部署和优化 即使是零基础的学员 只要能够坚持把所有章节都学完 也一定会受益匪浅 课程目
  • 单片机串口实现字符串命令解析---使用函数指针(类似哈希表)

    通常情况下串口通信用的大多数都是用十六进制数据来传输指令 比如最常见的modbus的通信 如读保持寄存器指令 01 03 00 00 00 01 84 0A 这种十六进制的指令在这里就不讨论了 想要详细了解可以看往期的文章 串口相关文章链接