上期我们实现了websocket后端的大部分代码,这期我们实现具体的匹配逻辑
1. 定义Mather类
我们新建一个Matcher类用来实现匹配逻辑
@Component
public class Matcher {
//每个匹配队列代表不同的段位,这里约定每一千分为一个段位
private ArrayList<Queue<User>> matchQueueList = new ArrayList<>();
@Autowired
private ObjectMapper objectMapper;
@Autowired
private OnlineUserManager onlineUserManager;
public Matcher() {
//暂定三个段位
for(int i = 0; i < 3; i++) {
matchQueueList.add(new LinkedList<>());
}
}
public void add(User user) {
int index = Math.min(user.getScore() / 3, 2);
Queue<User> queue = matchQueueList.get(index);
//对操作的队列加锁保证线程安全
synchronized (queue) {
queue.offer(user);
queue.notify();
}
System.out.println("用户 " + user.getUsername() + " 加入了 " + index + "号 队列");
}
public void remove(User user) {
int index = Math.min(user.getScore() / 3, 2);
Queue<User> queue = matchQueueList.get(index);
synchronized (queue) {
queue.remove(user);
}
System.out.println("把用户 " + user.getUsername() + " 从 " + index + "号 队列中删除");
}
}
2.修改websocket后端代码
//接收到请求后执行
@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
User user = (User) session.getAttributes().get("user");
MatchRequest request = objectMapper.readValue(message.getPayload(), 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.setErrMsg("非法请求");
}
//返回响应
session.sendMessage(new TextMessage(objectMapper.writeValueAsString(response)));
}
由于存在在匹配中途断开连接的情况,所有我们还要在断开连接代码中增加退出队列的代码进行:
//连接异常时执行
@Override
public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
//连接异常断开,玩家下线
try {
User user = (User)session.getAttributes().get("user");
//防止重复登录时删除正常登录的在线信息
if(onlineUser.getFromHall(user.getUserId()).equals(session)) {
onlineUser.exitGameHall(user.getUserId());
System.out.println("连接异常断开,用户:" + user.getUsername() + " 已下线");
//用户可能还在匹配队列中
matcher.remove(user);
}
}catch (NullPointerException e) {
e.printStackTrace();
MatchResponse response = new MatchResponse();
response.setMessage("no_login");
response.setOk(false);
response.setErrMsg("用户未登录");
session.sendMessage(new TextMessage(objectMapper.writeValueAsString(response)));
}
}
//连接正常断开后执行
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
//连接正常断开,玩家下线
try {
User user = (User)session.getAttributes().get("user");
//防止重复登录时删除正常登录的在线信息
if(onlineUser.getFromHall(user.getUserId()).equals(session)) {
onlineUser.exitGameHall(user.getUserId());
System.out.println("连接正常断开,用户:" + user.getUsername() + " 已下线");
//用户可能还在匹配队列中
matcher.remove(user);
}
}catch (NullPointerException e) {
e.printStackTrace();
MatchResponse response = new MatchResponse();
response.setMessage("no_login");
response.setOk(false);
response.setErrMsg("用户未登录");
session.sendMessage(new TextMessage(objectMapper.writeValueAsString(response)));
}
}
3. 实现匹配功能
3.1 创建线程扫描队列
我们为每个匹配队列创建一个线程,用来实现匹配功能,我们在构造方法中创建线程:
public Matcher() {
//暂定三个段位
for(int i = 0; i < 3; i++) {
matchQueueList.add(new LinkedList<>());
}
//每个队列创建一个线程扫描完成匹配功能
for(Queue<User> q : matchQueueList) {
Thread t = new Thread(()->{
while(true) {
//调用handlerMatch()完成匹配功能
handlerMatch(q);
}
});
}
}
3.2 实现handlerMatch()方法进行匹配
public void handlerMatch(Queue<User> matchQueue) {
try {
//对操作的队列加锁保证线程安全
synchronized (matchQueue) {
//1.检测队列中是否有两个元素
while(matchQueue.size() < 2) {
matchQueue.wait();
}
//2.从队列中取出两个玩家
User user1 = matchQueue.poll();
User user2 = matchQueue.poll();
//3.获取到两个玩家的会话信息
WebSocketSession session1 = onlineUserManager.getFromHall(user1.getId());
WebSocketSession session2 = onlineUserManager.getFromHall(user2.getId());
//4.todo 把两个玩家放到一个游戏房间中
//5.给用户返回匹配成功的响应
MatchResponse response = new MatchResponse();
response.setOk(true);
response.setMessage("success");
String json = objectMapper.writeValueAsString(response);
session1.sendMessage(new TextMessage(json));
session2.sendMessage(new TextMessage(json));
}
}catch (IOException | InterruptedException e) {
e.printStackTrace();
}
}
游戏房间功能我们下一期再实现。