注意
-
StratGameService StratGameServiceImpl接口实现 stratGameService 驼峰可以很方便的自动补全
-
Interger.parseInt(data.getFirst(“a_id”))这里明显变黄 根据提示更改点击就可以了
报错
- 注解没有加
调试方法
-
为了方便调试 把一个人的天梯分改大一点
-
修改完数据库但是数据并没有更新 可能是因为有缓存 重启一下就可以了
查看异常 user.get
查询ctrl
+f
查询所有users.get的操作 之前都需要加上条件
ctrl
+shift
+f
全局搜
这里针对所有websocket里的users.getid的操作都要加 因为不知道什么时候断开
每次查询都需要判断是否为空
如果一名玩家没有发送退出请求就直接关闭进程
发现玩家不存在时 通知另一名 并不再向此名玩家发送请求
如果发送 就会爆异常 正在匹配 刷新一下 但是人还在匹配持里
在websocket里边 如果断开 查询时就会为空 空的属性没有game的属性
此时就会报异常
ctrl
+shift
+t
恢复关掉的页面
知识点
component
需要加入 autowired就需要加入component
调试
开始匹配发现三个错误
Thread.sleep()缺少Thread
int j=i+1;写成j=0;
报错
403 forbiden 没有封尾
.antMatchers("/pk/start/game/").hasIpAddress("127.0.0.1")
NullPointerException
单击错误信息
restTemplate可能是空的原因
由于没有加上注解@Component
SpringBoot没有找到Bean树 成功设置respTemplate不为空
微服务调用后端
需要RestTemplate 把config复制过来
注意 这里的Template复制 这里直接复制所有代码
然后在微服务config这里右键粘贴 这里会自动补全路径
MathcingPool里边 注入
private static RestTemplate restTemplate;
@Autowired
public void setRestTemplate(RestTemplate restTemplate){MatchingPool.restTemplate=restTemplate;}
发送
MultiValueMap<String,String> data=new LinkedMultiValueMap<>();
data.add("a_id",a.getUserId().toString());
data.add("b_id",b.getUserId().toString());
restTemplate.postForObject(startGameUrl,data,String.class);
后端接受
写一个方法接受到消息
写一个方法的话 需要写一个service 一个serviceimpl(这里一个注解service) 一个controller
service 下新建pk(StartGameService) service->impl->新建Pk(serviceimpl)
在后端 consumer中的websocket已经有了一个startgame函数 这里改为public static
@Autowired
private StratGameService stratGameService; 注意命名规则
@PostMapping("/pk/start/game")
public String startGame(@RequestParam MultiValueMap<String,String> data){
Integer aId=Integer.parseInt(Objects.requireNonNull(data.getFirst("a_id")));
Integer bId=Integer.parseInt(Objects.requireNonNull(data.getFirst("b_id")));
return stratGameService.startGame(aId,bId);
}
写好controller 放行接口给微服务
.antMatchers("/pk/start/game").hasIpAddress("127.0.0.1")
这里增加的一句 后面有ip限制
匹配机制
优先匹配等待时间长的
防止用户流失 越往后加入的用户 越新 越往前的玩家越旧
所以应该从前往后枚举每一名玩家
匹配条件判定
应该是能被a接受 也能被b接受min
两人分差小于等待时间乘10 等待时间最小值乘10
如果使用max 则说明至少一方有需求就匹配
int ratingDelta=Math.abs(a.getRating()-b.getRating());
int waitingTime=Math.min(a.getWaitingTime(),b.getWaitingTime());
return ratingDelta<=waitingTime*10;
sleep(1000)//点击使用trycatch环绕
如果报异常catch写e.printStackTrace();
sleep(1000);
lock.unlock();
try{
increaseWaitingTime();
matchPlayers();
}finally {
lock.unlock();//如果不解锁 就死循环了
}
线程的逻辑
run里边写什么
逻辑是周期性的执行 每次执行都看看玩家有没有执行 如果匹配成功
就移除匹配池然后返回 如果没有匹配 就会一直等待
写一个死循环 然后写一个sleep函数 每一秒执行一遍函数
如果能配队 就配对 不能配队继续等待 每名玩家每等待一秒 就在
waitingtime+1 他未来匹配的阈值可以变宽
MatchingPool实现
存放用户
private static List<Player> players=new ArrayList<>();
这里我们不考虑线程安全不安全 我们会自己手动加锁 把他变成安全的
异步是不是顺序执行的 我切回一个线程执行 然后再切到另外一边执行
同步顺序 加上锁 一个是加入玩家的线程 一个是匹配
需要加锁
lock.lock();
try{players.add(new Player(userId,rating,0));
}finally {
lock.unlock();
}
删除不太好删除
先创建一个列表 把没有删除的加进去
List<Player> newPlayer=new ArrayList<>();
for(Player player:players){
if(!player.getUserId().equals(userId)){
newPlayer.add(player);
}
}
players=newPlayer;
注意
需要使用lambok才能使用注解
player
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Player {
private Integer userId;
private Integer rating;
private Integer waitingTime;//等待时间
}
MatchignPool继承自Thread
需要重写run方法
放到service里边 全局只有一个线程
所以定义成静态的
public final static MatchingPool matchingPool=new MatchingPool();
什么时候启动呢
可以选择一个喜欢的地方启动 在sp启动的时候启动
MatchingServiceImpl.matchingPool.start();
在项目启动之前启动 放在主函数之前
impl下新建utils存取相关
Player
MatchingPool
进行到前端发送一个请求 从socket里边 然后发送给matchingSystem
我们是在matchingSystem里边获取的
收到所有用户之后 放到一个池子里边 开一个新线程 每隔一秒钟扫描一遍数组 将能够匹配的人匹配到一起
匹配两名分值比较接近的玩家 随着时间越来越大 允许玩家之间的分值相差越来越大
发送给后端
private final static String addPlayerUrl="http://127.0.0.1:3001/player/add/";
private final static String removePlayerUrl="http://127.0.0.1:3001/player/remove/";
先把url定义成常量
System.out.println("start matching");
MultiValueMap<String,String> data=new LinkedMultiValueMap<>();
data.add("user_id",this.user.getId().toString());//注意这里的id需要转化
data.add("rating",this.user.getRating().toString());
restTemplate.postForObject(addPlayerUrl,data,String.class);//这里的String.class是返回值的类
java的反射机制
remove
private void stopMatching(){
System.out.println("stop matching");
MultiValueMap<String,String> data=new LinkedMultiValueMap<>();
data.add("user_id",this.user.getId().toString());
restTemplate.postForObject(addPlayerUrl,data,String.class);
}
运行一下本进程 出现enable 点一下就可以 构建的后端在服务里边
这里的rating是放在bot身上还是用户身上
应该是都是可以的
这里放在用户身上 通过不同修改bot获取自身id
重新连接数据库
root+密码 kob 连接 连接成功后刷新出kob
添加rating一列 默认值1500 刷新一下
bot删除rating一列 点击rating后 上方 - 号 减掉 刷新一下
旧的ui
然后修改pojo bot移动到user
然后修改一下构造函数 在新建user时实例user
在service的impl(接口实现) register接口
bot也改一下 修改bot时 将rating去掉
先定义一个静态变量
private static RestTemplate restTemplate;
@Autowired
public void setRestTemplate(RestTemplate restTemplate){WebSocketServer.restTemplate=restTemplate};
//这个自动执行
原理
如果我们加了Autowired的话 就会看看这个service接口是不是有一个唯一的加了注解Bean
的函数
和他对应 如果有 就调用一下这个函数 把返回值赋值过来
打通匹配服务和后端
comsumer下的websocket创建函数 startGame把startMatching后的东西都剪切过来
把matchpoo相关的都删除
springboot向后端发请求
如果希望在当前的component使用Service需要 先把他加一个注解bean
在config下新建类 RestTemplateConfig类 加入注解@Configuration
想取得谁 加一个Bean
注解
@Configuration
public class RestTemplateConfig {
@Bean
public RestTemplate getRestTemplate(){
return new RestTemplate();//可以让两个springboot之间进行通信
}
}
未来使用它的时候 就加一个@Autowired
就可以
添加后端进入总项目
在backendcloud下新建项目 backend
高级设置kob.backend
创建 删除src (这里我的没有同步 可能有问题) 把上次那个src复制过来
粘贴到idea项目树的刚刚那个位置 然后把原来的pom打开 把dependency
全部粘贴过来到backend的Pom
删除没有用的依赖thymeleaf
点开maven重新加载一下 如果在构建->同步
出现对号接成功了
运行项目
将main函数改名为MatchingSystemlApplication
@SpringBootApplication
SpringApplication.run(MatchingSystemApplication.class,args);
函数体运行代码
此时提示 在服务工具窗口管理多个SpringBoot运行 点击使用服务
访问端口号3001 发现是可以访问的 WhitelableErrorPage
访问player/add/
报错405 player/remove/
爆403
这里是因为仅仅放行了add 但是并没有放行remove
MatchingServier按理说只能接受后端
的请求 不能接受浏览器的请求 如果可以 有可能通过这个链接攻击服务器
加上权限控制 添加依赖spring-secrity
复制以前的代码
config
复制SecurityConfig的 删除jwt相关 留下configure函数
删除http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
去除无关引入变灰得得引用项
.antMatchers("/player/add/").hasIpAddress("127.0.0.1")
只允许本地访问
实现接口
加入注解@service
继承接口MatchingSystem
alt
+insert
实现方法 输出调试信息
sout user+rating return "addPalyerSuccess"
具体逻辑先不实现
创建controller
controller下创建类MatchingController
添加注解@RestController
注入接口@Authwired
private MatchingService matchingservice
因为涉及到数据的修改 所以用Post
@PostMapping("/player/add/")
public String addPlayer(@RequestParam MultiValueMap<String,String> data)
两个点 一个requestParam
一个Multivaluemap
Integer userId=Integer.parseInt(data.getFirst("user_id"));注意这里爆黄色感叹号 悬浮点击选项即可
Integer rating=Integer.parseInt(data.getFirst("rating"));
return matchingService.addPlayer(userId,rating);
MultiValue是一个关键字对应一个列表value
新建接口
创建controller service软件包
service下有impl接口的实现 service定义接口类 MatchingSystem在impl下实现
配置新项目
删除src目录 pom desciption
下加入标签 <packaging>pom</packaging>
加入springcloud依赖 创建子项目 backendcloud
创建模块 新建模块(空项目) matchingsystem
java,maven,1.8
高级设置加入组id.matchingsystem
添加git? 取消
不再询问 matchingsystem本质上也是一个springboot 也是需要添加依赖的
所以复制springcloud下的依赖 剪切出来<dependencies>
下所有标签 放到子目录的pom里边
复制完之后点一下刷新 同步成功就可以了 占用一个端口 配置
在resources下新建文件application.properties
->server.port=3001
web是3000
匹配系统的逻辑
client 发送请求给 servier(websocket和http controller) 本来使用微服务实现matching system 成功匹配后
返回结果给servier 开一个线程给game ->map 返回前端->每次读取玩家操作(也可以是bot) ->没有输入就超时了->
操作合法 继续读取操作
本节课实现微服务 matchingSystem 后来bot1和Bot2的执行也是微服务 微服务是一个独立的程序
(理解为新开了一个springBoot) 会向matchingSystem发送http请求 Ms接收到后开一个单独的线程
(每隔一秒钟扫描一下所有玩家 判断一下这些玩家能不能相互匹配出来) 成功就返回http 这里实现
用的是spring cloud
网关 注册 负载 均衡 (并发性不大)都不会遇到 显得不那么的SpringCloud
spring的微服务是使用http来实现的 结果返回是传一些url 传一些参数回来(Django是用thift通信的
使用普通的socket协议稍微快一些) 玩家特别多要分配服务器
springboot共有两个子项目 一个后端一个匹配系统
新建项目
spring项目
名称:backendcloud 组 com.kob
jdk1.8java8
加入springweb依赖
可以使用start.aliyun.com
redis带了解
sp-0125-6-3 微服务
客户端向服务器两端通信 生成游戏地图
表头
Idea操 作 指 南:https://www.acwing.com/blog/content/25456/
每次都要点击的网址:https://www.acwing.com/blog/content/28250/
G i t B a s h : https://www.acwing.com/blog/content/22768/
WindowsIdea :https://www.acwing.com/blog/content/23868/
本节课讲义https://www.acwing.com/file_system/file/content/whole/index/content/6325603/