数据队列的用因及用法

环形数据队列

在日常收发数据过程中,尤其多线程操作,数据的收发需要用到数据队列去处理,那么为什么要使用数据队列?什么是数据队列呢?

常规存储机制弊端

在接收或者发送数据的时候,你的数据的存储机制是什么样的呢 ?

  • 是否是采用下述方式?
#define MAX_BUF_SIZE    2048

unsigned short  recv_len;
unsigned char   recv_buf[MAX_BUF_SIZE];

int main()
{
    ......
    while(1)
    {
        recv_len = receive(recv_buf);
        ......
    }
}
  • 此种方法如果在单线程中问题不大,但是不适合多线程,多线程中,通常接收和发送函数是和主线程分离开来的,例如:

#define MAX_BUF_SIZE    2048

unsigned short  recv_len;
unsigned char   recv_buf[MAX_BUF_SIZE];

void *recv_thread(void *arg)
{   
    int ret;

    while(1)
    {   
        ret = receive(recv_buf); /* 如果接收到数据,则返回值为接收字节数 */
        if(ret > 0)
        {
            recv_len = ret;
        }
    }
}

int main()
{   
    pthread_t thread_other;
    ......
    
    /* 创建接收线程,主要作用是负责接收数据 */
    pthread_create (&thread_other, NULL, recv_thread, NULL);

    while(1)
    {
        if(recv_len > 0)
        {
            /* 处理接收 buf */
            ......
        }
    }
    return 0;
}

代码弊端:上述代码模仿单线程的接收处理,但是显然问题很大,因为处理和接收在不同线程,处理线程正在处理数据过程中,有可能接收线程又接收到新数据,导致处理线程处理的数据被覆盖掉,会导致意想不到的后果。

解决方法:多线程操作中,我们希望:如果前一次接收到的数据还没有处理结束,此时又有新数据到来,我们希望新数据不会将未处理的数据覆盖掉,而是存储到未处理数据后面,等上一次数据处理结束后再处理新数据,这就是数据队列的存储机制。

数据队列的实现

数据队列的核心就是数据的入栈和出栈。下述代码中,Data_Queue 就是数据队列,PutDataDropData 分别是数据的入栈和出栈:


#define MAX_DATA_SIZE   2048

typedef struct  
{
    unsigned short start;               //目前处理到的数据索引
    unsigned short next;                //最新入栈的数据索引
    unsigned short count;               //待处理数据数量
    unsigned short size;                //缓冲区大小
    unsigned char  buf[MAX_DATA_SIZE];  //数据缓冲区
}Data_Queue;

/*******************************************************************
* funcname:	QueueInit(Data_Queue *data_queue)
* para:	    data_queue-数据队列
* function:	数据队列初始化
* return:	
********************************************************************/
void QueueInit(Data_Queue *data_queue)
{
    (*data_queue).next = 0;
    (*data_queue).start = 0;
    (*data_queue).count = 0;
    (*data_queue).size = MAX_DATA_SIZE;
}

/*******************************************************************
* funcname:	PutData
* para:	    data_queue-数据队列,data-入栈数据
* function:	数据入栈
* return:	
********************************************************************/
void PutData(Data_Queue *data_queue, unsigned char data)
{
    (*data_queue).buf[(*data_queue).next++] = data;         //数据入栈

    if((*data_queue).next >= (*data_queue).size)            //入栈数据索引超过到缓冲区最大索引后,归零
    {
        (*data_queue).next = 0;
    }

    (*data_queue).count++;
    if((*data_queue).count >= (*data_queue).size)           //未处理的数据超多缓冲区容量后,舍弃旧数据,保留新数据
    {
        (*data_queue).count = (*data_queue).size;
        (*data_queue).start++;
        if((*data_queue).start >= (*data_queue).size)       //已处理数据索引超过到缓冲区最大索引后,归零
        {
            (*data_queue).start = 0;
        }
    }
}

/*******************************************************************
* funcname:	DropData
* para:	    data_queue-数据队列
* function:	数据出栈
* return:	
********************************************************************/
void DropData(Data_Queue *data_queue)
{
    if(((*data_queue).next == (*data_queue).start) && 
        ((*data_queue).count != (*data_queue).size))       //数据处理完毕直接返回
    {
        (*data_queue).count = 0;
        return;
    }

    (*data_queue).start++;                              //已处理数据索引 + 1
    if((*data_queue).start >= (*data_queue).size)
    {
        (*data_queue).start = 0;
    }

    if((*data_queue).count > 0)                         //未处理数据 -1
    {
        (*data_queue).count--;
    }
    
    return;
    

}

数据队列实战

1.数据队存储机制

此示例可以解决常规存储机制的弊端:


#define MAX_BUF_SIZE    2048

typedef struct  
{
    unsigned short start;               //目前处理到的数据索引
    unsigned short next;                //最新入栈的数据索引
    unsigned short count;               //待处理数据数量
    unsigned short size;                //缓冲区大小
    unsigned char  buf[MAX_BUF_SIZE];   //数据缓冲区
}Data_Queue;

Data_Queue data_queue;

void *recv_thread(void *arg)
{   
    int ret,i;
    unsigned short  recv_len;
    unsigned char   recv_buf[MAX_BUF_SIZE];

    while(1)
    {   
        recv_len = receive(recv_buf); /* 如果接收到数据,则返回值为接收字节数 */
        if(recv_len > 0)
        {
            for(i=0;i<recv_len;i++)
            {
                PutData(&data_queue,recv_buf[i]);
            }
        }
    }
}

int read(unsigned char *read_buf)
{   
    int i = 0;
    while(data_queue.count)
    {
        read_buf[i++] = data_queue.buf[start];
        DropData(&data_queue);
    }

    return i;
}

int main()
{   
    unsigned char read_buf[MAX_BUF_SIZE];
    pthread_t thread_other;

    /* 初始化数据队列 */
    QueueInit(&data_queue);
    ......
    
    /* 创建接收线程,主要作用是负责接收数据 */
    pthread_create (&thread_other, NULL, recv_thread, NULL);

    while(1)
    {
        if(read(read_buf) > 0)
        {
            /* 处理接收 buf */
            ......
        }
    }
    return 0;
}


2.利用数据队列处理接收到的特定字段

我们期望的是接收到的每个消息帧都是我们所需要的完整报文,但是现实往往不会和我们期望的一样。即:一帧消息里面往往包含多组我们需要的报文,我们需要将多组报文都处理;或者说多帧消息才能组成一组我们需要的报文段,我们需要将多个消息帧组成完整的报文段后再处理。这两个难题我们依然可以使用数据队列来解决。

例如:我们需要的消息帧以 0x68 开头,0x16 结尾,帧头后两个字节表示帧长度,此长度不包括帧头帧尾,我们把这个消息帧称为 698 报文。

  1. 举例1 :一帧消息帧中包含多个 698 报文,依次处理
    示例报文1:68 30 00 01 05 01 00 00 00 00 00 00 52 D9 81 00 00 07 E4 03 0F 00 09 01 23 00 00 07 E4 08 03 01 0D 28 34 CC 01 07 E4 08 03 01 0D 28 34 CC 01 50 80 16
    示例报文2:68 1E 00 81 05 01 00 00 00 00 00 00 D2 B6 01 00 00 00 3C 07 E4 03 0F 00 09 01 23 00 00 3F ED 16
    接收到的消息帧中包含这两组示例报文,即:示例报文1+示例报文2:
    68 30 00 01 05 01 00 00 00 00 00 00 52 D9 81 00 00 07 E4 03 0F 00 09 01 23 00 00 07 E4 08 03 01 0D 28 34 CC 01 07 E4 08 03 01 0D 28 34 CC 01 50 80 16 68 1E 00 81 05 01 00 00 00 00 00 00 D2 B6 01 00 00 00 3C 07 E4 03 0F 00 09 01 23 00 00 3F ED 16
    但是这包含两个 698 报文 的消息帧中,两组报文我们需要按照先后关系做对应处理。

  2. 举例2:多个消息帧,组成一个 698报文。
    示例报文:68 30 00 01 05 01 00 00 00 00 00 00 52 D9 81 00 00 07 E4 03 0F 00 09 01 23 00 00 07 E4 08 03 01 0D 28 34 CC 01 07 E4 08 03 01 0D 28 34 CC 01 50 80 16,这是完整的一组 698 报文。
    接收到3段消息,组合而成才是完整的 698 报文:
    示例消息1:68 30 00 01 05 01 00 00 00 00 00 00 52 D9 81 00 00 07 E4 03 0F 00 09 01
    示例消息2:23 00 00 07 E4 08 03 01 0D 28 34 CC 01 07 E4 08 03 01 0D 28 34 CC
    示例消息3:01 50 80 16
    我们需要将 3 个消息帧组合成我们需要的 698 报文然后处理:

#define MAX_BUF_SIZE    2048

typedef struct  
{
    unsigned short start;               //目前处理到的数据索引
    unsigned short next;                //最新入栈的数据索引
    unsigned short count;               //待处理数据数量
    unsigned short size;                //缓冲区大小
    unsigned char  buf[MAX_BUF_SIZE];   //数据缓冲区
}Data_Queue;

Data_Queue data_queue;

void *recv_thread(void *arg)
{   
    int ret,i;
    unsigned short  recv_len;
    unsigned char   recv_buf[MAX_BUF_SIZE];

    while(1)
    {   
        recv_len = receive(recv_buf); /* 如果接收到数据,则返回值为接收字节数 */
        if(recv_len > 0)
        {
            for(i=0;i<recv_len;i++)
            {
                PutData(&data_queue,recv_buf[i]);
            }
        }
    }
}

/* 此函数可以解决 2 个例子中的问题
*  1. 一帧消息帧中包含多个 698 报文,依次处理
*  2. 多个消息帧,组成一个 698报文,然后再行处理
*  例如我们需要的消息帧以0x68开头,0x16结尾,帧头后两个字节表示帧长度,此长度不包括帧头帧尾 
*/
void read_buf(unsigned char *read_buf)
{
	int ret;
	int	datalen;
	
	if(data_queue.count > 0)
	{
		for(int i=0;i<data_queue.count;i++)
		{
			if(data_queue.buf[mod(data_queue.start+i,data_queue.size)] == 0x68)
			{
				datalen = data_queue.buf[mod(data_queue.start+i+1,data_queue.size)];
				datalen += data_queue.buf[mod(data_queue.start+i+2,data_queue.size)]*256;
				if(data_queue.buf[mod(data_queue.start+i+1+datalen,data_queue.size)]==0x16)
				{	
					while(i--)
						dropdata(&data_queue);
					printf("rec xxxframe,byte = %d\r\n ",datalen+2);
					for(int j=0;j<datalen+2;j++)
					{
						read_buf[j] = data_queue.buf[mod(data_queue.start,data_queue.size)];
						dropdata(data_queue);
						printf("%02X ",read_buf[j]);
					}
					printf("\r\n");
					retrun j;
				}
			}
		}
	}
	return 0;
}

int main()
{   
    unsigned char read_buf[MAX_BUF_SIZE];
    pthread_t thread_other;

    /* 初始化数据队列 */
    QueueInit(&data_queue);
    ......
    
    /* 创建接收线程,主要作用是负责接收数据 */
    pthread_create (&thread_other, NULL, recv_thread, NULL);

    while(1)
    {	
        if(read_buf(read_buf) > 0)
        {
            /* 处理接收 当前buf中的消息 */
            ......
        }
    }
    return 0;
}


总结

上述主要以接收为例,说明了使用数据队列的原因以及用法,发送数据其实也一样,只需要在发送前 PutData,然后在发送结束后,DropData 即可。

  • 4
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

汤梦飞123

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值