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

【FreeRTOS】FreeRTOS队列

使用RTOS 需要注意的问题:像中断优先级、任务堆栈分配、可重入等,都是更容易出错的地方。

读队列时阻塞


当某个任务试图读一个队列时,其可以指定一个阻塞超时时间。在这段时间中,如
果队列为空,该任务将保持阻塞状态以等待队列数据有效。当其它任务或中断服务例程
往其等待的队列中写入了数据,该任务将自动由阻塞态转移为就绪态。当等待的时间超
过了指定的阻塞时间,即使队列中尚无有效数据,任务也会自动从阻塞态转移为就绪态。
由于队列可以被多个任务读取,所以对单个队列而言,也可能有多个任务处于阻塞
状态以等待队列数据有效。这种情况下,一旦队列数据有效,只会有一个任务会被解除
阻塞,这个任务就是所有等待任务中优先级最高的任务。而如果所有等待任务的优先级
相同,那么被解除阻塞的任务将是等待最久的任务。

写队列时阻塞


同读队列一样,任务也可以在写队列时指定一个阻塞超时时间。这个时间是当被写
队列已满时,任务进入阻塞态以等待队列空间有效的最长时间。
由于队列可以被多个任务写入,所以对单个队列而言,也可能有多个任务处于阻塞
状态以等待队列空间有效。这种情况下,一旦队列空间有效,只会有一个任务会被解除
阻塞,这个任务就是所有等待任务中优先级最高的任务。而如果所有等待任务的优先级
相同,那么被解除阻塞的任务将是等待最久的任务。

2.3 使用队列
xQueueCreate() API 函数
队列在使用前必须先被创建。
队列由声明为 xQueueHandle 的变量进行引用。 xQueueCreate()用于创建一个队
列,并返回一个 xQueueHandle 句柄以便于对其创建的队列进行引用。
当创建队列时, FreeRTOS 从堆空间中分配内存空间。分配的空间用于存储队列数
据结构本身以及队列中包含的数据单元。如果内存堆中没有足够的空间来创建队列,
xQueueCreate()将返回 NULL。

 xQueueSendToBack()和xQueueSendToFront()

xQueueSendToBack()用于将数据发送到队列尾;

xQueueSendToFront()用于将数据发送到队列首。


xQueueSend()完全等同于 xQueueSendToBack()。

但 切 记 不 要 在 中 断 服 务 例 程 中 调 用 xQueueSendToFront() 或xQueueSendToBack()。

系统提供中断安全版本的 xQueueSendToFrontFromISR()与xQueueSendToBackFromISR()用于在中断服务中实现相同的功能。

xQueueReceive()与 xQueuePeek() 


xQueueReceive()用于从队列中接收(读取)数据单元。接收到的单元同时会从队列中删除。
xQueuePeek()也是从从队列中接收数据单元,不同的是并不从队列中删出接收到的单元。

xQueuePeek()从队列首接收到数据后,不会修改队列中的数据,也不会改变数据在队列中的存储序顺。

切记不要在中断服务例程中调用 xQueueRceive()和 xQueuePeek()。

uxQueueMessagesWaiting() 


uxQueueMessagesWaiting()用于查询队列中当前有效数据单元个数。

切记不要在中断服务例程中调用 uxQueueMessagesWaiting()。应当在中断服务中
使用其中断安全版本 uxQueueMessagesWaitingFromISR()。

摘抄自:https://www.cnblogs.com/yangguang-it/p/7204541.html

本章节为大家讲解 FreeRTOS 的一个重要的通信机制----消息队列,初学者要熟练掌握,因为消息队
列在实际项目中应用较多。
消息队列的概念及其作用
消息队列就是通过 RTOS 内核提供的服务,任务或中断服务子程序可以将一个消息(注意,FreeRTOS
消息队列传递的是实际数据,并不是数据地址,RTX,uCOS-II 和 uCOS-III 是传递的地址)放入到队列。
同样,一个或者多个任务可以通过 RTOS 内核服务从队列中得到消息。通常,先进入消息队列的消息先传
给任务,也就是说,任务先得到的是最先进入到消息队列的消息,即先进先出的原则(FIFO),FreeRTOS
的消息队列支持 FIFO 和 LIFO 两种数据存取方式。
也许有不理解的初学者会问采用消息队列多麻烦,搞个全局数组不是更简单,其实不然。在裸机编程
时,使用全局数组的确比较方便,但是在加上 RTOS 后就是另一种情况了。 相比消息队列,使用全局数组
主要有如下四个问题:
 使用消息队列可以让 RTOS 内核有效地管理任务,而全局数组是无法做到的,任务的超时等机制需要用户自己去实现。
 使用了全局数组就要防止多任务的访问冲突,而使用消息队列则处理好了这个问题,用户无需担心。
 使用消息队列可以有效地解决中断服务程序与任务之间消息传递的问题。
 FIFO 机制更有利于数据的处理。

FreeRTOS 任务间消息队列的实现
任务间消息队列的实现是指各个任务之间使用消息队列实现任务间的通信。 下面我们通过如下的框图
来说明一下 FreeRTOS 消息队列的实现,让大家有一个形象的认识。

运行条件:
 创建消息队列,可以存放 10 个消息。
 创建 2 个任务 Task1 和 Task2,任务 Task1 向消息队列放数据,任务 Task2 从消息队列取数据。
 FreeRTOS 的消息存取采用 FIFO 方式。
运行过程主要有以下两种情况:
 任务 Task1 向消息队列放数据,任务 Task2 从消息队列取数据,如果放数据的速度快于取数据的速
度,那么会出现消息队列存放满的情况,FreeRTOS 的消息存放函数 xQueueSend 支持超时等待,
用户可以设置超时等待,直到有空间可以存放消息或者设置的超时时间溢出。
 任务 Task1 向消息队列放数据,任务 Task2 从消息队列取数据,如果放数据的速度慢于取数据的速
度,那么会出现消息队列为空的情况,FreeRTOS 的消息获取函数 xQueueReceive 支持超时等待,
用户可以设置超时等待,直到消息队列中有消息或者设置的超时时间溢出。
FreeRTOS 中断方式消息队列的实现
FreeRTOS 中断方式消息队列的实现是指中断函数和 FreeRTOS 任务之间使用消息队列。 下面我们通
过如下的框图来说明一下 FreeRTOS 消息队列的实现,让大家有一个形象的认识。

运行条件:
 创建消息队列,可以存放 10 个消息。

 创建 1 个任务 Task1 和一个串口接收中断。
 FreeRTOS 的消息存取采用 FIFO 方式。
运行过程主要有以下两种情况:
 中断服务程序向消息队列放数据,任务 Task1 从消息队列取数据,如果放数据的速度快于取数据的速
度,那么会出现消息队列存放满的情况。由于中断服务程序里面的消息队列发送函数
xQueueSendFromISR 不支持超时设置,所以发送前要通过函数 xQueueIsQueueFullFromISR 检测
消息队列是否满。
 中断服务程序向消息队列放数据,任务 Task1 从消息队列取数据,如果放数据的速度慢于取数据的速
度,那么会出现消息队列存为空的情况。在 FreeRTOS 的任务中可以通过函数 xQueueReceive 获取
消息,因为此函数可以设置超时等待,直到消息队列中有消息存放或者设置的超时时间溢出。

上面就是一个简单的 FreeRTOS 中断方式消息队列通信过程。 实际应用中,中断方式的消息机制要注意以
下四个问题:
 中断函数的执行时间越短越好,防止其它低于这个中断优先级的异常不能得到及时响应。
 实际应用中,建议不要在中断中实现消息处理,用户可以在中断服务程序里面发送消息通知任务,在
任务中实现消息处理,这样可以有效地保证中断服务程序的实时响应。同时此任务也需要设置为高优
先级,以便退出中断函数后任务可以得到及时执行。
 中断服务程序中一定要调用专用于中断的消息队列函数,即以 FromISR 结尾的函数。
 在操作系统中实现中断服务程序与裸机编程的区别。
 如果 FreeRTOS 工程的中断函数中没有调用 FreeRTOS 的消息队列 API 函数,与裸机编程是一
样的。
 如果 FreeRTOS 工程的中断函数中调用了 FreeRTOS 的消息队列的 API 函数,退出的时候要检
测是否有高优先级任务就绪,如果有就绪的,需要在退出中断后进行任务切换,这点与裸机编程
稍有区别,详见 20.4 小节实验例程说明(中断方式):
 另外强烈推荐用户将 Cortex-M3 内核的 STM32F103 和 Cortex-M4 内核的 STM32F407, F429
的 NVIC 优先级分组设置为 4,即:NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);这样中
断优先级的管理将非常方便。
 用户要在 FreeRTOS 多任务开启前就设置好优先级分组,一旦设置好切记不可再修改。

消息队列 API 函数
使用如下 23 个函数可以实现 FreeRTOS 的消息队列:
 xQueueCreateStatic()
 vQueueDelete()
 xQueueSend()

 xQueueSendFromISR()
 xQueueSendToBack()
 xQueueSendToBackFromISR()
 xQueueSendToFront()
 xQueueSendToFrontFromISR()
 xQueueReceive()
 xQueueReceiveFromISR()
 uxQueueMessagesWaiting()
 uxQueueMessagesWaitingFromISR()
 uxQueueSpacesAvailable()
 xQueueReset()
 xQueueOverwrite()
 xQueueOverwriteFromISR()
 xQueuePeek()
 xQueuePeekFromISR()
 vQueueAddToRegistry()
 vQueueUnregisterQueue()
 pcQueueGetName()
 xQueueIsQueueFullFromISR()
 xQueueIsQueueEmptyFromISR()
关于这 23 个函数的讲解及其使用方法可以看 FreeRTOS 在线版手册 。

这里我们重点的说以下 4 个函数:
 xQueueCreate ()
 xQueueSend ()
 xQueueSendFromISR ()
 xQueueReceive ()
因为本章节配套的例子使用的是这 4 个函数。

函数 xQueueCreate 

函数描述:
函数 xQueueCreate 用于创建消息队列。
 第 1 个参数是消息队列支持的消息个数。

 第 2 个参数是每个消息的大小,单位字节。

 返回值, 如果创建成功会返回消息队列的句柄,如果由于 FreeRTOSConfig.h 文件中 heap 大小不足,
无法为此消息队列提供所需的空间会返回 NULL。
使用这个函数要注意以下问题:
1. FreeRTOS 的消息传递是数据的复制,而不是传递的数据地址,这点要特别注意。 每一次传递都是
uxItemSize 个字节。

函数 xQueueSend

函数描述:
函数 xQueueSend 用于任务中消息发送。
 第 1 个参数是消息队列句柄。
 第 2 个参数要传递数据地址, 每次发送都是将消息队列创建函数 xQueueCreate 所指定的单个消息大
小复制到消息队列空间中。

 第 3 个参数是当消息队列已经满时,等待消息队列有空间时的最大等待时间,单位系统时钟节拍。
 返回值,如果消息成功发送返回 pdTRUE,否则返回 errQUEUE_FULL。
使用这个函数要注意以下问题:
1. FreeRTOS 的消息传递是数据的复制,而不是传递的数据地址。
2. 此函数是用于任务代码中调用的,故不可以在中断服务程序中调用此函数,中断服务程序中使用的是
xQueueSendFromISR。
3. 如果消息队列已经满且第三个参数为 0,那么此函数会立即返回。
4. 如果用户将 FreeRTOSConfig.h 文件中的宏定义 INCLUDE_vTaskSuspend 配置为 1 且第三个参数配
置为 portMAX_DELAY,那么此发送函数会永久等待直到消息队列有空间可以使用。
5. 消息队列还有两个函数 xQueueSendToBack 和 xQueueSendToFront,函数 xQueueSendToBack
实现的是 FIFO 方式的存取,函数 xQueueSendToFront 实现的是 LIFO 方式的读写。我们这里说的函
数 xQueueSend 等效于 xQueueSendToBack,即实现的是 FIFO 方式的存取。


函数 xQueueSendFromISR

函数描述:
函数 xQueueSendFromISR 用于中断服务程序中消息发送。
 第 1 个参数是消息队列句柄。
 第 2 个参数要传递数据地址, 每次发送都是将消息队列创建函数 xQueueCreate 所指定的单个消息大
小复制到消息队列空间中。
 第 3 个参数用于保存是否有高优先级任务准备就绪。如果函数执行完毕后,此参数的数值是 pdTRUE,
说明有高优先级任务要执行,否则没有。
 返回值,如果消息成功发送返回 pdTRUE,否则返回 errQUEUE_FULL。
使用这个函数要注意以下问题:
1. FreeRTOS 的消息传递是数据的复制,而不是传递的数据地址。 正因为这个原因,用户在创建消息队
列时单个消息大小不可太大,因为一定程度上面会增加中断服务程序的执行时间。
2. 此函数是用于中断服务程序中调用的,故不可以在任务代码中调用此函数,任务代码中使用的是
xQueueSend。
3. 消息队列还有两个函数 xQueueSendToBackFromISR 和 xQueueSendToFrontFromISR,函数
xQueueSendToBackFromISR 实现的是 FIFO 方式的存取,函数 xQueueSendToFrontFromISR 实
现的是 LIFO 方式的读写。我们这里说的函数 xQueueSendFromISR 等效于
xQueueSendToBackFromISR,即实现的是 FIFO 方式的存取。

 函数 xQueueReceive

函数描述:
函数 xQueueReceive 用于接收消息队列中的数据。
 第 1 个参数是消息队列句柄。
 第 2 个参数是从消息队列中复制出数据后所储存的缓冲地址,缓冲区空间要大于等于消息队列创建函
数 xQueueCreate 所指定的单个消息大小,否则取出的数据无法全部存储到缓冲区,从而造成内存溢
出。
 第 3 个参数是消息队列为空时,等待消息队列有数据的最大等待时间,单位系统时钟节拍。
 返回值,如果接到到消息返回 pdTRUE,否则返回 pdFALSE。
使用这个函数要注意以下问题:
1. 此函数是用于任务代码中调用的,故不可以在中断服务程序中调用此函数,中断服务程序使用的是
xQueueReceiveFromISR。
2. 如果消息队列为空且第三个参数为 0,那么此函数会立即返回。
3. 如果用户将 FreeRTOSConfig.h 文件中的宏定义 INCLUDE_vTaskSuspend 配置为 1 且第三个参数配
置为 portMAX_DELAY,那么此函数会永久等待直到消息队列有数据。

STM32F429 开发板实验 ,关键代码实现及解释:

复制代码
static void AppObjCreate (void)
{
    /* 创建10个uint8_t型消息队列 */
    xQueue1 = xQueueCreate(10, sizeof(uint8_t));
    if( xQueue1 == 0 )
    {
        /* 没有创建成功,用户可以在这里加入创建失败的处理机制 */
    }
    
    /* 创建10个存储指针变量的消息队列,由于CM3/CM4内核是32位机,一个指针变量占用4个字节 */
    xQueue2 = xQueueCreate(10, sizeof(struct Msg *));
    if( xQueue2 == 0 )
    {
        /* 没有创建成功,用户可以在这里加入创建失败的处理机制 */
    }
}

复制代码

复制代码
static void vTaskWork(void *pvParameters)
{

        MSG_T   *ptMsg;
    uint8_t ucCount = 0;
    
    /* 初始化结构体指针 */
    ptMsg = &g_tMsg;
    
    /* 初始化数组 */
    ptMsg->ucMessageID = 0;
    ptMsg->ulData[0] = 0;
    ptMsg->usData[0] = 0;

    while(1)
    {
            
        if (key1_flag==1)
        {
            key1_flag=0;
                    
                    ucCount++;
                
                    /* 向消息队列发数据,如果消息队列满了,等待10个时钟节拍 */
                    if( xQueueSend(xQueue1,
                                   (void *) &ucCount,
                                   (TickType_t)10) != pdPASS )
                    {
                        /* 发送失败,即使等待了10个时钟节拍 */
                        printf("K1键按下,向xQueue1发送数据失败,即使等待了10个时钟节拍\r\n");
                    }
                    else
                    {
                        /* 发送成功 */
                        printf("K1键按下,向xQueue1发送数据成功\r\n");                        
                    }
        }
        if(key2_flag==1)
        {
            key2_flag=0;
                    ptMsg->ucMessageID++;
                    ptMsg->ulData[0]++;;
                    ptMsg->usData[0]++;
                    
                    /* 使用消息队列实现指针变量的传递 */
                    if(xQueueSend(xQueue2,                  /* 消息队列句柄 */
                                 (void *) &ptMsg,           /* 发送结构体指针变量ptMsg的地址 */
                                 (TickType_t)10) != pdPASS )
                    {
                        /* 发送失败,即使等待了10个时钟节拍 */
                        printf("K2键按下,向xQueue2发送数据失败,即使等待了10个时钟节拍\r\n");
                    }
                    else
                    {
                        /* 发送成功 */
                        printf("K2键按下,向xQueue2发送数据成功\r\n");                        
                    }
            
        }
    
        
        vTaskDelay(20);
    }
}

复制代码

复制代码
void vTaskBeep(void *pvParameters)
{
    BaseType_t xResult;
    const TickType_t xMaxBlockTime = pdMS_TO_TICKS(3000); /* 设置最大等待时间为300ms */
    uint8_t ucQueueMsgValue;
    
    while(1)
    {
        xResult = xQueueReceive(xQueue1,                   /* 消息队列句柄 */
                                (void *)&ucQueueMsgValue,  /* 存储接收到的数据到变量ucQueueMsgValue中 */
                                (TickType_t)xMaxBlockTime);/* 设置阻塞时间 */
        
        if(xResult == pdPASS)
        {
            /* 成功接收,并通过串口将数据打印出来 */
            printf("接收到消息队列数据ucQueueMsgValue = %d\r\n", ucQueueMsgValue);
        }
        else
        {
            /* 超时 */
            BEEP_TOGGLE; 
        }
    }
        
            

}

复制代码

复制代码
void vTaskLed1(void *pvParameters)
{
    MSG_T *ptMsg;
    BaseType_t xResult;
    const TickType_t xMaxBlockTime = pdMS_TO_TICKS(2000); /* 设置最大等待时间为200ms */
    
    while(1)
    {
            xResult = xQueueReceive(xQueue2,                   /* 消息队列句柄 */
                                                            (void *)&ptMsg,             /* 这里获取的是结构体的地址 */
                                                            (TickType_t)xMaxBlockTime);/* 设置阻塞时间 */
            
            
            if(xResult == pdPASS)
            {
                /* 成功接收,并通过串口将数据打印出来 */
                printf("接收到消息队列数据ptMsg->ucMessageID = %d\r\n", ptMsg->ucMessageID);
                printf("接收到消息队列数据ptMsg->ulData[0] = %d\r\n", ptMsg->ulData[0]);
                printf("接收到消息队列数据ptMsg->usData[0] = %d\r\n", ptMsg->usData[0]);
            }
            else
            {
                    LED2_TOGGLE;
            }
    }
}

复制代码

 实验通过AppObjCreate函数创建两个队列消息,容量都是10个消息,队列1,2分别为uint8_t和struct Msg *类型,按键K1,实现队列1一个计数的增加,然后在Beep任务中接收这个变化的值,任务2实现结构体元素的增加,在LED任务中接收这个增量并打印出来。需要说明的是,freertos消息队列是通过副本机制传递的,而不是引用,

我们查看底层实现,

 freertos通过使用memcpy复制的内容。以简单的数据元素为例:

uint8_t ucCount = 0;

xQueueSend(xQueue1,(void *) &ucCount,(TickType_t)10)

这里是发送队列消息函数,下面看接收:

uint8_t ucQueueMsgValue;

xQueueReceive(xQueue1, /* 消息队列句柄 */
(void *)&ucQueueMsgValue, /* 存储接收到的数据到变量ucQueueMsgValue中 */
(TickType_t)xMaxBlockTime)

这里是最简单的uint_8类型元素,要想把发送函数的uint_8定义的数据,包括该数据在发送函数之前被更改后的值发送给接收函数,我们需要传递给发送函数send一个uint_8定义数据的地址,这样可以通过地址传递到memcpy函数,实现复制,这也就是为什么上面说的freertos的消息队列不是引用而是复制,要是引用的话,可以直接传这个uint_8类型的数据,而我们此时在freertos操作系统上,是副本传递,通过memcpy,所以需要给uint_8类型数据的地址。

这个或许并不具有什么迷惑性,但是,官方的参考demo,要是不认真理解一下,是想不通的。

在本次实验中传递一个结构体就是官方的参考历程:

发送函数:

MSG_T   *ptMsg;//MSG是个结构体

ptMsg = &g_tMsg;//g_tMsg是一个结构实体而且是全局区定义的
/* 初始化数组 */
ptMsg->ucMessageID = 0;
ptMsg->ulData[0] = 0;
ptMsg->usData[0] = 0;

xQueueSend(xQueue2, /* 消息队列句柄 */
(void *) &ptMsg, /* 发送结构体指针变量ptMsg的地址 */
(TickType_t)10)

 接收函数:

MSG_T *ptMsg;

xQueueReceive(xQueue2, /* 消息队列句柄 */
(void *)&ptMsg, /* 这里获取的是结构体的地址 */
(TickType_t)xMaxBlockTime);/* 设置阻塞时间 */

这里的关键就在第二个参数ptMsg,它已经是指针了,为什么还要取地址,这样不是一个二级指针了吗,而它的参数是void *,给人的感觉应该就是传一个地址,虽然二级指针也是地址,但是总觉得不应该设计成二级指针赋值给一个一级指针,哪怕你是void*。但是我们既然使用了freertos,就要遵循别人的设计,别人这样做,肯定有自己的道理,我们做到熟练应用即可。试想,消息发送函数,要发送数据,要得到这个数据的地址以给memcopy,如果传递的数据本身就是地址(指针),那么我们要把这个地址传到接收函数去,就应该得到此时指针的地址才行,也就是传递一个指针的值,注意不是指针指向的值。关键我们要通过memcpy函数,传递一个指针的值通过memcpy必然是需要二级指针的,这样才可以操作一级指针的值,这样也就可以理解为什么ptMsg已经是指针了,却还是要传递ptMsg的地址,因为只有这样,才可以通过memcpy函数把ptMsg指针的值给到接收函数的指针,这样在接收函数中操作这个结构体类型的指针,就可以得到发送端的数据。这样做的好处是,避免了大数据的拷贝,只拷贝指针,提高了效率,但是使用指针,一定不要在栈空间开辟,这也是为什么我们定义g_tMsg结构体实体在全局区。但是freertos任务中一直有while(1),元素生命周期一直都在,此时还是可以使用局部变量做数据传递工具,但是这样的编程模式应该摒弃,我们采用全局区开辟的空间。更多参见下一篇随笔。

那么你可能会问了,那我直接给指针ptMsg看看行不行呢,不给指针的地址即&ptMsg。答案是肯定的,不行。给ptMsg,相当于把ptMsg指向的数据给了接收端,而freertos要求是的,你传一个你想要发送消息的地址,我们想要发送的消息是ptMsg,它的地址是&ptMsg,所以我们必须传递&ptMsg。并不能简单看类型是否完全贴切,要看源码内部实现,毕竟强制类型转换太霸道。

再者,你还是觉得这样很诧异,那么你可以使用结构,而不要使用结构体指针,这样你传递的时候就是这个结构的指针了。但是注意,使用结构本身不使用结构体指针的时候,创建消息队列里面的siezof要改成结构体而不再是上面的结构体指针:

xQueue2 = xQueueCreate(10, sizeof(struct Msg ));

当然后面的->操作,要改成 . 操作。

实验现象如下:

 最后说两句:

而我测试了,深度给1,但我发送两个消息,程序还是可以工作,(并不是我给队列深度为1,就只能有一个队列消息发送函数)这和发送接收的允许阻塞时间有关。

所以,在等待时间合适的情况下,深度只给1,还是可以发送多次的。

相关文章:

  • 【GIT】git 合并分支和解决冲突
  • 【GIT】使用git pull文件时和本地文件冲突怎么办?
  • 【GIT】Git、GitHub、GitLab三者之间的联系以及区别
  • 【GIT】git-commit Angular规范--- 编辑中
  • 【分屏】2秒钟实现 Windows窗口多分屏的进阶技巧
  • 【GIT】Commit message 和 Change log 编写指南
  • while(1)和for(;;)的区别
  • 【GitFlow】GIT 工作流(GitFlow-如何使用Git的约定)
  • 【mesh】ESP32 官方文档(十六)ESP-MESH 编程指南
  • 【定位】TOF与TDOA
  • 【工具】source insight的使用方法整理|sourceinsight
  • 【大小端】大小端(数据在内存中的存储)
  • 【VS】vs2017无法打开“stdio.h”等源文件!
  • 【变量长度】int、long、short等在windows和linux/ 32位和64位下的长度(long long 才能保证64位)...
  • 【工具】二进制协议模拟测试工具(TcpEngine)使用简介
  • 2017-08-04 前端日报
  • C++回声服务器_9-epoll边缘触发模式版本服务器
  • CentOS7 安装JDK
  • ES6系统学习----从Apollo Client看解构赋值
  • Java|序列化异常StreamCorruptedException的解决方法
  • JS笔记四:作用域、变量(函数)提升
  • Linux CTF 逆向入门
  • mongodb--安装和初步使用教程
  • vue从入门到进阶:计算属性computed与侦听器watch(三)
  • 机器学习 vs. 深度学习
  • 基于Android乐音识别(2)
  • 驱动程序原理
  • # Python csv、xlsx、json、二进制(MP3) 文件读写基本使用
  • (10)ATF MMU转换表
  • (2021|NIPS,扩散,无条件分数估计,条件分数估计)无分类器引导扩散
  • (ResultSet.TYPE_SCROLL_INSENSITIVE,ResultSet.CONCUR_READ_ONLY)讲解
  • (笔试题)分解质因式
  • (附源码)python旅游推荐系统 毕业设计 250623
  • (过滤器)Filter和(监听器)listener
  • (九)One-Wire总线-DS18B20
  • (入门自用)--C++--抽象类--多态原理--虚表--1020
  • (万字长文)Spring的核心知识尽揽其中
  • .NET序列化 serializable,反序列化
  • .sh 的运行
  • :“Failed to access IIS metabase”解决方法
  • :如何用SQL脚本保存存储过程返回的结果集
  • []sim300 GPRS数据收发程序
  • [codeforces]Levko and Permutation
  • [CSS]CSS 的背景
  • [Design Pattern] 工厂方法模式
  • [HOW TO]怎么在iPhone程序中实现可多选可搜索按字母排序的联系人选择器
  • [JavaScript]如何讓IE9, IE8, IE7, IE6關閉視窗時不彈出對話訊息
  • [LeetCode] Ransom Note 赎金条
  • [Lua实战]整理Lua中忽略的问题
  • [luogu P1527]矩阵乘法(矩形k小)
  • [na]wireshark抓包排错-tcp.flags.reset
  • [ndss 2023]确保联邦敏感主题分类免受中毒攻击
  • [node] Node.js的文件系统
  • [NowCoder]牛客OI周赛3
  • [PHP]加密解密函数