浅谈多人游戏原理和简单实现。
- 😜作 者:是江迪呀
- ✒️本文关键词:
websocket
、网络
、原理
、多人游戏
- ☀️每日 一言:
这世上有两种东西无法直视,一是太阳,二是人心!
一、我的游戏史
我最开始接触游戏要从一盘300游戏的光碟说起,那是家里买DVD送的,《魂斗罗》、《超级马里奥》天天玩。自从买回来后,我就经常和姐姐因为抢电视机使用权而大打出手。有次她把遥控器藏到了沙发的夹层里,被我妈一屁股做成了两半,我和我姐喜提一顿暴打。那顿是我挨得最狠的,以至于现在回想起来,屁股还条件反射的隐隐作痛。
后来我骗我妈说我要学习英语、练习打字以后成为祖国的栋梁之才!让她给我买台小霸王学习机(游戏机),在我一哭二闹三上吊胡搅蛮缠的攻势下,我妈妥协了。就此我接触到了FC
游戏。现在还能记得我和朋友玩激龟快打,满屋子的小朋友在看的场景。经常有家长在我家门口喊他家小孩吃饭。那时候我们县城里面有商店卖游戏卡,小卡一张5块钱,一张传奇卡25-40块不等(所谓传奇卡就是角色扮演,带有存档的游戏),每天放学都要去商店去看看,有没有新的游戏卡,买不起,就看下封面过过瘾。我记得我省吃俭用一个多月,买了两张卡:《哪吒传奇》和《重装机兵》那是真的上瘾,没日没夜的玩。
再然后我接触到了手机游戏,记得那时候有个软件叫做冒泡游戏(我心目的中的Stream
),里面好多游戏,太吸引我了。一个游戏一般都是几百KB,最大也就是几MB,不过那时候流量很贵,1块钱1MB,并且!一个月只有30Mb。我姑父是收手机的,我在他那里搞到了一部半智能手机,牌子我现在还记得:诺基亚N70,那时候我打开游戏就会显示一杯冒着热气的咖啡,我很喜欢这个图标,因为看见它意味着我的游戏快加载完成了,没想到,十几年后我们会再次相遇,哈哈哈哈。我当时玩了一款网游叫做:《幻想三国》,第一回接触网游简直惊呆了,里面好多人都是其他玩的家,这太有趣了。并且我能在我的手机上看到其他玩家,能够看到他们的行为动作,这太神奇了!!!我也一直思考这到底是怎么实现的!
最后是电脑游戏,单机:《侠盗飞车》、《植物大战僵尸》、《虐杀原型》;网游:《DNF》、《CF》、《LOL》、《梦幻西游》我都玩过。
不过那个疑问一直没有解决,也一值留在我心中 —— 在网络游戏中,是如何实时更新其他玩家行为的呢?
二、解惑
在我进入大学后,我选择了软件开发专业,真巧!再次遇到了那个冒着热气的咖啡图标,这时我才知道它叫做——Java
。我很认真的去学,希望有一天能够做一款游戏!
参加工作后,我并没有如愿以偿,我成为了一名Java开发程序员
,但是我在日常的开发的都是web
应用,接触到大多是HTTP
请求,它是种请求-响应协议模式。这个问题也还是想不明白,难道每当其他玩家做一个动作都需要发送一次HTTP请求?然后响应给其他玩家。这样未免效率也太低了吧,如果一个服务器中有几千几万人,那么服务器得承受多大压力呀!一定不是这样的!!!
直到我遇到了Websocket
,这是一种长连接,而HTTP
是一种短连接,顿时这个问题我就想明白了。在此二者的区别我就不过多赘述了,请看我的另一篇文章:WebSocket详解以及应用
知道了这个知识后,我终于能够大致明白了网络游戏的基本原理。原来网络游戏是由客户端和服务器端组成的,客户端就是我们下载到电脑或者手机上的应用,而服务器端就是把其他玩家连接起来的中转站,还有一点需要说明的是,网络游戏是分房间的,这个房间就相当于一台服务器。首先,在玩家登陆客户端并选择房间建立长连接后,A玩家做出移动的动作,随即会把这个动作指令上传给服务器,然后服务器再将指令广播到房间中的其他玩家的客户端来操作A的角色,这样就可以实现实时更新其他玩家行为。
三、简单实现
客户端和服务端在处理指令时,方法必须是配套的。比如说,有新的玩家连接到服务器,那么服务器就应当向其它客户端广播创建一个新角色的指令,客户端在接收到该指令后,执行客户端创建角色的方法。
为了方便演示,这里需要定义两个HTML
来表示两个不同的客户端不同的玩家,这两套客户端代码除了玩家的信息不一样,其它完全一致!!!
3.1 客户端实现步骤
我在这里客户端使用HTML
+JQ
实现
客户端——1代码:
(1)创建画布
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Canvas Game</title><style>canvas {border: 1px solid black;}</style><script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
</head>
<body>
<canvas id="gameCanvas" width="800" height="800"></canvas>
</body>
</html>
(2)设置1s60帧更新页面
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');
function clearCanvas() {ctx.clearRect(0, 0, canvas.width, canvas.height);
}
function gameLoop() {clearCanvas();players.forEach(player => {player.draw();});}setInterval(gameLoop, 1000 / 60);
//清除画布方法
function clearCanvas() {ctx.clearRect(0, 0, canvas.width, canvas.height);}
(3)连接游戏服务器并处理指令
这里使用websocket链接游戏服务器
//连接服务器
const websocket = new WebSocket("ws://192.168.31.136:7070/websocket?userId=" + userId + "&userName=" + userName);//向服务器发送消息function sendMessage(userId,keyCode){const messageData = {playerId: userId,keyCode: keyCode};websocket.send(JSON.stringify(messageData));}//接收服务器消息,并根据不同的指令,做出不同的动作websocket.onmessage = event => {const data = JSON.parse(event.data);// 处理服务器发送过来的消息console.log('Received message:', data);//创建游戏对象if(data.type == 1){console.log("玩家信息:" + data.players.length)for (let i = 0; i < data.players.length; i++) {console.log("玩家id:"+playerOfIds);createPlayer(data.players[i].playerId,data.players[i].pointX, data.players[i].pointY, data.players[i].color);}}//销毁游戏对象if(data.type == 2){console.log("玩家信息:" + data.players.length)for (let i = 0; i < data.players.length; i++) {destroyPlayer(data.players[i].playerId)}}//移动游戏对象if(data.type == 3){console.log("移动;玩家信息:" + data.players.length)for (let i = 0; i < data.players.length; i++) {players.filter(player => player.id === data.players[i].playerId)[0].move(data.players[i].keyCode)}}};
(4)创建玩家对象
//存放游戏对象
let players = [];
//playerId在此写死,正常情况下应该是用户登录获取的
const userId = "1"; // 用户的 id
const userName = "逆风笑"; // 用户的名称
//玩家对象
class Player {constructor(id,x, y, color) {this.id = id;this.x = x;this.y = y;this.size = 30;this.color = color;}//绘制游戏角色方法draw() {ctx.fillStyle = this.color;ctx.fillRect(this.x, this.y, this.size, this.size);}//游戏角色移动方法 move(keyCode) {switch (keyCode) {case 37: // Leftthis.x = Math.max(0, this.x - 10);break;case 38: // Upthis.y = Math.max(0, this.y - 10);break;case 39: // Rightthis.x = Math.min(canvas.width - this.size, this.x + 10);break;case 40: // Downthis.y = Math.min(canvas.height - this.size, this.y + 10);break;}this.draw();}}
(5)客户端创建角色方法
//创建游戏对象方法
function createPlayer(id,x, y, color) {const player = new Player(id,x, y, color);players.push(player);playerOfIds.push(id);return player;
}
(6)客户端销毁角色方法
在玩家推出客户端后,其它玩家的客户端应当销毁对应的角色。
//角色销毁
function destroyPlayer(playId){players = players.filter(player => player.id !== playId);
}
客户端——2代码:
客户端2的代码只有玩家信息不一致:
const userId = "2"; // 用户的 idconst userName = "逆风哭"; // 用户的名称
3.2 服务器端
服务器端使用Java
+websocket
来实现!
(1)引入依赖:
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId><version>2.1.2.RELEASE</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId><version>2.3.7.RELEASE</version></dependency><dependency><groupId>org.apache.commons</groupId><artifactId>commons-lang3</artifactId><version>3.11</version></dependency><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.75</version></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.16.16</version></dependency><dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.6.3</version></dependency>
(2)创建服务器
@Component
@ServerEndpoint("/websocket")
@Slf4j
public class Server {/*** 服务器玩家池* 解释:这里使用 ConcurrentHashMap为了保证线程安全,不会出现同一个玩家存在多条记录问题* 使用 static fina修饰 是为了保证 playerPool 全局唯一*/private static final ConcurrentHashMap<String, Server> playerPool = new ConcurrentHashMap<>();/*** 存储玩家信息*/private static final ConcurrentHashMap<String, Player> playerInfo = new ConcurrentHashMap<>();/*** 已经被创建了的玩家id*/private static ConcurrentHashMap<String, Server> createdPlayer = new ConcurrentHashMap<>();private Session session;private Player player;/*** 连接成功后调用的方法*/@OnOpenpublic void webSocketOpen(Session session) throws IOException {Map<String, List<String>> requestParameterMap = session.getRequestParameterMap();String userId = requestParameterMap.get("userId").get(0);String userName = requestParameterMap.get("userName").get(0);this.session = session;if (!playerPool.containsKey(userId)) {int locationX = getLocation(151);int locationY = getLocation(151);String color = PlayerColorEnum.getValueByCode(getLocation(1) + 1);Player newPlayer = new Player(userId, userName, locationX, locationY,color,null);playerPool.put(userId, this);this.player = newPlayer;//存放玩家信息playerInfo.put(userId,newPlayer);}log.info("玩家:{}|{}连接了服务器", userId, userName);// 创建游戏对象this.createPlayer(userId);}/*** 接收到消息调用的方法*/@OnMessagepublic void onMessage(String message, Session session) throws IOException, InterruptedException {log.info("用户:{},消息{}:",this.player.getPlayerId(),message);PlayerDTO playerDTO = new PlayerDTO();Player player = JSONObject.parseObject(message, Player.class);List<Player> players = new ArrayList<>();players.add(player);playerDTO.setPlayers(players);playerDTO.setType(OperationType.MOVE_OBJECT.getCode());String returnMessage = JSONObject.toJSONString(playerDTO);//广播所有玩家for (String key : playerPool.keySet()) {synchronized (session){String playerId = playerPool.get(key).player.getPlayerId();if(!playerId.equals(this.player.getPlayerId())){playerPool.get(key).session.getBasicRemote().sendText(returnMessage);}}}}/*** 关闭连接调用方法*/@OnClosepublic void onClose() throws IOException {String playerId = this.player.getPlayerId();log.info("玩家{}退出!", playerId);Player playerBaseInfo = playerInfo.get(playerId);//移除玩家for (String key : playerPool.keySet()) {playerPool.remove(playerId);playerInfo.remove(playerId);createdPlayer.remove(playerId);}//通知客户端销毁对象destroyPlayer(playerBaseInfo);}/*** 出现错误时调用的方法*/@OnErrorpublic void onError(Throwable error) {log.info("服务器错误,玩家id:{},原因:{}",this.player.getPlayerId(),error.getMessage());}/*** 获取随即位置* @param seed* @return*/private int getLocation(Integer seed){Random random = new Random();return random.nextInt(seed);}
}
websocket配置:
@Configuration
public class ServerConfig {@Beanpublic ServerEndpointExporter serverEndpointExporter(){return new ServerEndpointExporter();}
}
(3)创建玩家对象
玩家对象:
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Player {/*** 玩家id*/private String playerId;/*** 玩家名称*/private String playerName;/*** 玩家生成的x坐标*/private Integer pointX;/*** 玩家生成的y坐标*/private Integer pointY;/*** 玩家生成颜色*/private String color;/*** 玩家动作指令*/private Integer keyCode;
}
创建玩家对象返回给客户端DTO:
@Data
@AllArgsConstructor
@NoArgsConstructor
public class PlayerDTO {private Integer type;private List<Player> players;
}
玩家移动指令返回给客户端DTO:
@Data
@AllArgsConstructor
@NoArgsConstructor
public class PlayerMoveDTO {private Integer type;private List<Player> players;
}
(4)动作指令
public enum OperationType {CREATE_OBJECT(1,"创建游戏对象"),DESTROY_OBJECT(2,"销毁游戏对象"),MOVE_OBJECT(3,"移动游戏对象"),;private Integer code;private String value;OperationType(Integer code, String value) {this.code = code;this.value = value;}public Integer getCode() {return code;}public String getValue() {return value;}
}
(5)创建对象方法
/*** 创建对象方法* @param playerId* @throws IOException*/private void createPlayer(String playerId) throws IOException {if (!createdPlayer.containsKey(playerId)) {List<Player> players = new ArrayList<>();for (String key : playerInfo.keySet()) {Player playerBaseInfo = playerInfo.get(key);players.add(playerBaseInfo);}PlayerDTO playerDTO = new PlayerDTO();playerDTO.setType(OperationType.CREATE_OBJECT.getCode());playerDTO.setPlayers(players);String syncInfo = JSONObject.toJSONString(playerDTO);for (String key :playerPool.keySet()) {playerPool.get(key).session.getBasicRemote().sendText(syncInfo);}// 存放createdPlayer.put(playerId, this);}}
(6)销毁对象方法
/*** 销毁对象方法* @param playerBaseInfo* @throws IOException*/private void destroyPlayer(Player playerBaseInfo) throws IOException {PlayerDTO playerDTO = new PlayerDTO();playerDTO.setType(OperationType.DESTROY_OBJECT.getCode());List<Player> players = new ArrayList<>();players.add(playerBaseInfo);playerDTO.setPlayers(players);String syncInfo = JSONObject.toJSONString(playerDTO);for (String key :playerPool.keySet()) {playerPool.get(key).session.getBasicRemote().sendText(syncInfo);}}
四、演示
4.1 客户端1登陆服务器
4.2 客户端2登陆服务器
4.3 客户端2移动
4.4 客户端1移动
4.5 客户端1退出
完结撒花
完整代码传送门
五、总结
以上就是我对网络游戏如何实现玩家实时同步的理解与实现,我实现后心里也释然了,哈哈哈,真的好有趣!!!
我希望大家也是,不要失去好奇心,遇到自己感兴趣的事情,一定要多思考呀~
后来随着我经验的不断积累,我又去了解了一下Java
作为游戏服务器的相关内容,发现Netty
更适合做这个并且更容易入门,比如《我的世界》一些现有的服务器就是使用Netty
实现的。有空也实现下,玩玩~
相关文章:

浅谈多人游戏原理和简单实现。
😜作 者:是江迪呀✒️本文关键词:websocket、网络、原理、多人游戏☀️每日 一言:这世上有两种东西无法直视,一是太阳,二是人心! 一、我的游戏史 我最开始接触游戏要从一盘300游戏…...

活动预告 | 龙智、紫龙游戏与JFrog专家将出席龙智DevSecOps研讨会,探讨企业大规模开发创新
2023年9月8日(周五)下午13:30-19:45,龙智即将携手Atlassian与JFrog在上海共同举办主题为“大规模开发创新:如何提升企业级开发效率与质量”的线下研讨会。 在此次研讨会上,龙智高级咨询顾问、Atlassian认证专家叶燕秀…...
米每秒和千米每小时的换算
千米每时和米每秒怎么换算?1米/秒3600米/3600秒 米每秒和千米每小时的换算(米每秒和千米每小时的换算) 3.6千米/小时。 3.6千米/小时1米/秒米每秒和千米每小时的换算1 米/秒(米每秒)3.6 千米/时(千米每小时)。 1米每秒3600米每时3.6千米每时。因为1小时3600秒。小时是一个时…...

js实现图形验证码
图形验证码起什么作用: 可以防止:恶意破解密码、刷票、论坛灌水,有效防止某个黑客对某一个特定注册用户用特定程序暴力破解方式进行不断的登录尝试。 验证码一般是防止批量注册的,人眼看起来都费劲,何况是机器。不少…...

一句话画出动漫效果
链接: AI Comic Factory - a Hugging Face Space by jbilcke-hfDiscover amazing ML apps made by the communityhttps://huggingface.co/spaces/jbilcke-hf/ai-comic-factory 选择类型: Japanese 输入提示词: beauty and school love st…...
【openGauss2.1.0 TPC-C数据导入】
openGauss2.1.0 TPC-C数据导入 一、下载tpch测试数据二、导入测试数据 一、下载tpch测试数据 使用普通用户如omm登录服务器执行如下命令下载测试数据库:git clone https://gitee.com/xzp-blog/tpch-kit.git二、导入测试数据 进入dbgen目录下,生成makef…...
vue+elementui表格导出
htmlToExcel.js import FileSaver from file-saver import XLSX from xlsxconst htmlToExcel {getExcel(dom, title 默认标题) {var excelTitle titlevar wb XLSX.utils.table_to_book(document.querySelector(dom))/* 获取二进制字符串作为输出 */var wbout XLSX.write(w…...
掌握前端利器:JavaScript页面渲染高阶方法解析与实战
引言 前端开发中,页面渲染的速度和质量是衡量一个开发者水平的重要标准。而在众多的前端技术中,JavaScript以其强大的页面渲染能力独占鳌头。本文将深入探讨JavaScript在页面渲染中的应用,并通过实例展示其高阶方法,旨在帮助读者…...

面试题——网络IO模型
一、socket socket是在应用层和传输层中间的抽象层,它把传输层(TCP/UDP)的复杂操作抽象成一些简单的接口,供应用层调用实现进程在网络中的通信。Socket起源于UNIX,在Unix一切皆文件的思想下,进程间通信就被…...

【JUC基础】JUC入门基础(二)
目录 异步回调JMM 理解对 volatile 的理解1、保证可见性2、不保证原子性3、禁止指令重排 对 JMM 的理解 详解单例模式饿汉式懒汉式DCL懒汉式:双重检测锁模式的懒汉式单例静态内部类实现单例通过反射破坏单例,修改后的DCL饿汉式枚举实现单例防止反射破坏 …...

Git Bash 和 Git GUI中文汉化
目录 为什么要中文汉化?Git Bash的汉化Git GUI的汉化 为什么要中文汉化? 看到中文大概能猜出是什么意思,便于使用,特别是Git GUI,中文版的菜单很容易理解是要做什么,如下图: Git Bash的汉化 …...

【Ubuntu】Ubuntu常用软件部署
1.安装jdk1.8 (1).apt方式安装 1).安装 1.在终端中输入以下命令,以更新软件包列表 sudo apt-get update2.在终端中输入以下命令,以安装JDK 1.8 sudo apt-get install openjdk-8-jdk3.将Java 1.8设置为默认版本。在终端中输入以下命令 sudo update-…...

Hadoop HA模式切换
Hadoop HA模式下 主从的切换(操作命令) YARN HA 获取所有RM节点的状态 yarn rmadmin -getAllServiceState获取 rm1 节点的状态 yarn rmadmin -getServiceState rm1手动将 rm1 的状态切换到STANDBY yarn rmadmin -transitionToStandby rm1 ##或者 y…...

自然语言处理(四):全局向量的词嵌入(GloVe)
全局向量的词嵌入(GloVe) 全局向量的词嵌入(Global Vectors for Word Representation),通常简称为GloVe,是一种用于将词语映射到连续向量空间的词嵌入方法。它旨在捕捉词语之间的语义关系和语法关系&#…...

Flink中RPC实现原理简介
前提知识 Akka是一套可扩展、弹性和快速的系统,为此Flink基于Akka实现了一套内部的RPC通信框架;为此先对Akka进行了解 Akka Akka是使用Scala语言编写的库,基于Actor模型提供一个用于构建可扩展、弹性、快速响应的系统;并被应用…...

ELK安装、部署、调试(五)filebeat的安装与配置
1.介绍 logstash 也可以收集日志,但是数据量大时太消耗系统新能。而filebeat是轻量级的,占用系统资源极少。 Filebeat 由两个主要组件组成:harvester 和 prospector。 采集器 harvester 的主要职责是读取单个文件的内容。读取每个文件&…...

Python数据分析案例30——中国高票房电影分析(爬虫获取数据及分析可视化全流程)
案例背景 最近总看到《消失的她》票房多少多少,《孤注一掷》票房又破了多少多少..... 于是我就想自己爬虫一下获取中国高票房的电影数据,然后分析一下。 数据来源于淘票票:影片总票房排行榜 (maoyan.com) 爬它就行。 代码实现 首先爬虫获…...

科技资讯|苹果Vision Pro头显申请游戏手柄专利和商标
苹果集虚拟现实和增强现实于一体的头戴式设备 Vision Pro 推出一个月后,美国专利局公布了两项苹果公司申请的游戏手柄专利,其中一项的专利图如下图所示。据 PatentlyApple 报道,虽然专利本身并不能保证苹果公司会推出游戏手柄,但是…...
Compose学习 - remember、mutableStateOf的使用
一、需求 在显示界面中,数据变动,界面刷新是非常常见的操作,所以使用compose该如何实现呢? 二、remember、mutableStateOf的使用 我们可以借助标题的两个概念 remember、mutableStateOf来完成。这里先不写定义,定义…...

字符串哈希
字符串前缀哈希法 str "ABCABCDEHGJK" 预处理每一个前缀的哈希值,如 : h[0] 0; h[1] "A"的哈希值 h[2] "AB"的哈希值 h[3] "ABC"的哈希值 h[4] "ABCA"的哈希值 问题 : 如何定义一个前缀的哈希值 : 将字符串看…...
零门槛NAS搭建:WinNAS如何让普通电脑秒变私有云?
一、核心优势:专为Windows用户设计的极简NAS WinNAS由深圳耘想存储科技开发,是一款收费低廉但功能全面的Windows NAS工具,主打“无学习成本部署” 。与其他NAS软件相比,其优势在于: 无需硬件改造:将任意W…...

7.4.分块查找
一.分块查找的算法思想: 1.实例: 以上述图片的顺序表为例, 该顺序表的数据元素从整体来看是乱序的,但如果把这些数据元素分成一块一块的小区间, 第一个区间[0,1]索引上的数据元素都是小于等于10的, 第二…...

中南大学无人机智能体的全面评估!BEDI:用于评估无人机上具身智能体的综合性基准测试
作者:Mingning Guo, Mengwei Wu, Jiarun He, Shaoxian Li, Haifeng Li, Chao Tao单位:中南大学地球科学与信息物理学院论文标题:BEDI: A Comprehensive Benchmark for Evaluating Embodied Agents on UAVs论文链接:https://arxiv.…...

1.3 VSCode安装与环境配置
进入网址Visual Studio Code - Code Editing. Redefined下载.deb文件,然后打开终端,进入下载文件夹,键入命令 sudo dpkg -i code_1.100.3-1748872405_amd64.deb 在终端键入命令code即启动vscode 需要安装插件列表 1.Chinese简化 2.ros …...

Java-41 深入浅出 Spring - 声明式事务的支持 事务配置 XML模式 XML+注解模式
点一下关注吧!!!非常感谢!!持续更新!!! 🚀 AI篇持续更新中!(长期更新) 目前2025年06月05日更新到: AI炼丹日志-28 - Aud…...

【配置 YOLOX 用于按目录分类的图片数据集】
现在的图标点选越来越多,如何一步解决,采用 YOLOX 目标检测模式则可以轻松解决 要在 YOLOX 中使用按目录分类的图片数据集(每个目录代表一个类别,目录下是该类别的所有图片),你需要进行以下配置步骤&#x…...

中医有效性探讨
文章目录 西医是如何发展到以生物化学为药理基础的现代医学?传统医学奠基期(远古 - 17 世纪)近代医学转型期(17 世纪 - 19 世纪末)现代医学成熟期(20世纪至今) 中医的源远流长和一脉相承远古至…...
人工智能--安全大模型训练计划:基于Fine-tuning + LLM Agent
安全大模型训练计划:基于Fine-tuning LLM Agent 1. 构建高质量安全数据集 目标:为安全大模型创建高质量、去偏、符合伦理的训练数据集,涵盖安全相关任务(如有害内容检测、隐私保护、道德推理等)。 1.1 数据收集 描…...

CVPR2025重磅突破:AnomalyAny框架实现单样本生成逼真异常数据,破解视觉检测瓶颈!
本文介绍了一种名为AnomalyAny的创新框架,该方法利用Stable Diffusion的强大生成能力,仅需单个正常样本和文本描述,即可生成逼真且多样化的异常样本,有效解决了视觉异常检测中异常样本稀缺的难题,为工业质检、医疗影像…...
WEB3全栈开发——面试专业技能点P4数据库
一、mysql2 原生驱动及其连接机制 概念介绍 mysql2 是 Node.js 环境中广泛使用的 MySQL 客户端库,基于 mysql 库改进而来,具有更好的性能、Promise 支持、流式查询、二进制数据处理能力等。 主要特点: 支持 Promise / async-await…...