如果有问题,请加QQ群 891339868 进行交流
项目太忙,博客也荒废了,今天项目终于告一段落了,赶紧总结一下,省的忘记了。前一段时间的项目的一部分功能是实现服务器与串口设备之间进行指令交互,整体的网络架构是BS架构,上位机一般用的是json数据,串口设备使用的是串口,所以作为中间设备的网关,必须实现json数据与串口数据之间的转换,我使用的json库是Cjson,库比较简单,很小,直接在程序中调用“.h”和“.c”文件就可以了,这个无需多言,今天主要来记录一下具体的实现流程和细节。
由于服务器和网关、网关与串口设备交互比较频繁,而且作为工业应用,数据的完整性很重要,所以采用类似消息队列的方式进行处理,虽然会有一定的延时,但是对于人机交互这种操作,不会有太大的影响,下面详细描述一下实现的具体细节:
1、队列的工作流程
使用四个队列,分别是json数据接收队列、json数据发送队列、串口数据接收队列、串口数据发送队列,我这里使用的是多线程的处理方式,如果是裸机编程,可以使用各种中断和主循环相互配合,实现类似于多线程的效果,json数据的接收创建一个线程、json数据转为uart数据创建一个线程、串口数据发送创建一个线程、串口数据的接收创建一个线程、串口数据转为json数据创建一个线程、json数据的发送创建一个线程,一共是这六个线程,这六个线程独立运行,之间的交叉操作使用互斥锁进行互斥操作:
2、指令队列的创建
创建队列可以使用数组的方式,也可以使用链表的方式,这两种各有优缺点。使用数组优点简单可靠,缺点是不够灵活,队列的长度固定,在数据量比较小的情况下,浪费内存,在数据量大的情况下,有可能内存溢出;使用链表的优点是队列长度可长可短,可以“量数据而行”,缺点是操作复杂,每次有新数据加入时,都需要动态申请内存,动态申请有失败的可能。我的观点是反正都有操作内存失败的可能,那就用链表,申请内存失败了,可以重新申请,使用数组的话,溢出就是溢出了,没有办法解决。对于这种FIFO操作,单向链表就可以满足要求,而对于队列的操作,只有四种方式,创建队列、入列、查看队列是否有成员、出列,队列一旦创建,不存在删除的可能,所以代码只要实现这四种操作就OK了。
(1)、队列的初始化,也就是创建队列
typedef struct _slist_node {//单向链表节点结构
struct _slist_node *p_next;//指向下一个节点位置
} slist_node_t;
typedef slist_node_t slist_head_t;//单向链表头节点
struct _queueCDT {//队列头结构
slist_head_t list_head;//包含一个单向链表头
};
typedef struct _queueCDT *queueADT;
//单向链表的初始化
ssize_t slist_init(slist_head_t *p_head)
{
if (p_head == NULL) {
return -1;
}
p_head->p_next = NULL;
return 0;
}
//创建一个队列,返回队列地址
queueADT newQueue(void)
{
queueADT queue;
queue = (queueADT)malloc(sizeof(struct _queueCDT));
slist_init(&queue->list_head);
return queue;
}
上图中的代码就是定义了一些结构数据和创建了一个新队列,首先创建一个单向链表的结构,再对链表进行初始化,其实就是将链表中的元素置位NULL,而创建一个队列,申请一块内存,并在这个地址上创建一个单向链表,这个时候,当前的队列是个空队列,只占两个字节,所以使用链表创建队列,在数据量小的情况下,非常节省内存。
(2)、将数据入列,入列的操作就是申请一块儿数据,将数据复制到这块儿数据,并将这块儿内存的地址放到链表的尾部,如下所示:
struct json_data_t {//接收json数据后的存储格式结构
size_t data_length;//数据的长度
char string[256];//数据的内容
};
struct _json_queue_node_t {//json数据队列节点结构
slist_node_t node;//单向链表节点信息
struct json_data_t data;//队列节点的数据
};
//在单向链表上添加一个节点
ssize_t slist_add(slist_head_t *p_head, slist_node_t *p_pos, slist_node_t *p_node)
{
p_node->p_next = p_pos->p_next;
p_pos->p_next = p_node;
return 0;
}
//得到单向链表中当前节点的前一个节点地址
slist_node_t *slist_prev_get(slist_head_t *p_head, slist_node_t *p_pos)
{
slist_node_t *p_tmp = p_head;
while ((p_tmp != NULL)&&(p_tmp->p_next!=p_pos)) {
p_tmp = p_tmp->p_next;
}
return p_tmp;
}
//得到当前单向链表的最后一个节点地址
slist_node_t *slist_tail_get(slist_head_t *p_head)
{
return slist_prev_get(p_head, NULL);
}
//在当前单向链表的尾部增加一个节点
ssize_t slist_add_tail(slist_head_t *p_head, slist_node_t *p_node)
{
slist_node_t *p_tmp = slist_tail_get(p_head);
return slist_add(p_head, p_tmp, p_node);
}
//将json数据加入到当前的队列中
ssize_t json_inQueue(queueADT queue, json_queueElementT value)
{
if (queueIsFull(queue) == 0) {
return -1;
}
struct _json_queue_node_t *p_node = (struct _json_queue_node_t *)calloc(1,
sizeof(struct _json_queue_node_t));
p_node->data.data_length = value->data_length;//拷贝数据的长度
strncpy(p_node->data.string, value->string, p_node->data.data_length);//拷贝数据
slist_add_tail(&queue->list_head, (slist_node_t *)p_node);
return 0;
}
我这里是使用json数据的队列入列数据举的例子,串口数据操作方式大同小异,链表的操作完全一致。这个入列操作就是很简单的两步,第一步就是根据加入队列的数据大小,申请一块儿内存,将数据复制到申请的内存中;第二步就是找到当前队列使用的单向链表的尾部,将当前尾部的节点中的信息与新增加的节点的信息进行更新,具体的就是将原来尾部节点的指向下一个节点的指针指向新加入节点的地址,将新加入的节点更新为当前链表的尾,将新节点的指向下一个节点的指针指向原尾部节点指向的位置,说起来有点儿绕,其实很简单,举个例子,你是你们团队的底层,直接干活的,你有上级,你的上级有上级,依次类推,都是直接管理,原来都是上面一级一级下发任务,最后你干活,突然有一天,来了个新人,你首先变成他的领导(就是将他的地址给你,你指向他),然后让他干活(就是让他指向原来你指向的地址)。
(3)、检查当前队列是否为空,这个比较简单,就是查看当前队列所在的链表的头地址与尾地址是否相同,如果相同的话,该队列就是空的,如下所示:
ssize_t queueIsEmpty(queueADT queue)
{
return (slist_begin_get(&queue->list_head) == slist_end_get(&queue->list_head));
}
(4)、队列数据出列,实质就是将链表头指向的内存数据拷贝出来,并且更新链表头部数据,如下所示:
//删除链表中某个节点的所有信息
ssize_t slist_del(slist_head_t *p_head, slist_node_t *p_node)
{
slist_node_t *p_prev = slist_prev_get(p_head, p_node);
if (p_prev) {
p_prev->p_next = p_node->p_next;
p_node->p_next = NULL;
return 0;
}
return -1;
}
//json数据出列
ssize_t json_outQueue(queueADT queue, json_queueElementT p_value)
{
if (queueIsEmpty(queue)) {
return -1;
}
slist_node_t *p_node = slist_begin_get(&queue->list_head);
p_value->data_length = ((struct _json_queue_node_t *)p_node)->data.data_length;//拷贝数据的长度
strncpy(p_value->string, ((struct _json_queue_node_t *)p_node)->data.string, \
p_value->data_length);//拷贝数据
slist_del(&queue->list_head, p_node);
free(p_node);
return 0;
}
还用上个例子,这段代码的作用就是找到现在部门的最高领导(找到链表的头部),把该领导手里的所有有价值数据拷贝一下(拷贝里面地址指向的数据),把该领导给开除(删除该节点),同时所有人都升了一级。
记录到这里,数据在队列中操作基本上就搞清楚了,指令队列的操作关键点有下面几点:1、创建一个单向链表;2、申请内存拷贝数据,将内存地址加入到单向链表尾部;3、检查链表的头尾是否一致,如果不一致,队列中有数据;4、找到单向链表的头部,取出地址,在该地址指向的位置拷贝数据,并更新单向链表的头部。
好了,今天就先记录到这里!