由于软件工程和软件体系结构为综合实验,以下报告只是控制器和小车组件部分
二、设计正文
1.可行性分析报告
1.1项目简介
1.1.1项目目标
本次实验的目标是构建一个分布式多车寻路系统,实现在未知区域内多辆小车进行地图探索。系统以模拟紧急救援系统为背景,旨在提高紧急救援和地图搜索的效率和准确性。通过多车协同搜索和分布式存储,实现搜索算法的仿真和路径生成。
1.1.2系统的简要描述,主要功能
用户可以通过注册登录该寻路系统,以用户管理员身份登陆后可以对当前系统所有注册用户进行查看和管理。以普通用户身份登录后可以查看当前探索进度。以配置管理员身份登录后,可以设置探索地图大小,最多存在的小车数量,并可以进行障碍物随机生成和手动添加移除。
当一次探索完后,配置管理员可以进入回放界面,手动输入开始时刻后即可开始回放,界面上同时实时显示分析数据。
1.2对现有系统的分析
1.2.1处理流程和数据流程(多泳道图)
如图2-1
图2-1分布式多车系统多泳道图
1.2.2对现有系统的其他说明
(1)维护:系统采用分布式结构,各组件之间通过消息中间件通信,大大降低了各组件间的耦合度,使各个组件易于维护。
(2)人员:需要专业技术人员进行系统开发和维护。
(3)设备:因为采用了分布式部署,各组件可运行在不同设备上,所需设备数量可能增加,但对设备的硬件的要求会降低
(4)局限性:因为各组件之间使用了rabbitmq和redis进行通信,要求所有使用组件的设备都要在同一个网段内。
1.3技术可行性分析
本系统采用了java,python,c#语言来开发不同组件,基于IDEA,VS2022开发。
搜索算法采用A*算法,实现路径搜索和优化。
分布式存储使用Redis,通信采用Rabbitmq。
当前所运用技术成熟完备,支持系统实现。
1.4经济可行性分析
成本:对于硬件,系统开发可利用现有设备。相关费用主要包括软件设计、开发和维护费用。
收益:提高紧急救援和地图搜索的效率,有较高的社会和经济价值。
1.5社会因素可行性分析
1.5.1法律因素
系统符合数据隐私和网络安全的相关法律法规。且项目为独立开发,不会侵犯专利和版权。
1.5.2用户使用可行性
系统界面数量较少,功能清晰,易于使用,适合计算机使用能力不同层级的用户使用。
1.6可供选择的方案
(1)单机版探索系统:使用本地数据存储,所有组件都运行在本地。优点是所有组件可自由关闭,本地数据可随时修改。缺点是若组件都部署在同一个设备上,需要的计算资源较大,且难以实现系统的长时间运行。
(2)分布式探索系统:使用Redis分布式数据存储,组件独立设计,可部署在任意设备上,可以合理的利用计算资源,对设备要求降低。缺点是系统的设计和实现较为复杂。
2、软件需求规格说明书(SRS)
2.1需求概述
1)系统为分布式多车系统,核心功能为实现小车可探索未知的地图。实现障碍物地图的编辑,小车的移动,目标点的智能生成,路径的智能生成,小车探索地图并实时显示,探索结束可以观看回放和分析。各组件间耦合度低,通过消息中间件通信。
2)运行环境
安装JDK8版本以上
安装redis:5.0.1版本
安装Rabbitmq:4.10.0版本
2.2功能需求
2.2.1顶层需求描述 如图2-2
图2-2分布式多车系统顶层数据流图
2.2.2一层数据流图
小车子系统:如图2-3
图2-3小车模块二层数据流图
控制器子系统:如图2-4
图2-4控制器模块二层数据流图
2.2.3二层数据流图
小车子系统 如图2-5
图2-5小车模块三级数据流图
2.3数据描述
数据词典:非关系型数据库redis中的数据
CarID:Hash类型
CarID=WorkList+isWork+PositionTarget+History+PositionNow+Ligst+Navigator
WorkList= “[”+{ “{“ x”:”+{数字}+ “, “y”:”+{数字}+ “}”}+(“,”)+ “]”
isWork=[“1”|“0”]
PositionTarget=“{“ x”:”+{数字}+ “, “y”:”+{数字}+ “}”
History= “[”+{ “{“count”:”+{数字}+“,“time”:”+{数字}+“,“x”:”+{数字}+“,“y”:”+{数字}+“}”}+(“,”)+“]”
PositionNow=“{“ x”:”+{数字}+ “, “y”:”+{数字}+ “}”
Ligst={数字}
Navigator=[“1”|“0”]
MaxNum={数字}
isOver=[“1”|“0”]
mapBarrier:mapbit类型
mapLight:mapbit类型
ConfigAdmin:Hash类型
MagUsers:Hash类型
User:Hash类型
2.4性能需求
1)数据精确度
暂无
2)时间特性
因为是分布式系统,各个小车需要解决同步问题,我们拟定每秒控制器发送一个时钟消息,小车只有接收到消息后才会进行一步操作。规定显示器每0.8秒刷新一次,只重画改变的部分。
这样会给历史信息带上一个时间戳,回放时可以完全还原各小车在每个时刻的运行情况。
3)适应性
暂无
2.5运行需求
(1)用户界面
界面在设计上整体保持一致,包括视觉风格、操作逻辑和功能布局。同时尽可能简洁明了,避免不必要的复杂性。界面设计应考虑用户的需求和使用场景,确保界面易于使用,要符合用户的操作习惯,提供容错机制并给出及时反馈。
(2)硬件接口
暂无
(3)软件接口
输入:手动选择地图规格,手动添加、删除障碍物,手动输入最大小车数量,人工输入账号信息等
输出:实时显示地图和小车探索情况,显示小车预测路径
(4)故障处理
正常运行时不应该出错,若运行时遇到不可恢复的系统错误,需要保证数据不被破坏,进行数据备份。
2.6其它需求
小车和导航器都可以开多个,来加快探索速度。多个导航器可以保证小车可以及时得到路径。
3、软件设计说明书
3.1软件体系结构图
1)整体传统架构方框图 如图3-1
图3-1分布式多车系统传统架构图
2)层次架构图 如图3-2
图3-2分布式多车系统层次架构图
3)分布式系统的部署或组件分布
因为本系统为分布式系统,Rabbitmq和Redis均可部署到不同服务器,各组件都可部署到不同设备上,小车、导航器、显示器还可部署多个到不同设备。
3.2分布式多车系统的架构
如图3-3
图3-3分布式多车系统架构图
3.3数据设计
1)数据库设计/外部文件描述:暂无
2)内存保存的数据结构:暂无
3)共享数据 :给出为软件各个模块所共享的全局数据的结构和存取模式(与分布式多车有关的数据结构,redis…) 如图2-1
表3-1Redis键数据表
键 | 类型 | 含义 |
Car001、Car002··· | Hash类型 | 该车的状态、任务、位置等信息 |
MaxNum | String类型 | 最大允许创建小车数 |
isOver | String类型 | 探索结束标志 |
mapBarrier | bitmap类型 | 障碍物地图 |
mapLight | bitmap类型 | 点亮地图 |
User | List类型 | 用户名及密码 |
小车哈希中的具体数据:如表3-2
表3-2小车哈希内键数据
键 | 类型 | 含义 |
WorkList | String | 小车任务队列json |
isWork | String | 小车正在工作标志 |
PositionTarget | String | 小车目的位置 |
PositionNow | String | 小车当前位置 |
History | String | 小车历史路径信息 |
Navigator | String | 小车导航标志 |
Light | String | 小车点亮数 |
3.4详细设计——模块描述
3.4.1控制器模块描述
1)功能:控制器循环检查redis中各小车任务队列是否为空,如果任务队列为空且导航标志为0,则导航mq队列和路径规划器mq队列发送该小车的编号,并将redis中该小车导航标志置1。同时控制器定时每秒向所有小车mq队列连接的交换机发送当前时刻。
2)接口:没有直接调用的模块,分布式系统中的各组件都是通过Mq中间件和redis通信的。
(1)小车mq队列:Car001,Car002···每个小车一个。
消息格式:String
消息示例:“23”
(2)路径规划器mq队列:PointQueue
消息格式:String
消息示例:“Car001”
(3)导航器mq队列:NavigatorQueue
消息格式:String
消息示例:“Car001”
(4)黑板中的小车属性:
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");
3)数据:使用了Redis中小车导航标志
4)处理:如图3-4 所示
图3-4控制器流程图
3.4.2 小车模块描述
1)功能:小车组件负责不断接收mq队列里的时钟消息,接收到一个就根据自身当前状态进行一次相应操作。
(1)小车刚创建时处于未分配状态,接收到时钟后,检查redis中的导航标志,如果为0,则不变,若为1,则改变状态为已分配。
(2)除了状态转换逻辑外,还设置了超时强制转换状态,当小车处于某个状态超过15秒时,强制转换状态为未分配,置导航标志为0,并清空任务队列。
(3)然后判断属性list是否为空,若为空,则说明本条路径已经走完了,改变状态为未分配。如果不为空,则取出第一个位置,判断是否已点亮,若未点亮则点亮。判断是否为障碍物。如果是障碍物,转换状态为未分配。如果不是,则设置小车下一步位置为该点。并设置障碍物地图为1,防止两辆车走到一个格子。
(4)如果状态为正在运行,先判断下一步是否为空,为空则置为未分配。否则将地图上一步的障碍物地图位置置0,然后把当前位置置1,来实现小车在的位置是障碍物。改变小车当前位置属性,实现移动。然后记录该时刻的历史信息。
(5)小车处于待启动状态时,从redis中读出任务路径,如果读出的为空,状态置为未分配。如果不为空,则存任务队列到属性。取出队列中的第一个位置,判断是否点亮,若未点亮则点亮。判断这个位置是否为障碍物,是障碍物则转换状态为未分配。如果不是障碍物,则设小车下一步坐标为该点,并将这个位置在障碍物地图暂时设为1,防止小车相撞,然后转换状态为正在运行。
(6)小车处于已分配状态时,先读redis中的worklist,若为[{“x”:-999,”y”:-999}]或[]时,则说明本次导航失败,置状态为未分配。若为其他且不为空时,设置小车状态为待启动
2)接口:
不直接调用其他模块,与其他模块的通信通过Rabbitmq和Redis实现
(1)小车mq队列:Car001,Car002···每个小车一个,实现接收控制器传来的时钟
消息格式:String
消息示例:“23”
(2)黑板中的小车属性:
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")
(3)PositionTarget:目标位置:string :{“x”:1,”y”:2}(小车没有直接使用)
(4)History:历史信息:string:[{“count”:2, “time”:23, “x”:2, “y”:4},]
jedis.hset(carID,"History",str);
(5)PositionNow:当前位置:string: {“x”:6,”y”:5}
jedis.hset(carID,"PositionNow","{\"x\":0,\"y\":0}");
(6)Navigator:导航标志:string : “0”
jedis.hset(carID,"Navigator","0");
jedis.hget(carID,"Navigator");
(7)障碍物地图 bitmap
jedis.getbit(MAP_KEY1,bitIndex5);
jedis.setbit(MAP_KEY1,bitIndex5, “0”);
(8)点亮地图 bitmap
jedis.getbit(MAP_KEY2,bitIndex5);
jedis.setbit(MAP_KEY2,bitIndex5, “1”);
3)数据:使用了redis中小车位置、任务、标志等信息。
4)处理:如图3-5
图3-5小车数据流图
3.5接口设计
1)软件接口
使用了黑板风格,组件间的通信通过Rabbitmq和Redis,没有对外接口。
2)分布式数据接口
(1)连接件1:
连接件名称:小车mq队列,每个小车一个
部署方式:mq解耦
队列名称:Car001,Car002···
队列方式:先进先出队列
收发频次:实时收发
消息格式:string
消息示例:“23”
(2)连接件2:
连接件名称:路径规划mq队列,共用一个
部署方式:mq解耦
队列名称:PointQueue
队列方式:先进先出队列
收发频次:实时收发
消息格式:string
消息示例:“Car003”
(3)连接件3:
连接件名称:导航mq队列,共用一个
部署方式:mq解耦
队列名称:NavigatorQueue
队列方式:先进先出队列
收发频次:实时收发
消息格式:string
消息示例:“Car003”
(4)连接件4:
连接件名称:Java-Redis客户端连接
部署方式:函数调用
消息格式:函数名称:get
参数名称:key
参数格式:String
参数定义域:Redis键名的集合
举例:jedis.get("myKey");
(5)连接件5:
连接件名称:Java-Redis客户端连接
部署方式:函数调用
消息格式:函数名称:hget
参数名称:key1,key2
参数格式:String
参数定义域:Redis键名的集合
举例:jedis.hget("Car001","WorkList");
(6)连接件6:
连接件名称:Java-Redis客户端连接
部署方式:函数调用
消息格式:函数名称:set
参数名称:key
参数格式:String
参数定义域:Redis键名的集合
举例:jedis.set("myKey");
(7)连接件7:
连接件名称:Java-Redis客户端连接
部署方式:函数调用
消息格式:函数名称:hset
参数名称:key1,key2,value
参数格式:String
参数定义域:Redis键名的集合
举例:jedis.hget("Car001","Navigator","0");
(8)连接件8:
连接件名称:Java-Redis客户端连接
部署方式:函数调用
消息格式:函数名称:setbit
参数名称:key1,key2
参数格式:String
参数定义域:Redis键名的集合
举例:jedis.setbit("mapBarrier", bitIndex4, true);
(9)连接件9:
连接件名称:Java-Redis客户端连接
部署方式:函数调用
消息格式:函数名称:getbit
参数名称:key1,key2
参数格式:String
参数定义域:Redis键名的集合
举例:jedis.getbit("mapBarrier", bitIndex4);
3)通讯接口
暂无
4)界面接口
界面接口设计见显示器组件报告
4、软件测试报告
4.1测试范围
本次测试将对组件进行单元测试,并对所有组件进行集成测试。通过测试结果的分析,发现系统中存在的问题,并评价该系统是否符合功能和性能要求
4.2测试计划
本次测试分为单元测试与集成测试两部分。
单元测试覆盖各组件的功能,针对各组件的功能分别进行测试。测试各组件是否达到需求规格说明书中要求实现的功能。
集成测试对集成后的整个系统进行测试,测试系统的易用性、可靠性、安全性、是否实现需求规格说明书中要求实现的功能。
4.2.1测试阶段
1)单元测试第一阶段:对小车组件进行测试,测试状态转换
2)单元测试第二阶段:对控制器组件进行测试,测试与mq的通信
3)单元测试第三阶段:进行回放分析显示测试
4)集成测试阶段:自上向下集成所有组件,测试完整性、可靠性等
4.2.2测试进度:
1)单元测试第一阶段:2024年6月27日-2024年6月28日
2)单元测试第二阶段:2024年6月29日-2024年6月30日
3)单元测试第三阶段:2024年7月1日-2024年7月2日
4)集成测试阶段:2024年7月2日-2024年7月3日
4.3测试项目说明
4.3.1小车组件测试
1)测试目的:测试模块的正确性
2)测试方法和测试软件:黑盒测试(等价类划分法)
3)测试用例:如表4-1
表4-1小车测试用例表
输入数据 | 预期输出 | |||||
时钟消息 | 小车状态 | 导航标志 | 任务路径 | 下一步 | 障碍物地图 | 后台输出 |
未收到 | 未分配 | 0 | [{}] | 无变化 | ||
未收到 | 已分配 | 1 | [{“x”:2, “y”:3}] | 无变化 | ||
未收到 | 待启动 | 1 | [{“x”:2, “y”:3}] | 无变化 | ||
未收到 | 正在运行 | 0 | [{“x”:2, “y”:3}] | 无变化 | ||
收到 | 未分配 | 0 | [{}] | 输出:由未分配转为已分配。 状态改变。 | ||
收到 | 未分配 | 1 | [{}] | 无变化 2秒后强制导航标志置0 | ||
收到 | 已分配 | 1 | [{}] | 无变化 | ||
收到 | 已分配 | 1 | [{“x”:-999, “y”:-999}] | 无变化 | ||
收到 | 已分配 | 1 | [{“x”:2, “y”:3}] | 输出:由已分配转为待启动 状态改变 | ||
收到 | 待启动 | 1 | [{“x”:2, “y”:3}] | 输出:由待启动转为正在运行 状态转换 | ||
收到 | 待启动 | 1 | [{}] | 输出:强制改变状态为未分配 状态转换 | ||
收到 | 正在运行 | 1 | [{“x”:2, “y”:3}] | 不为空 | 下一步为障碍物 | 输出:下一步为障碍物,转换状态为未分配 状态转换 |
收到 | 正在运行 | 1 | [{“x”:2, “y”:3}] | 不为空 | 下一步不为障碍物 | 输出:小车向前移动一步 |
收到 | 正在运行 | 1 | [{“x”:2, “y”:3}] | 为空 | 强制转换状态为未分配 |
4.3.2控制器组件测试
1)测试目的:测试模块的正确性
2)测试方法和测试软件:黑盒测试(等价类划分法)
3)测试用例:如表4-2
表4-2控制器测试用例表
输入数据 | 预期输出 | ||
小车导航标志 | 小车任务路径 | 结束标志 | 后台输出 |
1 | [] | 1 | 探索结束 |
1 | [] | 0 | 无变化 |
0 | [] | 1 | 探索结束 |
0 | [] | 0 | 为该小车分配导航器和路径规划器 |
0 | [{“x”:2, “y”:3}] | 0 | 无变化 |
4.3.3回放装置测试
1)测试目的:测试模块的正确性
2)测试方法和测试软件:黑盒测试(等价类划分法)
3)测试用例:如表4-3
表4-3回访装置测试用例表
输入数据 | 预期输出 | |
小车历史路径 | 障碍物地图 | 回放界面输出 |
[{“count”:2,“time”:3,“x”:2, “y”:3}] | 不为空 | 显示回放和分析 |
[{“count”:2,“time”:3,“x”:2, “y”:3}] | 为空 | 提示没有载入地图 |
[] | 不为空 | 界面无变化 |
4.3.4集成测试
采用自上而下的方法,逐步增加组件进行测试,先实现了控制器,小车的集成,然后加上导航器和路径规划器进行集成。最后加上显示器进行最后的集成测试。
4.4测试分析
1)实测结果数据
符合预期结果
2)与预期结果数据的偏差
符合预期结果
3)该项测试表明的事实
小车的设计较为可靠安全。因为设置了如果在已分配状态停留15秒,则强制转换状态为未分配,并强制清空任务并置导航标志为0,实现了系统的安全性,避免了死锁的发生。
4)该项测试发现的问题
小车若发生死锁,则需要15秒去砸锁,占用了较长时间。
测试结果见4-1,4-2
图4-1小车测试用例结果1
图4-2小车测试用例结果2
4.4.2控制器组件的测试分析
1)实测结果数据
符合预期结果
2)与预期结果数据的偏差
符合预期结果
3)该项测试表明的事实
控制器的设计较为可靠安全。
4)该项测试发现的问题
在控制器中,如果发现了导航标志和任务队列同时为空的小车后,向导航器mq和路径规划器mq发送该小车编号。实际上这样存在一个隐患,如果路径规划器全部发生故障,则部分已经接收任务的导航器就会持续等待状态,造成了资源的浪费。可优化为先向路径规划期发送小车编号,等目标点规划出来后,再向导航器发小车编号。
4.4.3回放部分的测试分析
1)实测结果数据
符合预期结果
2)与预期结果数据的偏差
符合预期结果
3)该项测试表明的事实
回放的设计较为可靠安全。
4)该项测试发现的问题
暂未实现覆盖物随回放的进行同时消失的功能。
测试结果如图4-3
图4-3回放测试用例结果
4.4.4集成测试分析
1)实测结果数据
符合预期结果
2)与预期结果数据的偏差
符合预期结果
3)该项测试表明的事实
各组件可以较好的连接在一起,接口设计较为合理。
4)该项测试发现的问题
因为系统中设置的时钟间隔为1秒,小车也就会一秒一动,较为缓慢,但可以通过缩短时钟间隔来加速。
测试结果见图4-4,图4-5
图4-4集成测试用例结果
图4-5集成测试用例结果
三、课程设计总结或结论
1、实验总结
在第一周讨论时,为了实现分布式开发,小组讨论后决定采用黑板风格开发,使用redis和Rabbitmq作为消息中间件来实现各个组件间的通信,降低各组件间的耦合性。
在设计控制器时,最先设计的是循环寻找路径为空且导航标志为0的小车时,额外把他们存到一个list里,后来发现这样当小车数量增大时会有较大延迟,最终设计为循环检查小车,发现为空小车时就发送这个小车的编号,实现了及时给小车分配导航。
在设计小车时,一开始不知道怎么实现小车和其他组件的配合,经过查阅资料和询问老师后,决定将小车设置为在四个状态间变化,每收到一个时钟判断变化一次,实现了各小车之间的同步,也方便了后续回放的设计。
通过本次实验,我们对软件开发有了更深刻的认识,小组合作能力也得到了提高。
2、改进方案
(1)控制器的设计可以更改为先向路径规划期发送小车编号,等目标点规划出来后,再向导航器发小车编号。
(2)回放模块的设计可以设计为手动设计时间粒度,实现倍速播放。