目录
[五、处理 websocket 请求、返回的响应(MatchAPI)](#五、处理 websocket 请求、返回的响应(MatchAPI))
[2.1 后端处理请求](#2.1 后端处理请求)
[2.2 客户端处理响应](#2.2 客户端处理响应)
一、约定前后端交互接口的参数
在上一篇博客中,我们约定了前后端交互接口的参数,如图:
1、websocket连接路径
对照着上面的约定,我们进行配置,前端代码如下:
javascript
// 此处进行初始化 websocket,并且实现前端的匹配逻辑
// 此处的路径必须写作 /findMatch
let websocketUrl = "ws://" + location.host + "/findMatch";
let websocket = new WebSocket(websocketUrl);
使用动态的方式配置路径,方便以后部署在云服务器上(云服务器上的主机号肯定不会是127.0.0.1,而端口号也可能不同,因此让websocket的 URL变为动态的更合理)。
后端代码:
java
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
@Autowired
private MatchAPI matchAPI;
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry webSocketHandlerRegistry) {
webSocketHandlerRegistry.addHandler(matchAPI, "/findMatch")
.addInterceptors(new HttpSessionHandshakeInterceptor());
}
}
注意 :连接建立完成之后,还要记得把之前连接的 HttpSession 拿到,添加到 websocketSession
2、构造请求、响应对象
我们需要创建两个对象,用来接收和传送,也就是 MatchRequest 和 MatchResponse
二、用户在线状态管理
为什么要维护 玩家 在线状态 呢?因为这样我们可以根据用户,获取该用户的Session,从而可以给 用户 传送数据。因为服务器是要给多个客户端发送数据的?我们怎么保证给指定的玩家发送数据呢?那么拿到该玩家的Session就可以了。
也能进行判断 在线 / 离线,方便处理 不同状态下的操作。比如:后面进行比赛了,如果对手掉线了,我们是不是能直接判断当前玩家赢了?
java
@Component
public class OnlineUserManager {
// 这个hash表就是用来表示当前用户在游戏大厅的在线状态
private ConcurrentHashMap<Integer, WebSocketSession> gameHall = new ConcurrentHashMap<>();
// 这个hash表用来维护用户Id和房间页面的在线状态
private ConcurrentHashMap<Integer, WebSocketSession> gameRoom = new ConcurrentHashMap<>();
public void enterGameHall(int userId, WebSocketSession webSocketSession) {
gameHall.put(userId, webSocketSession);
}
public void exitGameHall(int userId) {
gameHall.remove(userId);
}
public WebSocketSession getFromGameHall(int userId) {
return gameHall.get(userId);
}
public void enterGameRoom(int userId, WebSocketSession webSocketSession) {
gameRoom.put(userId, webSocketSession);
}
public void exitGameRoom(int userId) {
gameRoom.remove(userId);
}
public WebSocketSession getFromGameRoom(int userId) {
return gameRoom.get(userId);
}
}
这里有2个hash表,一个是用来维护 玩家 处在游戏大厅的在线状态,一个是用来维护 玩家 处在游戏房间的在线状态。
其中,维护的是 玩家Id 和 Session 的映射关系。
分别有三个方法:增、删、查。
三、房间管理
为什么维护游戏房间呢?联机游戏,例如 LOL,每时每刻都会有非常多的玩家在进行对局,那么怎么管理这么多玩家呢?我们就可以把若干个玩家放在一个游戏房间里,每个游戏房间都是相互独立的,互不干扰,而且每一个房间在不同时间中的对局状态都会不一样。如图:
这里,我们就这设置成 1个房间 有 2个玩家,因为五子棋的玩法是双人对弈的(后续也会进行扩展,例如观战功能)。使用 UUID 生成一个随机房间Id,类型是String。(为了保证房间Id不能重复,Java 内置的 UUID 类就已经能满足当前这个项目了)
1、房间类:
java
// 这个类表示一个游戏房间
@Data
public class Room {
// 使用字符串类型来表示,方便生成唯一值
private String roomId;
private User user1;
private User user2;
public Room() {
// 构造 Room 的时候生成一个唯一的字符串表示房间 id
// 使用 UUID 来作为房间 id
roomId = UUID.randomUUID().toString();
}
}
2、房间管理器:
java
// 房间管理器类
// 这个类也希望有唯一实例
@Component
public class RoomManager {
private ConcurrentHashMap<String, Room> rooms = new ConcurrentHashMap<>();
private ConcurrentHashMap<Integer, String> userIdToRoomId = new ConcurrentHashMap<>();
public void add(Room room, int userId1, int userId2) {
rooms.put(room.getRoomId(), room);
userIdToRoomId.put(userId1, room.getRoomId());
userIdToRoomId.put(userId2, room.getRoomId());
}
public void remove(String roomId, int userId1, int userId2) {
rooms.remove(roomId);
userIdToRoomId.remove(userId1);
userIdToRoomId.remove(userId2);
}
public Room getRoomByRoomId(String roomId) {
return rooms.get(roomId);
}
public Room getRoomByUserId(int userId) {
String roomId = userIdToRoomId.get(userId);
if(roomId == null) {
// userId -> roomId 映射关系不存在,直接返回 null
return null;
}
return rooms.get(roomId);
}
}
这里有 2个hash表,一个用来维护 房间Id 和 房间 的映射关系(希望有了房间Id,就能拿到这个房间),一个用来维护 用户Id 和 房间Id 的映射关系(根据用户Id 拿到 房间Id,再根据 房间Id 拿到 房间)。
提供的方法:增、删、查。(这里增、删需要同时维护上面2个hash表,查有两种方法,一是拿着房间Id,找到房间;二是拿着用户Id,找到房间)。
注意 :这里涉及到线程安全问题;我们想想,五子棋对战肯定是会有很多人在进行对战的,这时候,有多个客户端都对这两个hash表有修改、删除、查询的操作,在这种并发的情况下,就会涉及到线程安全问题。(因此使用ConcurrentHashMap,线程安全的hash表)
四、匹配器(Matcher)
匹配器 用来处理整个匹配模块的功能。
1、玩家实力划分
如今很多的竞技类游戏,都有段位,用来证明玩家的实力 的象征之一,也有其他的参数可以证明,例如战绩、KPI等等。
为了玩家的游戏体验、还有公平的游戏环境,所以要对不同水平的玩家进行划分
因此,这里我们也设置一个段位的功能,用来划分不同实力区间的玩家,使用匹配三个队列进行划分:normalQueue(普通玩家)、highQueue(高水平玩家)、veryHighQueue(大神)。
java
private Queue<User> normalQueue = new LinkedList<>();
private Queue<User> highQueue = new LinkedList<>();
private Queue<User> veryHighQueue = new LinkedList<>();
既然这里要实现匹配功能,就是要给玩家分配对手,约定了上面这三种匹配队列,我们就可以把水平相当的玩家匹配到同一个房间中了。
2、加入匹配队列(add)
根据上面的实力划分,就根据不同玩家的天梯区间积分,给加入到对应水平的匹配队列中了,代码如下:
java
// 操作匹配队列的方法
// 把玩家放到匹配队列中
public void add(User user) {
if(user.getScore() < 2000) {
synchronized (normalQueue) {
normalQueue.offer(user);
normalQueue.notify();
}
log.info("把玩家 " + user.getUsername() + " 加入到了 normalQueue 中");
} else if(user.getScore() >= 2000 && user.getScore() < 3000) {
synchronized (highQueue) {
highQueue.offer(user);
highQueue.notify();
}
log.info("把玩家 " + user.getUsername() + " 加入到了 highQueue 中");
} else {
synchronized (veryHighQueue) {
veryHighQueue.offer(user);
veryHighQueue.notify();
}
log.info("把玩家 " + user.getUsername() + " 加入到了 veryHighQueue 中");
}
}
3、移除匹配队列(remove)
既然有了加入匹配队列,对应的也要有删除,比如以下场景会用到:
1、玩家点击 停止匹配
2、玩家匹配成功,也需要把该玩家从匹配队列中删除
3、玩家在匹配的时候掉线了
java
// 当玩家点击停止匹配,就需要把该玩家从匹配队列删除
// 当匹配成功后,玩家进入房间,也需要把该玩家从匹配队列删除
public void remove(User user) {
if(user.getScore() < 2000) {
synchronized (normalQueue) {
normalQueue.remove(user);
}
log.info("玩家: " + user.getUsername() + " 在 normalQueue 队列被删除");
} else if(user.getScore() >= 2000 && user.getScore() < 3000) {
synchronized (highQueue) {
highQueue.remove(user);
}
log.info("把玩家: " + user.getUsername() + " 在 highQueue 队列被删除");
} else {
synchronized (veryHighQueue) {
veryHighQueue.remove(user);
}
log.info("把玩家: " + user.getUsername() + " 在 veryHighQueue 队列被删除");
}
}
4、创建三个线程
创建三个线程,分别对这三个匹配队列进行扫描,看该队列中有没有2玩家,有就要把这两个玩家加入同一个房间中。
java
public Matcher() {
// 创建三个线程,分别针对这三个匹配队列,进行操作
Thread t1 = new Thread() {
@Override
public void run() {
// 扫描normalQueue
while(true) {
handlerMatch(normalQueue);
}
}
};
t1.start();
Thread t2 = new Thread() {
@Override
public void run() {
while (true) {
handlerMatch(highQueue);
}
}
};
t2.start();
Thread t3 = new Thread() {
@Override
public void run() {
while (true) {
handlerMatch(veryHighQueue);
}
}
};
t3.start();
}
这三个线程是在构造方法中创建、启动的。一匹配就需要知道这三个匹配队列,分别有没有2个玩家,有就继续后面的操作,没有就要继续扫描。
5、加入房间(handlerMatch)
这里涉及到忙等问题,在上篇博客有讲述。
(1)检测匹配队列是否有2个玩家
在上面这三个线程扫描时,就会进来判断有没有玩家要不要加入匹配队列,判断逻辑如下:
java
// 1、检测队列中元素个数是否达到 2
// 队列的初始情况可能是 空
// 如果往队列中添加一个元素,这个时候,仍然是不能进行后续匹配操作的
// 因此在这里使用 while 循环检查更合理
while (matchQueue.size() < 2) {
matchQueue.wait();
}
(2)取出匹配队列中的玩家
java
// 2、尝试从队列中取出两个玩家
User player1 = matchQueue.poll();
User player2 = matchQueue.poll();
log.info("匹配出两个玩家: " + player1.getUsername() + ", " + player2.getUsername());
(3)获取玩家的Session
这里获取的玩家Session可能为null,因为玩家也可能在加入匹配队列后掉线了。
java
// 3、获取到玩家的 WebSocket 会话
// 获取到会话的目的是为了告诉玩家,你排到了~
WebSocketSession session1 = onlineUserManager.getFromGameHall(player1.getUserId());
WebSocketSession session2 = onlineUserManager.getFromGameHall(player2.getUserId());
// 理论上来说,匹配队列中的玩家一定是在线的状态
// 因为前面的逻辑进行了处理,当玩家断开连接的时候,就把玩家从匹配队列移除了
// 但是这里还是进行一次判定,进行双重判定会更稳妥一点
if(session1 == null) {
// 如果玩家1不在线了,就把玩家2放回匹配队列
matchQueue.offer(player2);
return;
}
if(session2 == null) {
// 如果玩家1不在线了,就把玩家2放回匹配队列
matchQueue.offer(player1);
return;
}
// 当前能否排到两个玩家是同一个用户的情况吗?一个玩家入队列两次
// 理论上也不会存在~
// 1) 如果玩家下线,就会对玩家移除匹配队列
// 2) 又禁止了玩家多开
// 但是仍然在这里多进行一次判定,以免前面的逻辑出现 bug 时,带来严重的后果
if(session1 == session2) {
// 把其中的一个玩家放回匹配队列
matchQueue.offer(player1);
return;
}
(4)双方玩家加入房间
java
// 4、把这两个玩家放到同一个游戏房间中
Room room = new Room();
roomManager.add(room, player1.getUserId(), player2.getUserId());
(5)加入房间成功后,给客户端返回响应
java
// 5、给玩家反馈信息
// 通过 WebSocket 返回一个 message 为 "matchSuccess" 这样的响应
MatchResponse response1 = new MatchResponse();
response1.setOk(true);
response1.setMessage("matchSuccess");
String json1 = objectMapper.writeValueAsString(response1);
session1.sendMessage(new TextMessage(json1));
MatchResponse response2 = new MatchResponse();
response2.setOk(true);
response2.setMessage("matchSuccess");
String json2 = objectMapper.writeValueAsString(response2);
session2.sendMessage(new TextMessage(json2));
五、处理 websocket 请求、返回的响应(MatchAPI)
因为需要消息推送机制,所以我们使用了 websocket 协议,它既能满足消息推送机制,又能节省带宽资源,提高传输效率的协议。
在建立 websocket 连接后,主要的方法有以下 4 个:
afterConnectionEstablished:在建立 websocket 连接时,要做的处理
handleTextMessage:接收玩家的 请求,返回对应的 响应。
handleTransportError:当 websocket 连接出现错误时,要做的处理
afterConnectionClosed:当 websocket 连接关闭时,要做的处理
1、afterConnectionEstablished
建立websocket连接成功后,就要进行校验用户信息,如果是新登录的玩家,就把该玩家设为游戏大厅在线状态,在这个方法里面,要处理用户多开的问题 和 未登录却直接访问游戏大厅的不合理操作。
java
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
// 玩家上线,加入到 OnlineUserManager 中
//1、先获取到当前用户的身份信息(谁在游戏大厅中,建立的连接)
// 此处的代码,之所以能够getAttributes,全靠了在注册 websocket 的时候,
// 加上了 .addInterceptors(new HttpsessionHandshakeInterceptor())
// 这个逻辑就把 HttpSession 中的 Attribute 都给拿到 WebSocketSession 中了
// 在 Http 登录逻辑中,往 HttpSession 中存了 User 数据:httpSession.setAttribute("user", user)
// 此时就可以在 WebSocketSession 中把之前 HttpSession 里存的 User 对象给拿到了
// 注意,此处拿到的 user,可能是为空的
// 如果之前用户压根就没有通过 HTTP 来进行登录,直接就通过 /game_hall.html 这个URL来进行访问游戏大厅了
// 此时就会出现 user 为 null 的情况
try {
User user = (User) session.getAttributes().get("user");
//2、拿到了身份信息之后,进行判断当前用户是否已经登录过(在线状态),如果已经是在线,就不该继续进行后续逻辑
if(onlineUserManager.getFromGameHall(user.getUserId()) != null
|| onlineUserManager.getFromGameRoom(user.getUserId()) != null) {
// 说明该用户已经登录了
// 针对这个情况,要告知客户端,你这里重复登录了
MatchResponse response = new MatchResponse();
response.setOk(true);
response.setReason("当前用户已经登录, 静止多开!");
response.setMessage("repeatConnection");
session.sendMessage(new TextMessage(objectMapper.writeValueAsString(response)));
// 此处直接关闭有些太激进了,还是返回一个特殊的 message,供客户端来进行处理
//session.close();
return;
}
onlineUserManager.enterGameHall(user.getUserId(), session);
// System.out.println("玩家" + user.getUsername() + " 进入游戏大厅");
log.info("玩家 {}",user.getUsername() + " 进入游戏大厅");
} catch (NullPointerException e) {
//e.printStackTrace();
log.info("[MatchAPI.afterConnectionEstablished] 当前用户未登录");
// 出现空指针异常,说明当前用户的身份信息为空,也就是用户未登录
// 就把当前用户尚未登录,给返回回去
MatchResponse response = new MatchResponse();
response.setOk(false);
response.setReason("您尚未登录,不能进行后续匹配");
session.sendMessage(new TextMessage(objectMapper.writeValueAsBytes(response)));
}
}
2、afterConnectionClosed
既然关闭了 websocket 连接,也就意味着玩家下线了,要把当前玩家从游戏大厅的在线状态给删除掉;如果是在匹配中,不经要删除游戏大厅的在线状态,还要在匹配队列删除该玩家。
java
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
// 玩家下线,删除 OnlineUserManager 中的该用户的Session
try {
User user = (User) session.getAttributes().get("user");
WebSocketSession tmpSession = onlineUserManager.getFromGameHall(user.getUserId());
if(tmpSession == session) {
onlineUserManager.exitGameHall(user.getUserId());
}
// 如果玩家正在匹配中,但WebSocket断开了,就应该把该玩家移除匹配队列
log.info("Closed玩家: {}", user.getUsername() + " 下线");
matcher.remove(user);
} catch (NullPointerException e) {
log.info("[MatchAPI.afterConnectionClosed] 当前用户未登录");
}
3、handleTransportError
既然连接出现错误了,那么也肯定要把玩家的游戏大厅在线状态给删除掉,如果在匹配,匹配队列也应该删掉该玩家,代码逻辑和关闭连接一样。
java
public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
// 玩家下线,删除 OnlineUserManager 中的该用户的Session
try {
User user = (User) session.getAttributes().get("user");
WebSocketSession tmpSession = onlineUserManager.getFromGameHall(user.getUserId());
if(tmpSession == session) {
onlineUserManager.exitGameHall(user.getUserId());
}
// 如果玩家正在匹配中,但WebSocket断开了,就应该把该玩家移除匹配队列
log.info("Error玩家: {}", user.getUsername() + " 下线");
matcher.remove(user);
} catch (NullPointerException e) {
log.info("[MatchAPI.handleTransportError] 当前用户未登录");
}
}
4、handleTextMessage
这里才是真正的处理 websocket 请求、返回对应响应的逻辑。(处理开始匹配请求 和 停止匹配请求,返回对应响应)
1、首先要拿到用户信息以及用户发来的请求,约定了发送过来的是:startMatch、stopMatch
startMatch:就要把玩家加入到匹配队列中,同时构造返回响应,返回给客户端
stopMatch:就要把玩家从匹配队列中删除,同时构造返回响应,返回给客户端
java
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
// 实现处理开始匹配请求和停止匹配请求
User user = (User) session.getAttributes().get("user");
// 拿到客户端发给服务器的数据
String payload = message.getPayload();
// 当前传过来的数据是JSON格式的字符串,就需要把它转成 Java 对象:MatchRequest
MatchRequest request = objectMapper.readValue(payload, MatchRequest.class);
MatchResponse response = new MatchResponse();
if(request.getMessage().equals("startMatch")) {
// 进入匹配队列
// 把当前用户加入到匹配队列中
matcher.add(user);
// 把玩家信息放入匹配队列后,就可以返回一个响应给客户端了
response.setOk(true);
response.setMessage("startMatch");
} else if (request.getMessage().equals("stopMatch")) {
// 退出匹配队列
// 在匹配队列中把当前用户给删除了
matcher.remove(user);
// 在匹配队列中把当前用户给删除后,就可以返回一个响应给客户端了
response.setOk(true);
response.setMessage("stopMatch");
} else {
// 非法情况
response.setOk(false);
response.setMessage("非法的匹配请求");
}
String jsonString = objectMapper.writeValueAsString(response);
session.sendMessage(new TextMessage(jsonString));
}
六、前端代码的逻辑处理
1、建立连接
通过指定 websocketURL,和后端的 websocket 路径保存一致,这样就能和后端建立websocket连接了。
java
// 此处进行初始化 websocket,并且实现前端的匹配逻辑
// 此处的路径必须写作 /findMatch
let websocketUrl = "ws://" + location.host + "/findMatch";
let websocket = new WebSocket(websocketUrl);
websocket.onopen = function () {
console.log("onopen");
}
websocket.onclose = function () {
console.log("onclose");
}
websocket.onerror = function () {
console.log("onerror");
}
// 监听页面关闭事件,在页面关闭之前,手动调动这里的 websocket 的 close 方法
window.onbeforeunload = function () {
websocket.close();
}
//一会重点来实现,要处理服务器返回的响应
websocket.onmessage = function (e) {
//用来处理响应,下面会介绍
}
事件:
websocket.open :连接建立时,客户端这边进行的操作。
websocket.onclose :连接关闭时,客户端这边的操作。
websocket.onerror :连接错误时,客户端这边的操作。
websocket.onmessage :发送请求。
window.onbeforeunload :箭头页面关闭事件,这里让页面关闭后,断开websocket连接。
2、发送请求
这里有个点击事件,当点击 "开始匹配" 按钮,就会发送 "startMatch" 数据,当点击 匹配中...(点击停止匹配),就会发送 "stopMatch" 数据。
java
// 给匹配按钮添加一个点击事件
let matchButton = document.querySelector('#match-button');
matchButton.onclick = function () {
//在触发 websocket 请求之前,先确认下 websocket 连接是否好着
if (websocket.readyState == websocket.OPEN) {
//如果当前 readyState 处在 OPPEN状态,说明连接是好着的
//这里发送的数据有两种可能,开始匹配/停止匹配
if (matchButton.innerHTML == '开始匹配') {
console.log("开始匹配");
websocket.send(JSON.stringify({
message: 'startMatch',
}))
} else if (matchButton.innerHTML == '匹配中...(点击停止)') {
console.log("停止匹配");
websocket.send(JSON.stringify({
message: 'stopMatch',
}));
}
} else {
//这是说明当前连接是异常的状态
alert("当前您的连接已经断开! 请重新登录!");
// location.assign('/login.html');
location.replace("/login.html");
}
}
开启这个点击事件的前提是 :websocket连接 是正常的,如果是异常状态,说明玩家掉线了,那就给个弹窗,然后返回到登录页面,进行重新登录。
3、处理响应
处理响应,说明玩家发送的 开始/停止匹配 请求后端收到了,并发送过来了。
响应状态有2种,一种resp.ok == false: 可能是客户端这里没有进行登录,直接进入游戏大厅页面,导致后端拿到的User=null,这时候就直接给出提示弹窗,然后返回登录页面。
另一种响应状态,resp.ok == true:这时候又会有五个分支
resp.message='startMatch' :这时候就要把客户端的"开始匹配"按钮,转为"匹配中...(点击停止)"按钮。
resp.message='stopMatch' :这时候就要把客户端的"匹配中...(点击停止)"按钮,转为"开始匹配"按钮。
resp.message='matchSuccess' :说明匹配到对手了,进入游戏房间页面。
resp.message='repeatConnection' :说明用户多开情况,给出提示弹窗,跳转到登录页面。
上面这些情况都不是 :说明出现了我们意料之外的bug,打印一个日志信息。
java
//一会重点来实现,要处理服务器返回的响应
websocket.onmessage = function (e) {
// 处理服务器返回的响应数据,这个响应就是针对 "开始匹配" / "结束匹配" 来应对的
//解析得到的响应对象,返回的数据是一个 JSON 字符串,解析成 js 对象
let resp = JSON.parse(e.data);
let matchButton = document.querySelector("#match-button");
if (!resp.ok) {
console.log("游戏大厅中接收到了失败响应! " + resp.reason);
alert("游戏大厅中接收到了失败响应! " + resp.reason);
location.replace("/login.html");
return;
}
if (resp.message == 'startMatch') {
//开始匹配请求发起成功
console.log("进入匹配队列成功");
matchButton.innerHTML = '匹配中...(点击停止)';
} else if (resp.message == 'stopMatch') {
//结束匹配请求发起成功
console.log("离开匹配队列成功");
matchButton.innerHTML = '开始匹配';
} else if (resp.message == 'matchSuccess') {
//已经匹配到对手了
console.log("匹配到对手! 进入游戏房间");
// location.assign("/game_room.html");
location.replace("/game_room.html");
} else if (resp.message == 'repeatConnection') {
// 多开的情况
alert("当前检测到多开,请使用其他账号登录!");
// location.assign("/login.html");
location.replace("/login.html");
} else {
console.log("收到了非法的响应! message=" + resp.message);
}
}
七、梳理前后端交互流程
以下流程保证是在预期逻辑下的匹配过程,不涉及异常等错误情况。
1、玩家的匹配
(1)建立连接
进入登录页面,输入账号密码:
进入游戏页面,建立websocket连接:(后端拿到玩家信息,把该玩家设置为大厅在线状态)
执行到这一步,客户端、服务器的websocket连接建立成功。
(2)玩家发起请求
玩家进入游戏大厅后,点击"开始匹配"按钮:
发送 websocket 请求:带有"startMatch"的数据。
(3)服务器处理请求
服务器接收到前端发来的 message='startMatch' 字样信息,把该玩家加入到匹配队列中。
加入匹配队列:
不断扫描线程,看匹配队列有没有2个玩家:
(这里只分析匹配队列有1个玩家的情况)
构造响应数据,关键字样:ok=true,message='startMatch',把该响应数据发送给客户端。
(4)客户端处理响应
客户端接收到服务器的返回的响应,校验响应数据:message='startMatch',就把"开始匹配"按钮修改为"匹配中...(停止匹配)"(修改的是html文本信息)。
修改后:
以上就是每个玩家都会进入的匹配流程
2、玩家匹配成功
当有玩家匹配成功时,就要把这两个玩家加入同一个游戏房间中。
此时也会跳转到游戏房间页面,如图:(目前还没介绍对局模块,后面博客会介绍)
流程介绍:玩家匹配流程上面已经介绍,下面主要介绍匹配成功的流程
2.1 后端处理请求
(1)服务器发现匹配队列有2个玩家
两个玩家都进行匹配,服务器会把用户都加进对应匹配队列中:
扫描线程:
发现匹配队列有2个玩家了:
跳出这个循环,进行后面的逻辑操作。
(2)两个玩家加入同一个游戏房间
先把两个玩家从匹配队列拿出来:
获取到对应玩家的Session:
把这两个玩家加入到同一个游戏房间:
构造响应,给玩家返回响应:
设置关键信息:message='matchSuccess'
2.2 客户端处理响应
客户端这边拿到关键信息:message='matchSuccess'
那就跳转到游戏房间页面。如图: