五子棋双人对战项目(3)——匹配模块
目录
一、分析需求
二、约定前后端交互接口
匹配请求:
匹配响应:
三、实现游戏大厅页面(前端代码)
game_hall.html:
common.css:
game_hall.css:
四、实现后端代码
WebSocketConfig
MatchAPI(处理请求)
MatchResponse(响应)
MatchRequest (请求)
OnlineUserManager(用户在线状态)
Matcher(匹配器)
Room(游戏房间)
RoomManager(房间管理器)
五、线程安全问题
1、HashMap 和 多开
2、三个队列
六、忙等问题
一、分析需求
需求:多个玩家,在游戏大厅进行匹配,系统会把实力相近的玩家匹配到一起。
要想实现上述效果,就需要利用到消息推送机制,即需要使用到 WebSocket 协议。如图:
二、约定前后端交互接口
通过需求分析,确认了要使用 WebSocket 协议,来实现消息推送的效果,因此,约定前后端交互接口也是根据 WebSocket 展开的。
WebSocket 协议,可以传输文本数据,也可以传输二进制数据,这里就采用传输 JSON 格式的文本数据。
匹配请求:
这里并不需要传送用户信息,因为在前面登录的时候,就已经把当前用户信息保存到HttpSession中了,在进行WebSocket连接时,只需要把HttpSession中的Session拿过来就行了,并保存在WebSocket连接中。
匹配响应:
这里会有两个不同的匹配响应。
匹配响应1:是指玩家点击开始匹配,玩家的这个操作的请求发送成功,后端返回回来的响应(立即返回的响应)。
匹配响应2:指有两个玩家成功匹配到一起了,服务器主动推送回来的响应(多久返回这个响应?服务器并不知道)。(匹配到的对手信息保存在服务器中)
三、实现游戏大厅页面(前端代码)
game_hall.html:
JSON字符串 和 JS对象的转换:
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>游戏大厅</title><link rel="stylesheet" href="css/common.css"><link rel="stylesheet" href="css/game_hall.css">
</head><body><div class="nav">五子棋对战</div><!-- 整个页面的容器元素‘ --><div class="container"><!-- 这个div在container中是处于垂直水平居中这样的位置的 --><div><!-- 展示用户信息 --><div id="screen"></div><!-- 匹配按钮 --><div id="match-button">开始匹配</div></div></div><script src="./js/jquery.min.js"></script><script>$.ajax({type: 'get',url: '/userInfo',success: function (body) {let screenDiv = document.querySelector("#screen");screenDiv.innerHTML = "玩家: " + body.username + "分数: " + body.score+ "<br> 比赛场次: " + body.totalCount + "获胜场数: " + body.winCount},error: function () {alert("获取用户信息失败");}});// 此处进行初始化 websocket,并且实现前端的匹配逻辑// 此处的路径必须写作 /findMatchlet websocket = new WebSocket("ws://127.0.0.1:8080/findMatch");websocket.onopen = function () {console.log("onopen");}websocket.onclose = function () {console.log("onclose");// alert("游戏大厅中接收到了失败响应! 请重新登录");// location.assign("/login.html");}websocket.onerror = function () {console.log("onerror");}// 监听页面关闭事件,在页面关闭之前,手动调动这里的 websocket 的 close 方法window.onbeforeunload = function () {websocket.close();}//一会重点来实现,要处理服务器返回的响应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.assign("/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");} else {console.log("收到了非法的响应! message=" + resp.message);}}// 给匹配按钮添加一个点击事件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');}}</script></body></html>
common.css:
* {margin: 0;padding: 0;box-sizing: border-box;
}html, body {height: 100%;background-image: url(../image/blackboard.jpg);background-repeat: no-repeat;background-position: center;background-size: cover;
}.nav {height: 50px;background: rgb(50, 50, 50);color: white;line-height: 50px;padding-left: 20px;
}.container {width: 100%;height: calc(100% - 50px);display: flex;align-items: center;justify-content: center;
}
game_hall.css:
.container {width: 100%;height: calc(100% - 50px);display: flex;align-items: center;justify-content: center;
}#screen {width: 400px;height: 200px;font-size: 20px;background-color: gray;color: white;border-radius: 10px;text-align: center;line-height: 100px;
}#match-button {width: 400px;height: 50px;font-size: 20px;background-color: orange;color: white;border: none;outline: none;border-radius: 10px;text-align: center;line-height: 50px;margin-top: 20px;
}#match-button:active{background-color: gray;
}
四、实现后端代码
后端要想建立WebSocket连接,需要创建一个专门的类(MatchAPI),来处理 WebSocket 的请求;同时,还要新建一个类(WebSocketConfig),进行WebSocket连接、配置 WebSocket 连接的路径,以及拿到之前 HTTP 连接时的 Session。
WebSocketConfig
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {@Autowiredprivate MatchAPI matchAPI;@Overridepublic void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {registry.addHandler(matchAPI, "/findMatch").addInterceptors(new HttpSessionHandshakeInterceptor());}
}
MatchAPI(处理请求)
JSON字符串 和 Java对象的转换:
//通过这个类来处理匹配功能中的 websocket 请求
@Slf4j
@Component
public class MatchAPI extends TextWebSocketHandler {@Autowiredprivate OnlineUserManager onlineUserManager;@Autowiredprivate Matcher matcher;private ObjectMapper objectMapper = new ObjectMapper();@Overridepublic 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、拿到了身份信息之后,进行判断当前用户是否已经登录过(在线状态),如果已经是在线,就不该继续进行后续逻辑WebSocketSession tmpSession = onlineUserManager.getFromGameHall(user.getUserId());if(tmpSession != null) {// 说明该用户已经登录了// 针对这个情况,要告知客户端,你这里重复登录了MatchResponse response = new MatchResponse();response.setOk(false);response.setReason("当前用户已经登录, 静止多开!");session.sendMessage(new TextMessage(objectMapper.writeValueAsString(response)));session.close();return;}onlineUserManager.enterGameHall(user.getUserId(), session);
// System.out.println("玩家" + user.getUsername() + " 进入游戏大厅");log.info("玩家 {}",user.getUsername() + " 进入游戏大厅");} catch (NullPointerException e) {e.printStackTrace();// 出现空指针异常,说明当前用户的身份信息为空,也就是用户未登录// 就把当前用户尚未登录,给返回回去MatchResponse response = new MatchResponse();response.setOk(false);response.setReason("您尚未登录,不能进行后续匹配");session.sendMessage(new TextMessage(objectMapper.writeValueAsBytes(response)));}}@Overrideprotected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {// 实现处理开始匹配请求和停止匹配请求User user = (User) session.getAttributes().get("user");// 拿到客户端发给服务器的数据String payload = message.getPayload();// 当前传过来的数据是JSON格式的字符串,就需要把它转成 Java 对象:MatchRequestMatchRequest 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));}@Overridepublic void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {// 玩家下线,删除 OnlineUserManager 中的该用户的Sessiontry {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) {e.printStackTrace();MatchResponse response = new MatchResponse();response.setOk(false);response.setReason("您尚未登录,不能进行后续匹配");session.sendMessage(new TextMessage(objectMapper.writeValueAsBytes(response)));}}@Overridepublic void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {// 玩家下线,删除 OnlineUserManager 中的该用户的Sessiontry {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) {e.printStackTrace();MatchResponse response = new MatchResponse();response.setOk(false);response.setReason("您尚未登录,不能进行后续匹配");session.sendMessage(new TextMessage(objectMapper.writeValueAsBytes(response)));}}
}
对于 WebSocket 请求、返回的响应,把传送的数据封装成这两个类:
MatchResponse(响应)
// 这是表示一个 WebSocket响应
@Data
public class MatchResponse {private boolean ok;private String reason;private String message;
}
MatchRequest (请求)
// 这是表示一个 WebSocket 请求
@Data
public class MatchRequest {private String message;
}
OnlineUserManager(用户在线状态)
之所以要维护用户的在线状态,目的是为了能够在代码中比较方便的获取到某个用户当前的WebSocket 会话,从而通过这个会话来对客户端发送消息。
同时,也能感知到用户的 在线/离线 状态~。
此处使用 哈希表 来维护 userId 和 WebSocketSession 的映射关系。
@Component
public class OnlineUserManager {//这个hash表就是用来表示当前用户在游戏大厅的在线状态private ConcurrentHashMap<Integer, WebSocketSession> gameHall = 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);}
}
Matcher(匹配器)
通过这个匹配器,来处理玩家的匹配功能。
// 这个类表示匹配器,通过这个类来负责整个的匹配功能
@Slf4j
@Component
public class Matcher {// 创建三个匹配队列private Queue<User> normalQueue = new LinkedList<>();private Queue<User> highQueue = new LinkedList<>();private Queue<User> veryHighQueue = new LinkedList<>();@Autowiredprivate OnlineUserManager onlineUserManager;@Autowiredprivate RoomManager roomManager;private ObjectMapper objectMapper = new ObjectMapper();// 操作匹配队列的方法// 把玩家放到匹配队列中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 中");}}// 当玩家点击停止匹配,就需要把该玩家从匹配队列删除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 队列被删除");}}public Matcher() {// 创建三个线程,分别针对这三个匹配队列,进行操作Thread t1 = new Thread() {@Overridepublic void run() {// 扫描normalQueuewhile(true) {handlerMatch(normalQueue);}}};t1.start();Thread t2 = new Thread() {@Overridepublic void run() {while (true) {handlerMatch(highQueue);}}};t2.start();Thread t3 = new Thread() {@Overridepublic void run() {while (true) {handlerMatch(veryHighQueue);}}};t3.start();}public void handlerMatch(Queue<User> matchQueue) {synchronized (matchQueue) {try {// 1、检测队列中元素个数是否达到 2// 队列的初始情况可能是 空// 如果往队列中添加一个元素,这个时候,仍然是不能进行后续匹配操作的// 因此在这里使用 while 循环检查更合理while (matchQueue.size() < 2) {matchQueue.wait();}// 2、尝试从队列中取出两个玩家User player1 = matchQueue.poll();User player2 = matchQueue.poll();log.info("匹配出两个玩家: " + 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) {// 如果玩家1不在线了,就把玩家2放回匹配队列matchQueue.offer(player1);return;}// 当前能否排到两个玩家是同一个用户的情况吗?一个玩家入队列两次// 理论上也不会存在~// 1) 如果玩家下线,就会对玩家移除匹配队列// 2) 又禁止了玩家多开// 但是仍然在这里多进行一次判定,以免前面的逻辑出现 bug 时,带来严重的后果if(session1 == session2) {// 把其中的一个玩家放回匹配队列matchQueue.offer(player1);return;}// 4、把这两个玩家放到同一个游戏房间中Room room = new Room();roomManager.add(room, player1.getUserId(), player2.getUserId());// 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));} catch (IOException | InterruptedException e) {e.printStackTrace();}}}
}
Room(游戏房间)
// 这个类表示一个游戏房间
@Data
public class Room {// 使用字符串类型来表示,方便生成唯一值private String roomId;private User user1;private User user2;public Room() {// 构造 Room 的时候生成一个唯一的字符串表示房间 id// 使用 UUID 来作为房间 idroomId = UUID.randomUUID().toString();}
}
RoomManager(房间管理器)
// 房间管理器类
// 这个类也希望有唯一实例
@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 映射关系不存在,直接返回 nullreturn null;}return rooms.get(roomId);}
}
五、线程安全问题
1、HashMap 和 多开
如果多个线程访问同一个HashMap,就容易出现线程安全问题。
如果同时多个用户和服务器 建立/断开 连接,此时服务器就是并发的在针对 HashMap 进行修改。
所以要避免这种情况,解决这个线程安全问题,可以直接使用ConcurrentHashMap。
当多个浏览器,通时对一个用户进行登录,进入游戏大厅,会引发下面这种问题:
所以,我们不仅要解决线程安全问题,也要考虑用户多开的情况,那么用户能进行多开操作吗?显然是不能的,所以上面的代码逻辑也是会处理这种多开的情况,如果当前用户已经登录,禁止其他地方再登录。
2、三个队列
在匹配模块(Matcher类),为了划分玩家水平实力,使用了三个队列表示不同的实力分段;
同时,创建三个线程,当用户进行匹配时,就会不停的扫描这三个队列,看是否能匹配对局成功。
但因为匹配时,就会把玩家加入到对应段位的队列,而停止匹配,也会把玩家从对应的队列删除,又有多个线程并发的去执行,所以,存在线程安全问题。
怎么办?
针对这三个队列对象,分别进行加锁,如图:
这三个线程都是调用同一个方法。如图:
因此,我们针对这一个方法加锁就好了:
六、忙等问题
我们创建了三个线程:
会不停的去扫描这三个队列,元素个数是否达到2,如果达到2,就要吧这两个用户取出来,放在同一个房间中进行对局。
但这里要一直扫描码?显然是不用的,所以可以在这里 wait 一下。
既然 wait了,那就要有 notify,来唤醒它,继续锁竞争。那什么时候唤醒呢?当然是这个队列有新的用户加进来了,那再进行唤醒,再重新判断用户个数是否达到2.
这样,我们就能解决忙等的问题。
相关文章:

五子棋双人对战项目(3)——匹配模块
目录 一、分析需求 二、约定前后端交互接口 匹配请求: 匹配响应: 三、实现游戏大厅页面(前端代码) game_hall.html: common.css: game_hall.css: 四、实现后端代码 WebSocketConfig …...
开源软件简介
一、开源运动的发起 近几十年,软件已经称为战略性的社会资源。各大软件供应商传统的对外封锁源代码的运营模式虽说有积极的一面,比如可以维护开发商的利益,使其可以持续地维护进一步开发的能力,以及可以保护软件商及客户的私密信息…...

Bruno:拥有 11.2k star 的免费开源 API 测试工具
Github 开源地址: https://github.com/usebruno/bruno 官网地址: https://www.usebruno.com/ 下载地址: https://www.usebruno.com/downloads 使用文档: https://docs.usebruno.com/ Bruno 是一款全新且创新的 API 客户端&…...

C动态内存管理
前言:不知不觉又过去了很长的一段时间。今天对C语言中的动态内存管理进行一个系统性的总结。 1 为什么要有动态内存分配 在C语言中,使用int,float,double,short等数据内置类型以及数组不是也可以开辟内存空间吗&…...

系列二、案例实操
一、创建表空间 1.1、概述 在Oracle数据库中,表空间是一个逻辑存储单位,它是Oracle数据库中存储数据的地方。 1.2、超级管理员登录 sqlplus / as sysdba 1.3、创建表空间 create tablespace water_boss datafile C:\Programs\oracle11g\oradata\orcl\…...

Python编码系列—Python状态模式:轻松管理对象状态的变化
🌟🌟 欢迎来到我的技术小筑,一个专为技术探索者打造的交流空间。在这里,我们不仅分享代码的智慧,还探讨技术的深度与广度。无论您是资深开发者还是技术新手,这里都有一片属于您的天空。让我们在知识的海洋中…...

卸载WSL(Ubuntu),卸载linux
禁用 WSL 功能 打开 Windows 功能: 按下 Windows R 打开运行对话框,输入 optionalfeatures,然后按回车。 禁用 WSL: 在弹出的 Windows 功能窗口中,找到 适用于 Linux 的 Windows 子系统(Windows Subsystem…...
Lumerical脚本语言-系统(System)
系统命令包括同操作系统文件系统交互的命令、以及运行脚本文件的命令等。 1、系统命令 命令描述newproject 创建一个新的模拟设计环境 newmode 创建一个新的 MODE 设计环境 save 保存一个 fsp 文件或者 lms 文件 load装载一个 fsp 文件或者 lms 文件 del 删除一个文件 rm 删除一…...

QT 界面编程中使用协程
QT 界面编程中使用协程 一、概述二、集成2.1、编译 Acl2.2、将 Acl 库集成到 QT 项目中2.3、开始编写代码2.3.1、QT 程序初始化时初始化 Acl 协程2.3.2、在界面中创建协程2.3.3、界面程序退出前需要停止协程调度2.3.4、在界面线程中下载数据2.3.5、在协程中延迟创建窗口 2.4、效…...

macOS 开发环境配置与应用开发
✅作者简介:2022年博客新星 第八。热爱国学的Java后端开发者,修心和技术同步精进。 🍎个人主页:Java Fans的博客 🍊个人信条:不迁怒,不贰过。小知识,大智慧。 💞当前专栏…...

第13讲 实践:设计SLAM系统
设计一个视觉里程计,理解SLAM软件框架如何搭建,理解视觉里程计设计容易出现的问题以及解决方法。 目录 1、工程目标 2、工程框架 3、实现 附录 1、工程目标 实现一个精简版的双目视觉里程计。由一个光流追踪的前端和一个局部BA的后端组成。 2、工程…...

NeRF2: Neural Radio-Frequency Radiance Fields 笔记
任务:用 NeRF 对无线信号的传播进行建模,建模完成后可以用NeRF网络生成新位置下的信号。生成的信号用于指纹定位、信道估计等下游任务。 核心思路 在视觉 NeRF 的基础上,根据无线信号的特点修改了隐式场模型、渲染函数,网络的输…...

以太网交换安全:MAC地址表安全
一、MAC地址表安全 MAC地址表安全是网络安全中的一个重要方面,它涉及到网络设备的MAC地址表的管理和保护。以下是对MAC地址表安全的详细介绍: (1)基本概念 定义:MAC地址表是网络设备(如交换机࿰…...
CSS综合页布面局案例
写的比较一般,如果想要参考可以点击链接。 CSS综合案例(登录页面)资源-CSDN文库 引言: 我们学习CSS和HTML都是为了想要做一个网页布局,但是每逢上手可能就会需要查阅很多语言,我觉得是没有什么问题的,熟能生巧,编程是需要练的,但是写网页的时候需要实现某个效果时需…...

低代码可视化-UniApp二维码可视化-代码生成器
市面上提供了各种各样的二维码组件,做了一简单的uniapp二维码组件,二维码实现依赖davidshimjs/qrcodejs。 组件特点 跨浏览器支持:利用Canvas元素实现二维码的跨浏览器兼容性,兼容微信小程序、h5、app。 无依赖性:QR…...
Electron 使用 Nodemon 配置自动重启
在Electron项目中,每次修改了代码都需要手动关闭应用,再执行npm start重启应用。 Nodemon 是一个非常实用的工具,主要用于在开发 Node.js 应用时自动监测文件的变化并重新启动服务器。 安装nodemon 开发环境安装nodemon: npm …...

JVM和GC监控技术
一、监控技术简介 JVM是什么?项目里面有JVM吗?JVM跟Tomcat有什么关系?为什么需要去分析JVM? 1. JVM(全称:Java Virtual Machine),Java虚拟机 是Java程序运行的环境,它是一个虚构的计算机&…...
Android中级控件
文章目录 图形定制1.图形Drawable2.形状图像3.九宫格图片4.状态列表图形 选择按钮1.复选框CheckBox2.开关按钮Switch3.单选按钮 文本输入1.编辑框EditText2.焦点变更监听器3.文本变化监听器 对话框1.提醒对话框2.日期对话框DatePickerDialog3.时间对话框TimePickerDialog 图形定…...

WebSocket消息防丢ACK和心跳机制对信息安全性的作用及实现方法
WebSocket消息防丢ACK和心跳机制对信息安全性的作用及实现方法 在现代即时通讯(IM)系统和实时通信应用中,WebSocket作为一种高效的双向通信协议,得到了广泛应用。然而,在实际使用中,如何确保消息的可靠传输…...

生信初学者教程(二十二):Boruta+RF筛选候选标记物
文章目录 介绍加载R包导入数据准备数据机器学习特征筛选数据分割基础模型Boruta特征筛选调参最终分类模型测试集验证标记基因输出结果总结介绍 采用了Boruta结合 RF(Random Forest) 的方法,对差异基因(参考 @sec-different-limma) 进行了特征筛选。通过这种方法,能够从大…...
反向工程与模型迁移:打造未来商品详情API的可持续创新体系
在电商行业蓬勃发展的当下,商品详情API作为连接电商平台与开发者、商家及用户的关键纽带,其重要性日益凸显。传统商品详情API主要聚焦于商品基本信息(如名称、价格、库存等)的获取与展示,已难以满足市场对个性化、智能…...

工业安全零事故的智能守护者:一体化AI智能安防平台
前言: 通过AI视觉技术,为船厂提供全面的安全监控解决方案,涵盖交通违规检测、起重机轨道安全、非法入侵检测、盗窃防范、安全规范执行监控等多个方面,能够实现对应负责人反馈机制,并最终实现数据的统计报表。提升船厂…...
day52 ResNet18 CBAM
在深度学习的旅程中,我们不断探索如何提升模型的性能。今天,我将分享我在 ResNet18 模型中插入 CBAM(Convolutional Block Attention Module)模块,并采用分阶段微调策略的实践过程。通过这个过程,我不仅提升…...

汽车生产虚拟实训中的技能提升与生产优化
在制造业蓬勃发展的大背景下,虚拟教学实训宛如一颗璀璨的新星,正发挥着不可或缺且日益凸显的关键作用,源源不断地为企业的稳健前行与创新发展注入磅礴强大的动力。就以汽车制造企业这一极具代表性的行业主体为例,汽车生产线上各类…...
土地利用/土地覆盖遥感解译与基于CLUE模型未来变化情景预测;从基础到高级,涵盖ArcGIS数据处理、ENVI遥感解译与CLUE模型情景模拟等
🔍 土地利用/土地覆盖数据是生态、环境和气象等诸多领域模型的关键输入参数。通过遥感影像解译技术,可以精准获取历史或当前任何一个区域的土地利用/土地覆盖情况。这些数据不仅能够用于评估区域生态环境的变化趋势,还能有效评价重大生态工程…...

CMake 从 GitHub 下载第三方库并使用
有时我们希望直接使用 GitHub 上的开源库,而不想手动下载、编译和安装。 可以利用 CMake 提供的 FetchContent 模块来实现自动下载、构建和链接第三方库。 FetchContent 命令官方文档✅ 示例代码 我们将以 fmt 这个流行的格式化库为例,演示如何: 使用 FetchContent 从 GitH…...

关键领域软件测试的突围之路:如何破解安全与效率的平衡难题
在数字化浪潮席卷全球的今天,软件系统已成为国家关键领域的核心战斗力。不同于普通商业软件,这些承载着国家安全使命的软件系统面临着前所未有的质量挑战——如何在确保绝对安全的前提下,实现高效测试与快速迭代?这一命题正考验着…...

让回归模型不再被异常值“带跑偏“,MSE和Cauchy损失函数在噪声数据环境下的实战对比
在机器学习的回归分析中,损失函数的选择对模型性能具有决定性影响。均方误差(MSE)作为经典的损失函数,在处理干净数据时表现优异,但在面对包含异常值的噪声数据时,其对大误差的二次惩罚机制往往导致模型参数…...
return this;返回的是谁
一个审批系统的示例来演示责任链模式的实现。假设公司需要处理不同金额的采购申请,不同级别的经理有不同的审批权限: // 抽象处理者:审批者 abstract class Approver {protected Approver successor; // 下一个处理者// 设置下一个处理者pub…...

【C++进阶篇】智能指针
C内存管理终极指南:智能指针从入门到源码剖析 一. 智能指针1.1 auto_ptr1.2 unique_ptr1.3 shared_ptr1.4 make_shared 二. 原理三. shared_ptr循环引用问题三. 线程安全问题四. 内存泄漏4.1 什么是内存泄漏4.2 危害4.3 避免内存泄漏 五. 最后 一. 智能指针 智能指…...