本节实现的功能
- 匹配系统
- 在云端生成地图
细化总览
1、实现匹配系统的原理剖析
2、WebSocket协议与http协议区别
3、我们在云端维护游戏的整个流程
4、WebSocket协议原理剖析
5、集成WebSocket
6、实现从后端向前端发信息
7、WebSocketServer
8、配置config.SecurityConfig
9、加上jwt验证
10、实现前端页面
11、匹配界面布局
12、前端实现——开始实现具体逻辑
13、写匹配池
匹配系统
大致的过程就是,用户在前端向服务器端发送请求,服务器端再向匹配系统发送信息,
然后再从匹配系统反馈给服务器端,再返回到用户的前端
在此过程中要用到 WebSocket 协议
WebSocket 协议与https协议 区别
一问一答式http
问一次返回多次中间还有间隔时间用websocket协议
这种情况下我们的https就不能满足要求了、websocket协议
不仅客户端可以主动像服务器发送请求、服务器端也可以主动向客户端发送请求
是两遍对称的一个通信方式
WebSocket 原理
每一个连接我们都会在后端维护起来
我们会把前端建立的每一个websocket连接在后端维护起来,
比如我们的Clint1连接到我们的服务器、其实一个连接就是一个类
其实就是一个websocketserver类,每来一个连接,其实就是new一个这个类的实例
先创建这个类,我们每次来一个连接的时候本质上就是new一个这个类的实例
每一个连接都是这个类的一个实列来维护的、所有和这个连接相关的信息
都会存到这个类里面、如果是每一个连接自己独有的信息、比如说维护这个连接对应的用户是谁
那可以存成私有变量、如果是维护所有连接的公共信息
比如我们想去维护一下当前哪些用户建立的连接、那么可以存成一个静态变量
WebSocket就是一个多线程、每来一个连接就会开一个新的线程来维护它这个websocket就是一个类
每来一个连接就会开一个线程创建一个类,去维护这个连接流程
用户开始匹配的时候向后端发送一个请求、就会在后端websocket里new一个新的类开一个线程
来维护这个链接那么接收到这个请求之后、我们会把我们的信息发送给我们的匹配系统
匹配系统是一个单独的额外的程序、匹配系统当接收到很多的用户之后随着时间的推移
出现两名玩家的战斗力比较接近匹配出来一局、匹配系统就会将信息返回给我们的后端服务器
也就是我们的websocket服务器、websocket服务器接受到这个信息之后就会将这个信息返回
给这局对战的两名玩家、根据两名玩家建立的链接返还到他们的前端的浏览器里面
同时在我们的服务器端创建一个游戏的过程、因为整个游戏的判断地图的生成都是在云端进行的
集成WebSocket依赖
在pom.xml文件中添加依赖:
spring-boot-starter-websocket
fastjson
实现后端向前端发送信息
1. 用WebSocket的一个api建立链接时把session存下来
2. 存储所有的链接
3.匹配系统接到匹配成功的消息后,根据用户的id找到对应的链接是谁,然后,由此链接向前端发送请求)
1.实现后端向前端发送信息
@OnError
public void onError(Session session, Throwable error) {
error.printStackTrace();
}
//从后端向前端发送信息
public void sendMessage(String message){
synchronized (this.session){
try {
this.session.getBasicRemote().sendText(message);
}catch (IOException e){
e.printStackTrace();
}
}
}
2.存储所有的链接
//对于存所有的链接,应当用一个全局变量来表示,同时对于所有实例可见,应用全局静态变量
//由于每个实例在每个线程里面,所以公共的变量是线程安全的用线程安全的哈希表
将userid映射到我们的websocket实例
final private static ConcurrentHashMap<Integer,WebSocketServer>users=new ConcurrentHashMap<>();
3.
private User user;//存贮每个连接对应的用户是谁
//开一个匹配池(暂时作为匹配系统)
final private static CopyOnWriteArrayList<User>matchpool=new CopyOnWriteArrayList<>();
//注入Mapper
private static UserMapper userMapper;
@Autowired
public void setUserMapper(UserMapper userMapper){
WebSocketServer.userMapper=userMapper;
}
//从后端向前端发送信息
private Session session=null;//维护每个链接
@OnOpen
public void onOpen(Session session, @PathParam("token") String token) throws IOException {
// 建立连接
this.session=session;
System.out.println("connected!");
Integer userId= JwtAuthentication.getUserId(token);
this.user=userMapper.selectById(userId);
if(this.user!=null){
users.put(userId,this);
}else{
this.session.close();
}
System.out.println(users);
}
配置config.SecurityConfig
//放行websocket的所有链接
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/websocket/**");
}
在前端调试一下
成功的话
jwt验证
我们现在建立链接的时候、直接把用户id传过来就可以建立链接了
这样就可以实现一个身份的验证、如果可以正常解析出来的话就表示登录成功
前端需要将id改成token
//consum包下创建一个utils包,在utils包下创建JwtAuthentication
import com.example.backend.utils.JwtUtil;
import io.jsonwebtoken.Claims;
public class JwtAuthentication {
public static Integer getUserId(String token){
int userId=-1;
try {
Claims claims = JwtUtil.parseJWT(token);
userId = Integer.parseInt(claims.getSubject());
} catch (Exception e) {
throw new RuntimeException(e);
}
return userId;
}
}
前端页面的实现
//PkIndexView.vue
<template>
<div class="bjimg"></div>
<PlayGround v-if="$store.state.pk.status==='playing'" />
<MatchGround v-if="$store.state.pk.status==='matching'" />
</template>
<script>
import PlayGround from '@/components/PlayGround.vue'
import MatchGround from '@/components/MatchGround.vue'
export default{
components:{
PlayGround,
MatchGround
},
//MatchGround
<template>
<div class="matchground">
<div class="row">
<div class="col-6">
<div class="user-photo">
<img :src="$store.state.user.photo" alt="">
</div>
<div class="user-username">
{{ $store.state.user.username }}
</div>
</div>
<div class="col-6">
<div class="user-photo">
<img :src="$store.state.pk.opponent_photo" alt="">
</div>
<div class="user-username">
{{ $store.state.pk.opponent_username }}
</div>
</div>
<div class="col-12" style="text-align: center; padding-top: 15vh;">
<button @click="click_match_btn" type="button" class="btn btn-light">{{match_btn_info}}</button>
</div>
</div>
</div>
</template>
<script>
</script>
<style scoped>
.matchground{
width: 60vw;
height: 70vh;
/* background: cornflowerblue; */
margin: 80px auto;/*margin表示距离上下左右的距离,auto使其居中,auto前的数据可以调整上下边距*/
background-color: rgba(50, 50, 50, 0.5);
}
.user-photo{
text-align: center;
padding-top: 10vh;
}
.user-photo > img {
border-radius: 50%;
width:20vh;
}
.user-username{
text-align: center;
font-size: 24px;
font-weight: 600;
padding-top: 2vh;
}
</style>
匹配实现
1. 前端创建按钮 开始匹配 取消
2. MatchGround中对按钮进行绑定,同时向后端发送匹配的请求
3. 后端应当创建一个匹配池(本节暂时使用)
4. 后端在@OnMessage 下取出前端的请求,创建对应开始匹配函数,取消函数
成功匹配后
5. 后端处理完用户的信息后再发往前端
6. 前端需要在store下创建pk.js,在pk.js中实现数据的对接与更新
7. 需要在PkIndexView.vue下调用pk.js实现对应对象的更新
云端实现地图
- 后端consumer.utils.game 创建地图(该地图实现障碍物的填充,两点的连通性,整个地图实现)
- 再发往前端的数据上加上地图(WebSocketServer中多加一个地图)
- 前端在画布生成时调用store(GameMap.vue),由于后端生成地图,
所以前端(GameMap.js)中相应功能应该去掉
create_walls(){
const g=this.store.state.pk.gamemap;
for(let r=0;r<this.rows;r++)
{
for(let l=0;l<this.cols;l++)
{
if(g[r][l])
{
this.walls.push(new Wall(r,l,this))
}
}
}
}
- pk界面 匹配成功后 应当更新地图