以下为控制器和小车组件的实验报告,不包含导航器,路径规划器,显示器
一、课程设计(综合实验)的目的与要求
软件体系结构是软件工程专业的专业必修课。软件体系结构是软件工程方法学的一个分支,开设本课程的目的是使学生在了解了软件工程基础原理、方法、过程的基础上进一步掌握软件结构设计的基本理论和方法,培养设计软件结构的基本能力。本课程的基本内容包括软件体系结构的基本概念、发展现状、软件体系结构风格、传统的软件体系结构、现代软件体系结构等。
本课程实验的目标是培养学生的基础编程能力,其培养目标是程序员;软件工程课程使学生上升到软件系统的认识,其培养目标是软件工程师。本课程教学内容属于软件工程的概要设计阶段的方法学,其培养目标是软件架构师。
二、设计(实验)正文
- 实验概述
- 实验内容
本次实验目标为设计一个分布式多车协作探索地图程序,要求各组件间共享数据,组件独立。
-
- 分工合作
本次实验为分组进行,二组的分工如下:
:控制器、小车
:导航器
:路径规划器
:显示器
以下设计我将重点从控制器模块和小车模块进行解释
- 全局软件结构设计
- 总体设计逻辑
分布式多车寻路系统采用黑板风格架构,由5个组件组成:控制器,小车,路径规划器,导航器,显示器。各组件之间通过redis和rabbitmq通信,实现协作,达到了解耦的目的。
-
- 全局软件结构图
-
- 构件清单(详细描写控制器和小车组件,其他组件的功能清单见组员报告)
构件1:
构件名称:黑板
部署方式:独立部署
黑板中需要存储的数据有:
- 障碍物地图:位图 bitmap
- 点亮地图:位图 bitmap
- 汽车信息: WorkList:任务队列:string
(哈希存储) IsWork:正在工作标志:string
PositionTarget:目标位置:string
History:历史信息
PositionNow:当前位置
Navigator:导航标志
- 最大汽车数量:string
- 探索结束标志:string
- 用户信息:list存储
构件2:
构件名称:控制器
部署方式:独立部署
调用方式:消息驱动
功能清单:
功能A:找到redis中格式匹配某模式的键值数,找到小车数量
函数名称:countKeysByPattern
输入:Jedis jedis(redis数据库连接)
String pattern(匹配模式)
输出:int count(redis中匹配该模式的键的数量)
参考数据格式:jedis:jedis = ConnectionUtil.getJedis();
pattern:类似“Car*”的正则字符串
参数示例:jedis = ConnectionUtil.getJedis();
pattern=“Car*”
功能B: 构造函数,初始化,定义一个发送时钟的线程任务
函数名称:Controller
输入:无
输出:clock(向小车mq队列连接的交换机发送时刻消息)
参考数据格式: string
参数示例: “34”
功能C: 线程任务,扫描redis中小车,为没有任务的小车分配导航器和路径规划器
函数名称:run
输入:无
输出:CarID(向导航器mq和路径规划器mq发送小车编号)
参考数据格式: string
参数示例: “Car001”
构件3:
构件名称:小车组件
部署方式:独立部署
调用方式:消息驱动
功能清单:
功能A:启动定时器,为砸锁计时
函数名称:startTimer
输入:无
输出:无
功能B:检查状态持续时间,防止小车在一个状态持续太久
函数名称:checkStateDuration
输入:无
输出:redis中小车任务队列,导航标志(置空,置0)
参考数据格式:string
参数示例:“”,“0”
功能C:从rabbitmq接收消息后的处理函数,进行状态转换和判断小车是否移动
函数名称:deliverCallback
输入:message(从rabbitmq中接收到的消息,这里为控制器生成的时刻)
输出:无
参考数据格式:string
参数示例:“23”
功能D:线程函数,从rabbitmq中接收消息,并转到处理函数
函数名称:run
输入:clock(时刻)
输出:无
参考数据格式:string
参数示例:“23”
构件4:
构件名称:路径规划器
部署方式:独立部署
调用方式:消息驱动
功能清单:(详细功能见白雪实验报告)
构件5:
构件名称:导航器
部署方式:独立部署
调用方式:消息驱动
功能清单:(详细功能见吉扬实验报告)
构件6:
构件名称:显示器
部署方式:独立部署
调用方式:消息驱动
功能清单:(详细功能见王宛婷实验报告)
-
- 连接件清单
连接件1:
连接件名称:小车mq队列,每个小车一个
部署方式:mq解耦
队列名称:Car001,Car002···
队列方式:先进先出队列
收发频次:实时收发
消息格式:string
消息示例:“23”
连接件2:
连接件名称:路径规划mq队列,共用一个
部署方式:mq解耦
队列名称:PointQueue
队列方式:先进先出队列
收发频次:实时收发
消息格式:string
消息示例:“Car003”
连接件3:
连接件名称:导航mq队列,共用一个
部署方式:mq解耦
队列名称:NavigatorQueue
队列方式:先进先出队列
收发频次:实时收发
消息格式:string
消息示例:“Car003”
连接件4:
连接件名称:Java-Redis客户端连接
部署方式:函数调用
消息格式:函数名称:get
参数名称:key
参数格式:String
参数定义域:Redis键名的集合
举例:jedis.get("myKey");
连接件5:
连接件名称:Java-Redis客户端连接
部署方式:函数调用
消息格式:函数名称:hget
参数名称:key1,key2
参数格式:String
参数定义域:Redis键名的集合
举例:jedis.hget("Car001","WorkList");
连接件6:
连接件名称:Java-Redis客户端连接
部署方式:函数调用
消息格式:函数名称:set
参数名称:key
参数格式:String
参数定义域:Redis键名的集合
举例:jedis.set("myKey");
连接件7:
连接件名称:Java-Redis客户端连接
部署方式:函数调用
消息格式:函数名称:hset
参数名称:key1,key2,value
参数格式:String
参数定义域:Redis键名的集合
举例:jedis.hget("Car001","Navigator","0");
连接件8:
连接件名称:Java-Redis客户端连接
部署方式:函数调用
消息格式:函数名称:setbit
参数名称:key1,key2
参数格式:String
参数定义域:Redis键名的集合
举例:jedis.setbit("mapBarrier", bitIndex4, true);
连接件9:
连接件名称:Java-Redis客户端连接
部署方式:函数调用
消息格式:函数名称:getbit
参数名称:key1,key2
参数格式:String
参数定义域:Redis键名的集合
举例:jedis.getbit("mapBarrier", bitIndex4);
-
- 运行规则与约束
- 控制器负责扫描redis中为空的小车队列,每找到一个,就将这个小车编号发到导航器和路径规划器队列中,并更改redis中小车的导航标志为1,表示正在为该车规划路径。导航器还定时每秒向rabbitmq中的小车交换机发送该时刻的时间(第几秒),广播到所有小车。
- 路径规划器负责监视一个路径规划mq队列,若收到小车编号,则为该小车分配目标点,写入redis
- 导航器负责监视一个导航mq队列,若收到小车编号,则读出路径规划期生成的终点,为小车规划一条路径,写入redis
- 小车组件每接收到一条时钟消息,就判断自身是否需要状态变化,并进行相应操作。共分为4个状态:正在运行,已分配,未分配,待启动
- 显示器负责从redis中读地图信息和小车位置,路径信息,绘制在屏幕上。
- 运行规则与约束
- 小车组件描述
- 功能描述
小车组件负责不断接收mq队列里的时钟消息,接收到一个就根据自身当前状态进行一次相应操作
-
- 工作流程
- 小车刚创建时处于未分配状态,接收到时钟后,检查redis中的导航标志,如果为0,则不变,若为1,则改变状态为已分配。
- 小车处于已分配状态时,先读redis中的worklist,若为[{“x”:-999,”y”:-999}]或[]时,则说明本次导航失败,置状态为未分配。若为其他且不为空时,设置小车状态为待启动
- 小车处于待启动状态时,从redis中读出任务路径,如果读出的为空,状态置为未分配。如果不为空,则存任务队列到属性。取出队列中的第一个位置,判断是否点亮,若未点亮则点亮。判断这个位置是否为障碍物,是障碍物则转换状态为未分配。如果不是障碍物,则设小车下一步坐标为该点,并将这个位置在障碍物地图暂时设为1,防止小车相撞,然后转换状态为正在运行。
- 如果状态为正在运行,先判断下一步是否为空,为空则置为未分配。否则将地图上一步的障碍物地图位置置0,然后把当前位置置1,来实现小车在的位置是障碍物。改变小车当前位置属性,实现移动。然后记录该时刻的历史信息。
- 工作流程
然后判断属性list是否为空,若为空,则说明本条路径已经走完了,改变状态为未分配。如果不为空,则取出第一个位置,判断是否已点亮,若未点亮则点亮。判断是否为障碍物。如果是障碍物,转换状态为未分配。如果不是,则设置小车下一步位置为该点。并设置障碍物地图为1,防止两辆车走到一个格子。
-
-
- 除了状态转换逻辑外,还设置了超时强制转换状态,当小车处于某个状态超过15秒时,强制转换状态为未分配,置导航标志为0,并清空任务队列。
- 运行状态变化描述
-
-
- 接口设计
- 小车mq队列:Car001,Car002···每个小车一个,实现接收控制器传来的时钟
- 接口设计
消息格式:String
消息示例:“23”
-
-
- 黑板中的小车属性:
-
WorkList:任务队列:string:[{“x”:1,”y”:2},{“x”:1,”y”:3}]
jedis.hget(carID, "WorkList");
IsWork:正在工作标志:string: “0”
jedis.hget(carID,"isWork")
jedis.hset(carID,"isWork","1")
PositionTarget:目标位置:string :{“x”:1,”y”:2}(小车没有直接使用)
History:历史信息:string:[{“count”:2, “time”:23, “x”:2, “y”:4},]
jedis.hset(carID,"History",str);
PositionNow:当前位置:string: {“x”:6,”y”:5}
jedis.hset(carID,"PositionNow","{\"x\":0,\"y\":0}");
Navigator:导航标志:string : “0”
jedis.hset(carID,"Navigator","0");
jedis.hget(carID,"Navigator");
-
-
- 障碍物地图 bitmap
-
jedis.getbit(MAP_KEY1,bitIndex5);
jedis.setbit(MAP_KEY1,bitIndex5, “0”);
点亮地图 bitmap
jedis.getbit(MAP_KEY2,bitIndex5);
jedis.setbit(MAP_KEY2,bitIndex5, “1”);
-
- 流程图
- 控制器组件描述
- 功能描述
控制器用来协调其他各组件的合作
-
- 工作流程
- 控制器循环检查redis中各小车任务队列是否为空,如果任务队列为空且导航标志为0,则导航mq队列和路径规划器mq队列发送该小车的编号,并将redis中该小车导航标志置1.
- 同时控制器定时每秒向所有小车mq队列连接的交换机发送当前时刻。
- 接口设计
- 小车mq队列:Car001,Car002···每个小车一个。
- 工作流程
消息格式:String
消息示例:“23”
-
-
- 路径规划器mq队列:PointQueue
-
消息格式:String
消息示例:“Car001”
-
-
- 导航器mq队列:NavigatorQueue
-
消息格式:String
消息示例:“Car001”
-
-
- 黑板中的小车属性:
-
WorkList:任务队列:string:[{“x”:1,”y”:2},{“x”:1,”y”:3}]
jedis.hget(carID, "WorkList");
Navigator:导航标志:string : “0”
jedis.hset(carID,"Navigator","1");
jedis.hget(carID,"Navigator");
-
- 流程图
三、课程设计(综合实验)总结或结论
- 实验总结
- 本次实验为较大的综合性实验,同时考验了课内知识和小组协作能力。经过三周时间,我们完成了实验要求的内容,并进行了改进。
- 从控制器组件和小车组件来看,刚开始实验时并不知道要怎么找到未分配导航器和路径规划器的小车。之后通过组内讨论和询问老师,发现可以给小车设置状态,通过时钟控制状态转换来实现所有小车的同步变化。
- 但控制器的设计还可以改进为先向路径规划器发送小车编号,等待路径规划器规划出一个目标点后,再向导航器发送小车编号,这样设计可以避免路径规划器规划出终点前导航器资源的占用。
四、参考文献
附录(设计流程图、程序、表格、数据等)