实验目的
模拟在道路上汽车行驶的场景:
1,订单可以根据订单生成器(生成器可参考附件的文件,但可自行修改;比如根据车辆数,修改订单生成的速率)生成起点和终点。车辆以概率p(比如p=0.8)车根据订单沿着道路行走,行驶速度根据路段给定;车辆以1-p的概率随机移动(当没有订单时);(路网为DIMACS格式的文件。)
2,有些车发布消息(生产者,比如内容是一个图像),有些车接收和消费消息(消费者);当车与车之间的距离小于R时(比如R=100m),可以进行通信:生产者把消息发送给在通信范围内的消费者。当大于R时,通信停止;【不断变动消息的订阅关系】
把厦门市的道路路网进行可视化展现(基于前端html页面或者java窗体);并能展现汽车移动。
基于Spring 的 JMS编程模型,完成以上消息发布、消息接收等模拟器编程。
实验过程
- springboot +JMS开发环境搭建
- 路网搭建
- 搭建订单生成服务器
- 搭建车辆模拟服务器
- 显示车辆移动
SpringBoot +JMS开发环境搭建
参考博文https://www.cnblogs.com/niit-soft-518/p/6957384.html
路网搭建
这里采用JGraphT搭建厦门的全联通的时间依赖的速度路网,具体过程略。
搭建订单生成服务器
订单生成器读取真实订单txt文件,将其转化成消息传递到request队列中,供车辆模拟服务器异步获取进行处理。
模拟开始时,需要同步两个服务器的时间,具体做法如下:订单生成服务器启动时会监听simulatorCommand队列,车辆模拟服务器开始模拟时会将start命令传入此队列,告诉订单生成器现在的时间,并通知其开始工作。关键代码如下:
@JmsListener(destination = "simulatorCommand",containerFactory = "queueListenerContainer")
public void recieve(String msg) throws Exception {
String[] info=msg.split("#");
//从消息中获取车辆模拟服务器传达的命令,如果是start则开始工作
String command=info[0];
if(command.equals("start")){
if(thread!=null&&thread.isAlive()){
return;
}
this.startTime=Integer.valueOf(info[1]);
this.time=startTime;
this.unitTime=Integer.valueOf(info[2]);
this.isInterupt=false;
this.thread=new Thread(this,"simulator");
thread.start();
}else if(command.equals("stop")){
isInterupt=true;
}else if(command.equals("terminate")){
isInterupt=true;
bufferedReader.close();
bufferedReader=null;
}
}
在真实场景中,用户通过rest形式的http请求发出乘车请求,对应的controllor将用户的乘车信息转化为能够处理的消息格式,传入request队列中:
@RequestMapping(value = "/request",method = RequestMethod.POST)
public void getRequest(@RequestBody Map<String,String> request){
jmsTemplate.convertAndSend("request",request.get("request"));
}
订单生成器会读取真实订单txt文件,并模拟http请求,根据时间调用此controller,达到模拟真实场景的效果。
搭建车辆模拟服务器
服务器的每次调度过程如下:
1.向request队列中取有限个请求加到原有的请求队列中
2.获取当前的请求队列和空载车辆,选择匹配算法进行匹配(这里使用最近优先匹配)
3.根据匹配结果更新被匹配车辆状态
4.模拟车辆发布接收消息
5.随机移动空载车辆
如果将每一辆车都配置一个独立的topic,则要为每辆车配置总车辆数个监听器来监听其他车辆的topic,太麻烦。我们将所有车辆发出的信息保存的一个topic中,交给第三方——消息处理器分发信息。
每次调度,选择1辆车发布消息,选择与它最近的5辆车订阅此消息。
关键代码如下:
public void sendMessage(){
List<SimulatorCar> allCars=carExcutor.getAllCars();
int selectId=(int)(new Random().nextDouble()*allCars.size())+1;
for(Car car:allCars){
selectId--;
if(selectId==0){
jmsTemplate.convertAndSend("carMessage",car.getId()+"#car "+car.getId()+" message");
break;
}
}
}
@JmsListener(destination = "carMessage",containerFactory = "topicListenerContainer")
public void getMessage(String msg){
String[] info=msg.split("#");
int carId=Integer.valueOf(info[0]);
String message=info[1];
List<SimulatorCar> allCars=carExcutor.getAllCars();
Car messageCar=allCars.get(carId-1);
List<Car> nearCars=getNearCars(5,messageCar);
for(Car car:nearCars){
System.out.println("car "+car.getId()+" receive message: "+message);
}
}
private List<Car> getNearCars(int num,Car center){
List<Car> nearCars=new LinkedList<>();
List<Double> distances=new LinkedList<>();
for(Car car:carExcutor.getAllCars()){
if(center==car){
continue;
}
RoadNode carL=((SimulatorCar)car).getLocation();
RoadNode centerL=((SimulatorCar)center).getLocation();
double distance=(carL.getLat()-centerL.getLat())*(carL.getLat()-centerL.getLat())+(carL.getLon()-centerL.getLon())*(carL.getLon()-centerL.getLon());
for(int i=0;i<nearCars.size();i++){
if (distance<distances.get(i)){
nearCars.add(i,car);
distances.add(i,distance);
break;
}
}
if(nearCars.size()<num){
nearCars.add(car);
distances.add(distance);
}
if(nearCars.size()>num){
((LinkedList<Car>) nearCars).removeLast();
((LinkedList<Double>) distances).removeLast();
}
}
return nearCars;
}
前4步做完计算服务器花费的时间,并转化为模拟时间,将空载车辆随机移动这么多的时间。
显示车辆移动
我们调用高德地图的JS API进行可视化,模拟器缓存了当前空载车辆的位置信息,我们通过提供的接口调用获取,并在图中标出相应的位置。每隔1s轮询刷新一次。
实验结果
先启动订单生成服务器,再启动车辆模拟服务器,调用/start接口开始模拟
订单生成器输出订单信息(只输出了请求的t0信息):
消息处理器输出车辆之间的消息接收信息:
显示车辆位置信息: