[网页五子棋][匹配模块]处理开始匹配/停止匹配请求(匹配算法,匹配器的实现)

article/2025/8/23 16:08:43

文章目录

  • 处理开始匹配/停止匹配请求
  • 匹配算法
  • 实现匹配器(1)
    • 完善匹配器的 TODO
  • 实现匹配器(2)
    • 实现 handlerMatch
      • 线程安全
      • 忙等问题

处理开始匹配/停止匹配请求

实现 handleTestMessage

  • 先从会话中拿到当前玩家的信息
  • 解析客户端发来的请求
  • 判定请求的类型
    • 如果是 startMatch,则把用户加入到匹配队列
    • 如果是 stopMatch,则把用户对象从匹配队列中删除
  • 此处需要实现一个匹配器对象,来处理匹配的实际逻辑
@Override  
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 (response.getMessage().equals("startMatch")) {  // 进入匹配队列  // TODO 先创建一个类,来表示匹配队列,把当前用户给加进去  // 把玩家信息放入匹配队列之后,就可以返回一个响应给客户端了  response.setOk(true);  response.setMessage("startMatch");  } else if (response.getMessage().equals("stopMatch")){  // 退出匹配队列  // TODO 先创建一个类表示匹配队列,把当前用户从队列中移除  // 移除之后,就可以返回一个响应给客户端了  response.setOk(true);  response.setMessage("stopMatch");  } else {  // 非法情况  response.setOk(false);  response.setReason("非法的匹配请求!");  }  
}

匹配算法

目标:从待匹配的玩家中,选出分数尽量相近的玩家

把整个所有的玩家,按照分数,划分为三类:

  1. Normal: socre < 2000
  2. High: score >= 2000 && score < 3000
  3. VeryHigh: score >= 3000
    给这三个等级,分配三个不同的队列。根据当前玩家的分数,来把这个玩家的用户信息,放到对应的队列里面

接下来再搞一个专门的线程,去不停地扫描这个匹配队列。只要队列里面的元素 (匹配的玩家) 凑成了一对,就把这对玩家取出来,放到一个游戏房间中

  • 当前的匹配实现,比较粗糙,只是简单的搞了三个段位的队列
  • 如果想要匹配的更加精确,就可以多搞几个队列

实现匹配器(1)

创建 game.Matcher

  • Matcher 中创建三个队列(按上面分类)
  • 提供 add 方法,供 MatchAPI 类来调用,用来把玩家加入匹配队列
  • 提供 remove 方法,供 MatchAPI 类来调用,用来把玩家移出匹配队列
  • 同时 Matcher 找那个要记录 OnlineUserManager,来获取到玩家的 session
package org.example.java_gobang.game;  import org.example.java_gobang.model.User;  
import org.springframework.beans.factory.annotation.Autowired;  
import org.springframework.stereotype.Component;  import java.util.LinkedList;  
import java.util.Queue;  // 这个类表示”匹配器“,这个类负责完成整个匹配功能  
@Component  
public class Matcher {  // 创建三个匹配队列  private Queue<User> normalQueue = new LinkedList<>();  private Queue<User> highQUeue = new LinkedList<>();  private Queue<User> veryHighQueue = new LinkedList<>();  // 将用户在线状态这个管理器引入,方便我们后续随时能够得到相应玩家的会话信息,也可以判断用户的在线状态  @Autowired  private OnlineUserManager onlineUserManager;  // 操作匹配队列的方法:  // 把玩家放到匹配队列中  public void  add(User user) {  if (user.getScore() < 2000) {  normalQueue.offer(user);  System.out.println("把玩家 " + user.getUsername() + " 加入到了 normalQueue 中!");  } else if (user.getScore() >= 2000 && user.getScore() < 3000) {  highQUeue.offer(user);  System.out.println("把玩家 " + user.getUsername() + " 加入到了 highQueue 中!");  }else {  veryHighQueue.offer(user);  System.out.println("把玩家 " + user.getUsername() + " 加入到了 veryHighQueue 中!");  }  }  // 当玩家点击停止匹配的时候,就需要把玩家从匹配队列中删除  public void remove(User user) {  if (user.getScore() < 2000) {  normalQueue.remove(user);  System.out.println("把玩家 " + user.getUsername() + " 从 normalQueue 中删除了!");  } else if (user.getScore() >= 2000 && user.getScore() < 3000) {  highQUeue.remove(user);  System.out.println("把玩家 " + user.getUsername() + " 从 highQueue 中删除了!");  }else {  veryHighQueue.remove(user);  System.out.println("把玩家 " + user.getUsername() + " 从 veryHighQueue 中删除了!");  }  }  
}

完善匹配器的 TODO

MatchAPI 类中创建 Matcher 对象

@Autowired  
private Matcher matcher;

然后完善 TODO 部分的逻辑
image.png|333

  • 上面的红框改为:macher.add(user)
  • 下面的红框改为:macher.remove(user)

针对连接异常和连接关闭的两个方法,我们也要进行相应处理

  • 当在匹配的时候,突然连接关闭/断开了,相应的匹配就要停止了
  • 停止匹配逻辑为:matcher.remove(user); image.png|324

实现匹配器(2)

修改 game.Matcher,实现匹配逻辑

Matcher 的构造方法中,创建一个线程,使用该线程扫描每个队列,把每个队列的头两个元素取出来,匹配到一组中

// 匿名内部内的方式,继承 Thread 类  
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() {  // 扫描 highQueue            while (true) {  handlerMatch(highQUeue);  }  }  };  t2.start();  Thread t3 = new Thread() {  @Override  public void run() {  // 扫描 veryHighQueue            while (true) {  handlerMatch(veryHighQueue);  }  }  };
}

实现 handlerMatch

private void handlerMatch(Queue<User> matchQueue) {  try {  // 1. 检测队列中元素个数是否达到 2        if (matchQueue.size() < 2) {  return;  }  // 2. 尝试从队列中取出两个玩家  User player1 = matchQueue.poll();  User player2 = matchQueue.poll();  System.out.println("匹配两个玩家:" + player1.getUsername() + ", " + player2.getUsername());  // 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) {  // 如果玩家2 现在不在线了,就把玩家1 重新放回匹配队列中  matchQueue.offer(player1);  return;  }  // 当前能否排到两个玩家是同一个用户的情况吗?一个玩家入队列了两次  // 理论上也不存在  // 1) 如果玩家下线,就会将玩家移出匹配队列  // 2) 又禁止了玩家多开  // 但是仍然在这里多进行一次判定,以免前面的逻辑出现 bug 的时候带来严重的后果  if (session1 == session2) {  // 把其中一个玩家放回匹配队列  matchQueue.offer(player1);  return;  }  // 4. TODO 把这两个玩家放到一个游戏房间中  // 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);  session1.sendMessage(new TextMessage(json2));  }catch (IOException e) {  e.printStackTrace();  }  
}

线程安全

我们主要涉及到要操作 normalQueuehighQueueveryHighQueue 这三个队列,而这三个队列本身就是在多个线程中进行的

  • 使用到多线程代码的时候,一定要时刻注意“线程安全”问题

此处我们使用 synchornized 进行加锁

  • 指定一个“锁对象”
  • 到底针对谁进行加锁
  • 只有多个线程在尝试针对同一个锁对象进行加锁的时候,才会有互斥效果
  • 此处进行加锁的时候,要明确
    • 如果多个线程访问的是不同的队列,就不会涉及到线程安全问题
    • 必须是多个线程操作同一个队列,才需要加锁
  • 因此在加锁的时候选取的锁对象,就是 normalQueuehighQueueveryHighQueue 这三个队列对象本身

image.png image.png

  • addremove 里面的这些对队列的操作进行加锁操作image.png|325

还要把 Macher 类中的 handlerMatch 这个方法全部加锁image.png

忙等问题

如果当前匹配队列中,只有一个元素,或者没有元素,会出现什么效果?
image.png

  • 在这个代码中,就会出现 handlerMatch 已进入方法就快速返回,然后再次进入方法…
  • 循环速度飞快,但是却没有什么实质的意义,这个过程中 CPU 占用率会非常高
  • 这个就是我们说的——忙等

如何解决?

  1. 在调用完 handlerMatch 之后,加上个 sleep(500) 合理吗

这个方法可以,但是不是很完美

  • 当有玩家匹配到了之后,可能要 500ms 之后才能真正得到匹配的返回结果
  • 通过 sleep 是难以两全其美的,要么就得让玩家多等,要么就得让 CPU 多转
  1. 这里我们更好地选择就是使用 wait/notify
  • 在扫描线程中,使用 wait 来等待
  • 当真正有玩家进入匹配队列之后,就调用 notify 来唤醒

添加 waitnotify
image.png

  • handlerMatch 方法里面,当元素未达到 2 的时候,让其进行等待
  • 当三个队列里面任意一个队列有元素加进来了,就进行通知

队列的初始情况可能是 0,如果往队列中添加一个元素,这个时候,让然是不能进行后续的匹配操作,因此在 handlerMatch 方法里面,元素未达到 2 的时候,使用 while 循环检查时更合理的image.png|368


http://www.hkcw.cn/article/fPxcZPOAIK.shtml

相关文章

11.spark源码编译

1.构建环境 构建环境&#xff1a; ubuntu 22.04 jdk 11 scala 2.12 maven 3.9 spark 3.5 2.构建 获取代码 pull代码后&#xff0c;切换到对应分支。 git checkout branch-3.5编译 构建spark mvn -DskipTests clean package \-Dhadoop.version3.3.6 \-Phive -Phive-thrif…

前端必备——Grid布局详解

Grid布局&#xff0c;又称为CSS网格布局&#xff0c;是W3C提出的一个二维布局系统&#xff0c;它允许开发者以行和列的形式来布局网页内容。以下是Grid布局属性的详细解析&#xff1a; 一、容器属性 display grid&#xff1a;将元素设置为块级网格容器。inline-grid&#xff1…

基于Java Web和SSM框架的硕士研究生招生考试系统开发

本文还有配套的精品资源&#xff0c;点击获取 简介&#xff1a;本项目旨在构建一个硕士研究生招生考试专业报考查询及学习系统&#xff0c;使用Java Web技术并集成了Spring、SpringMVC和MyBatis&#xff08;SSM&#xff09;框架。系统通过模块化架构和分离关注点&#xff08…

MCP Web Research Server 安装与配置指南

MCP Web Research Server 安装与配置指南 mcp-webresearch MCP web research server (give Claude real-time info from the web) 项目地址: https://gitcode.com/gh_mirrors/mcpw/mcp-webresearch 1. 项目基础介绍 MCP Web Research Server 是一个基于 Model Context …

0基础入门前端到精通(一)

目录 一丶前端开发介绍 1. 网页的组成部分 2. 网页的本质是什么&#xff1f; 3. 前端代码如何变成网页&#xff1f; 4. 统一网页显示标准&#xff1a;Web 标准 二丶HTML & CSS 1. 什么是 HTML&#xff1f; 2. 什么是 CSS&#xff1f; ​3. HTML 快速入门&#xff0…

一文了解Blob文件格式,前端必备技能之一

文章目录 前言一、什么是Blob&#xff1f;二、Blob的基本特性三、Blob的构造函数四、常见使用场景1. 文件下载2. 图片预览3. 大文件分片上传 四、Blob与其他API的关系1. File API2. FileReader3. URL.createObjectURL()4. Response 五、性能与内存管理六、实际案例&#xff1a;…

双剑破天门:攻防世界Web题解之独孤九剑心法(十)

免责声明&#xff1a;用户因使用公众号内容而产生的任何行为和后果&#xff0c;由用户自行承担责任。本公众号不承担因用户误解、不当使用等导致的法律责任 **本文以攻防世界部分题为例进行演示&#xff0c;后续会对攻防世界大部分的web题目进行演示&#xff0c;如果你感兴趣请…

解决 Android WebView 无法加载 H5 页面常见问题的实用指南

目录 1. WebView 简介 2. 常见问题 3. 网络权限设置 4. 启用 JavaScript 5. DOM Storage 的重要性 6. 处理 HTTPS 问题 7. 设置 WebViewClient 8. 调试工具 9. 其他调试技巧 10. 结论 相关推荐 1. WebView 简介 Android WebView 是一种视图组件&#xff0c;使得 And…

【若依框架】代码生成详细教程,15分钟搭建Springboot+Vue3前后端分离项目,基于Mysql8数据库和Redis5,管理后台前端基于Vue3和Element Plus,开发小程序数据后台

今天我们来借助若依来快速的搭建一个基于springboot的Java管理后台,后台网页使用vue3和 Element Plus来快速搭建。这里我们可以借助若依自动生成Java和vue3代码,这就是若依的强大之处,即便你不会Java和vue开发,只要跟着石头哥也可以快速的开发一款Java和vue程序。 技术点 …

OpenWebUI如何对外提供HTTP接口?

from 公众号&#xff1a;程序员more OpenWebUI通过HTTP方式提供对外接口&#xff0c;使得开发者可以通过HTTP方式快速对接拥有RAG能力的模型基座。 01 OpenWebUI配置app key OpenWebUI使用BearerToken机制对 API 请求进行身份验证。从 Open WebUI 中的“设置>帐户”获取 …

中使馆回应美将撤销中国留学生签证 坚决反对歧视做法

中使馆回应美将撤销中国留学生签证 坚决反对歧视做法!中国驻美国大使馆发言人于5月29日针对美国务院发表的声明作出回应,该声明称将撤销有关中国在美留学生的签证。发言人表示,中方坚决反对美方这一政治性、歧视性的做法。这种行为将严重损害中国在美留学人员的正当合法权益…

【Markdown基础语法】

Markdown语法 顶部 一、标题#~###### 一级标题 二级标题 三级标题 四级标题 五级标题 六级标题 二、粗体 **粗体** 粗体字 三、斜体** 斜体字 四、删除字~~~~ 删除文字​ 五、分割线***、—、___ 六、无序列表-、、* 1 2 3 七、有序列表数字后加点1. 第一项第二项 …

ROS云课基础篇-03-apt趣味小应用-250529

基本看完01和02&#xff0c;还想继续&#xff0c;十分魔幻&#xff0c;来点有趣好玩的吧。 ROS云课基础篇-01-Linux-250529-CSDN博客 ROS云课基础篇-02-C-250529-CSDN博客 在Ubuntu中&#xff0c;有许多有趣且实用的小应用可以为系统增添乐趣和功能性。以下是一些类似xeyes和…

上海老电梯更新费用规定 公积金可提取支付

上海老电梯更新费用规定 公积金可提取支付。为支持职工筹集住宅老旧电梯更新费用,改善居住条件,上海市住房公积金管委会近日印发《关于上海市住房公积金支持老旧电梯更新有关政策的通知》,将于2025年7月1日起施行。根据通知规定,住宅老旧电梯更新项目经业主依法表决通过,小…

网络编程之网络编程预备知识

网络编程预备知识 1. 网络编程 1.1 网络通信 通信/通讯需要具备两个基本条件&#xff1a; 物理媒介&#xff08;物理层面&#xff09; 网线&#xff0c;光纤 协议&#xff08;软件层面&#xff09;网络通信协议 1.2 网络协议层次模型 层次&#xff1a;把不同的功能封装成…

Spring Boot+Activiti7入坑指南初阶版

介绍  Activiti 是一个轻量级工作流程和业务流程管理 (BPM) 平台,面向业务人员、开发人员和系统管理员。其核心是一个超快且坚如磐石的 Java BPMN 2 流程引擎。它是开源的,并根据 Apache 许可证分发。Activiti 可以在任何 Java 应用程序、服务器、集群或云中运行。它与 Spri…

信创改造选择C86,相比于ARM架构有什么优势?

随着信息技术应用创新&#xff08;信创&#xff09;产业的快速发展&#xff0c;信创改造已成为我国信息技术产业升级的重要方向。信创改造的核心目标是实现信息技术的自主可控&#xff0c;保障国家信息安全&#xff0c;同时推动国产软硬件在各行业的广泛应用。 2025年作为信创产…

20250529-C#知识:运算符重载

C#知识&#xff1a;运算符重载 运算符重载能够让我们像值类型数据那样使用运算符对类或结构体进行运算&#xff0c;并且能够自定义运算逻辑。 1、运算符重载及完整代码示例 作用是让自定义的类或者结构体能够使用运算符运算符重载一定是public static的可以把运算符看成一个函…

【AI面试秘籍】| 第24期:Transformers / LLM的词表应该选多大?

在构建和训练Transformers以及大型语言模型 (LLM) 时&#xff0c;词表 (Vocabulary) 大小的选择是一个至关重要且常常令人困惑的问题。它不仅直接影响模型的性能&#xff0c;还关系到模型的计算效率和内存占用。那么&#xff0c;当面试官抛出这个问题时&#xff0c;我们应该如何…

一文清晰理解目标检测指标计算

一、核心概念 1.交并比IoU 预测边界框与真实边界框区域的重叠比&#xff0c;取值范围为[0,1] 设预测边界框为&#xff0c;真实边界框为 公式&#xff1a; IoU计算为两个边界框交集面积与并集面积之比&#xff0c;图示如下 IoU值越高&#xff0c;表示预测边界框与真实边界框的对…