当前位置: 首页 > news >正文

韩版传奇 2 源码分析与 Unity 重制(二)客户端启动与交互流程

专题介绍

该专题将会分析 LOMCN 基于韩版传奇 2,使用 .NET 重写的传奇源码(服务端 + 客户端),分析数据交互、状态管理和客户端渲染等技术,此外笔者还会分享将客户端部分移植到 Unity 和服务端用现代编程语言重写的全过程。

概览

在这一篇文章中,我们将从客户端入手,分析从 TCP 连接建立、登录鉴权、角色选择、开始游戏到游戏内交互的全过程。

客户端启动

WinForm 入口 Program.cs

与服务端类似,客户端也是一个 WinForm 应用程序,在 Application 启动后,会先跳转到 AMain 检查是否有热更新,随后再跳转到 CMain 开启客户端主逻辑:

// Program.cs
[STAThread]
private static void Main(string[] args)
{// ...Application.EnableVisualStyles();Application.SetCompatibleTextRenderingDefault(false);if (Settings.P_Patcher) Application.Run(PForm = new Launcher.AMain());else Application.Run(Form = new CMain());// ...
}

监听事件循环

在 CMain 的构造函数中,我们监听了 Application Idle 事件作为事件循环:

// CMain.cs
public CMain()
{InitializeComponent();Application.Idle += Application_Idle;// ...
}

在 Application_Idle 中,我们通过 UpdateTime 更新客户端全局的时间戳,通过 UpdateEnviroment 处理网络数据,通过 RenderEnvironment 处理客户端渲染:

private static void Application_Idle(object sender, EventArgs e)
{try{while (AppStillIdle){UpdateTime();UpdateEnviroment();RenderEnvironment();}}catch (Exception ex){SaveError(ex.ToString());}
}

客户端场景划分

在用户登录之前,UpdateEnviroment 发现连接实例为空不会做任何操作,因此我们先跳过这个函数来看 RenderEnvironment 的处理过程,这里实际上就是基于 Direct 3D 的客户端的渲染循环,请大家注意 MirScene.ActiveScene.Draw 这个调用,传奇通过 Scene 去区分不同的场景,例如登录页面、角色选择页面和游戏页面,每个页面都是一个独立的 Scene:

private static void RenderEnvironment()
{try{if (DXManager.DeviceLost){DXManager.AttemptReset();Thread.Sleep(1);return;}DXManager.Device.Clear(ClearFlags.Target, Color.CornflowerBlue, 0, 0);DXManager.Device.BeginScene();DXManager.Sprite.Begin(SpriteFlags.AlphaBlend);DXManager.SetSurface(DXManager.MainSurface);// Note hereif (MirScene.ActiveScene != null)MirScene.ActiveScene.Draw();DXManager.Sprite.End();DXManager.Device.EndScene();DXManager.Device.Present();}catch (Direct3D9Exception ex){DXManager.DeviceLost = true;}catch (Exception ex){SaveError(ex.ToString());DXManager.AttemptRecovery();}
}

那么当前的 ActiveScene 是在哪里设置的呢?实际上在 MirScene 初始化时它会被指定为 LoginScene:

public abstract class MirScene : MirControl
{public static MirScene ActiveScene = new LoginScene();// ...
}

因此上面的 Draw 方法其实会将登录页面绘制出来,我们这里先跳过 GUI 相关的部分,直接来看一下当用户输入完账号密码后是如何建立连接和发起登录的。

TCP 连接建立

传奇中的每个 Scene 都是继承自 MirControl 的 UI 对象,MirControl 提供了 Shown 回调用于监听 UI 的展示,在 LoginScene 展示时我们会开启 TCP 连接:

public LoginScene()
{// ...Shown += (sender, args) =>{Network.Connect();_connectBox.Show();};
}

Network 是客户端的网络管理类,在 Connect 方法中我们会创建一个 TcpClient 对象并发起连接,服务端的信息通过配置获取:

public static void Connect()
{if (_client != null)Disconnect();ConnectAttempt++;_client = new TcpClient {NoDelay = true};_client.BeginConnect(Settings.IPAddress, Settings.Port, Connection, null);
}

与服务端的处理方式类似,在 BeginConnect 的异步回调中,我们会开启 receiveList 和 sendList 两个队列,然后通过 BeginReceive 接收服务端数据、处理成 Packet 并加入 receiveList 等待处理。在客户端每帧 Process 的过程中,我们会处理 receiveList 更改客户端状态,同时根据用户输入产生数据包加入到 sendList 发送到服务端。

第一个数据包

服务端发送 S.Connected

通过上面的分析我们知道客户端启动的第一步是发起 TCP 连接请求,服务端在对 Client 进行 Accept 时会创建 MirConnection 对象(如果对此没有印象可以参考第一篇文章),在 MirConnection 的构造方法中我们会向客户端发送 Connected 数据包,这便是客户端与服务端交流的第一个数据包啦:

public MirConnection(int sessionID, TcpClient client)
{// ..._receiveList = new ConcurrentQueue<Packet>();_sendList = new ConcurrentQueue<Packet>();_sendList.Enqueue(new S.Connected());_retryList = new Queue<Packet>();Connected = true;BeginReceive();
}

客户端处理 S.Connected

前面我们提到在 TCP 连接建立之前基于 Application Idle 的事件循环对 UpdateEnviroment 的调用会被忽略,而在连接建立之后这里会通过 Network.Process 处理服务端数据包和发送这一帧产生的数据包,数据包会被路由到 ActiveScene 进行处理,因此这里的 ProcessPacket 会调用到 LoginScene:

public static void Process()
{// ...while (_receiveList != null && !_receiveList.IsEmpty){if (!_receiveList.TryDequeue(out Packet p) || p == null) continue;MirScene.ActiveScene.ProcessPacket(p);}if (CMain.Time > TimeOutTime && _sendList != null && _sendList.IsEmpty)_sendList.Enqueue(new C.KeepAlive());if (_sendList == null || _sendList.IsEmpty) return;TimeOutTime = CMain.Time + Settings.TimeOut; // 5000msList<byte> data = new List<byte>();while (!_sendList.IsEmpty){if (!_sendList.TryDequeue(out Packet p)) continue;data.AddRange(p.GetPacketBytes());}CMain.BytesSent += data.Count;BeginSend(data);
}

在 LoginScene 的 ProcessPacket 中包含了对客户端初始化和账户相关的数据处理,由于当前数据包是 S.Connected 自然会进入到 ServerPacketIds.Connected 这个 case,随后客户端通过 SendVersion 发送数据完整性检查请求(这里会对 Executable 进行 hash):

public override void ProcessPacket(Packet p)
{switch (p.Index){case (short)ServerPacketIds.Connected:Network.Connected = true;SendVersion();break;case (short)ServerPacketIds.ClientVersion:ClientVersion((S.ClientVersion) p);break;// ...default:base.ProcessPacket(p);break;}
}

数据完整性检查与 Connected 数据包类似,首先客户端发送 hash 到服务端,服务端校验后将结果返回到客户端,这是一个初级的逆向对抗策略,可通过修改发送的 hash 或忽略返回的错误跳过。

客户端登录过程

在上述检查通过以后,客户端会展示账号密码输入页面,用户输入账号密码后点击登录会调用 Login 方法发起登录请求:

// LoginScene.cs
private void Login()
{OKButton.Enabled = false;Network.Enqueue(new C.Login {AccountID = AccountIDTextBox.Text, Password = PasswordTextBox.Text});
}

作为一款早期的游戏,传奇的密码采用了明文传输(囧),服务端收到 C.Login 数据包后,会尝试从 Account Database 中查询与之匹配的账户,如果校验失败会发送 S.Login 返回登录失败的原因,成功则发送 S.LoginSuccess:

// Envir.cs
public void Login(ClientPackets.Login p, MirConnection c)
{// ...if (!AccountIDReg.IsMatch(p.AccountID)){c.Enqueue(new ServerPackets.Login { Result = 1 });return;}if (!PasswordReg.IsMatch(p.Password)){c.Enqueue(new ServerPackets.Login { Result = 2 });return;}var account = GetAccount(p.AccountID);if (account == null){c.Enqueue(new ServerPackets.Login { Result = 3 });return;}// ...if (string.CompareOrdinal(account.Password, p.Password) != 0){if (account.WrongPasswordCount++ >= 5){account.Banned = true;account.BanReason = "Too many Wrong Login Attempts.";account.ExpiryDate = DateTime.Now.AddMinutes(2);c.Enqueue(new ServerPackets.LoginBanned{Reason = account.BanReason,ExpiryDate = account.ExpiryDate});return;}c.Enqueue(new ServerPackets.Login { Result = 4 });return;}account.WrongPasswordCount = 0;lock (AccountLock){account.Connection?.SendDisconnect(1);account.Connection = c;}c.Account = account;c.Stage = GameStage.Select;account.LastDate = Now;account.LastIP = c.IPAddress;MessageQueue.Enqueue(account.Connection.SessionID + ", " + account.Connection.IPAddress + ", User logged in.");c.Enqueue(new ServerPackets.LoginSuccess { Characters = account.GetSelectInfo() });
}

相应地,在客户端侧也包含了对 Login 和 LoginSuccess 的处理:

// LoginScene.cs
public override void ProcessPacket(Packet p)
{switch (p.Index){// ...case (short)ServerPacketIds.Login:Login((S.Login) p);break;case (short)ServerPacketIds.LoginSuccess:Login((S.LoginSuccess) p);break;default:base.ProcessPacket(p);break;}
}

在登录失败时会调用到 private void Login(S.Login p) 这个重载方法展示登录失败原因(事实上出于安全考虑,登录失败的原因应当尽可能模糊):

// LoginScene.cs
private void Login(S.Login p)
{_login.OKButton.Enabled = true;switch (p.Result){case 0:MirMessageBox.Show("Logging in is currently disabled.");_login.Clear();break;case 1:MirMessageBox.Show("Your AccountID is not acceptable.");_login.AccountIDTextBox.SetFocus();break;case 2:MirMessageBox.Show("Your Password is not acceptable.");_login.PasswordTextBox.SetFocus();break;case 3:MirMessageBox.Show(GameLanguage.NoAccountID);_login.PasswordTextBox.SetFocus();break;case 4:MirMessageBox.Show(GameLanguage.IncorrectPasswordAccountID);_login.PasswordTextBox.Text = string.Empty;_login.PasswordTextBox.SetFocus();break;}
}

在登录成功时会调用到 private void Login(S.LoginSuccess p) 这个重载方法切换到角色选择 Scene 等待用户的下一步操作,为了避免额外的数据交互,服务端在登录成功后会返回角色列表:

// LoginScene.cs
private void Login(S.LoginSuccess p)
{Enabled = false;_login.Dispose();if(_ViewKey != null && !_ViewKey.IsDisposed) _ViewKey.Dispose();SoundManager.PlaySound(SoundList.LoginEffect);_background.Animated = true;_background.AfterAnimation += (o, e) =>{Dispose();ActiveScene = new SelectScene(p.Characters);};
}

开始游戏

服务端同步角色数据

在用户选择完角色点击开始游戏后,客户端会发送包含角色选择信息的 C.StartGame 数据包到服务端:

// SelectScene.cs
public void StartGame()
{// ...Network.Enqueue(new C.StartGame{CharacterIndex = Characters[_selected].Index});
}

服务端在接收到 C.StartGame 后会读从数据库读取角色数据,随后新建一个 PlayerObject 调用 StartGame 方法:

// MirConnection.cs
private void StartGame(C.StartGame p)
{// ...CharacterInfo info = null;for (int i = 0; i < Account.Characters.Count; i++){if (Account.Characters[i].Index != p.CharacterIndex) continue;info = Account.Characters[i];break;}if (info == null){Enqueue(new S.StartGame { Result = 2 });return;}// ...Player = new PlayerObject(info, this);Player.StartGame();
}

在 PlayerObject 的 StartGame 方法中,服务端将角色添加到地图中,随后发送游戏开始和玩家数据到客户端:

// PlayerObject.cs
public void StartGame()
{Map temp = Envir.GetMap(CurrentMapIndex);if (temp != null && temp.Info.NoReconnect){Map temp1 = Envir.GetMapByNameAndInstance(temp.Info.NoReconnectMap);if (temp1 != null){temp = temp1;CurrentLocation = GetRandomPoint(40, 0, temp);}}if (temp == null || !temp.ValidPoint(CurrentLocation)){temp = Envir.GetMap(BindMapIndex);if (temp == null || !temp.ValidPoint(BindLocation)){SetBind();temp = Envir.GetMap(BindMapIndex);if (temp == null || !temp.ValidPoint(BindLocation)){StartGameFailed();return;}}CurrentMapIndex = BindMapIndex;CurrentLocation = BindLocation;}temp.AddObject(this);CurrentMap = temp;Envir.Players.Add(this);StartGameSuccess();//Call Login NPCCallDefaultNPC(DefaultNPCType.Login);//Call Daily NPCif (Info.NewDay){CallDefaultNPC(DefaultNPCType.Daily);}
}

随后在 StartGameSuccess 的调用中向客户端发送游戏开发和角色数据,这里的每个 Get 方法的作用都是将地图和角色数据同步到客户端:

// PlayerObject.cs
private void StartGameSuccess()
{Connection.Stage = GameStage.Game;// ...Enqueue(new S.StartGame { Result = 4, Resolution = Settings.AllowedResolution });ReceiveChat(string.Format(GameLanguage.Welcome, GameLanguage.GameName), ChatType.Hint);// ...Spawned();SetLevelEffects();GetItemInfo();GetMapInfo();GetUserInfo();GetQuestInfo();GetRecipeInfo();GetCompletedQuests();GetMail();GetFriends();GetRelationship();if ((Info.Mentor != 0) && (Info.MentorDate.AddDays(Settings.MentorLength) < DateTime.Now))MentorBreak();elseGetMentor();CheckConquest();GetGameShop();// ...
}private void GetUserInfo()
{string guildname = MyGuild != null ? MyGuild.Name : "";string guildrank = MyGuild != null ? MyGuildRank.Name : "";S.UserInformation packet = new S.UserInformation{ObjectID = ObjectID,RealId = (uint)Info.Index,Name = Name,GuildName = guildname,GuildRank = guildrank,NameColour = GetNameColour(this),Class = Class,Gender = Gender,Level = Level,Location = CurrentLocation,Direction = Direction,Hair = Hair,HP = HP,MP = MP,Experience = Experience,MaxExperience = MaxExperience,LevelEffects = LevelEffects,Inventory = new UserItem[Info.Inventory.Length],Equipment = new UserItem[Info.Equipment.Length],QuestInventory = new UserItem[Info.QuestInventory.Length],Gold = Account.Gold,Credit = Account.Credit,HasExpandedStorage = Account.ExpandedStorageExpiryDate > Envir.Now ? true : false,ExpandedStorageExpiryTime = Account.ExpandedStorageExpiryDate};Info.Inventory.CopyTo(packet.Inventory, 0);Info.Equipment.CopyTo(packet.Equipment, 0);Info.QuestInventory.CopyTo(packet.QuestInventory, 0);//IntelligentCreaturefor (int i = 0; i < Info.IntelligentCreatures.Count; i++)packet.IntelligentCreatures.Add(Info.IntelligentCreatures[i].CreateClientIntelligentCreature());packet.SummonedCreatureType = SummonedCreatureType;packet.CreatureSummoned = CreatureSummoned;Enqueue(packet);
}

客户端开始游戏

客户端目前处于 SelectScene,在收到游戏启动成功的数据包 S.StartGame 后会根据返回数据调整分辨率并切换到 GameScene:

public void StartGame(S.StartGame p)
{StartGameButton.Enabled = true;switch (p.Result){case 0:MirMessageBox.Show("Starting the game is currently disabled.");break;case 1:MirMessageBox.Show("You are not logged in.");break;case 2:MirMessageBox.Show("Your character could not be found.");break;case 3:MirMessageBox.Show("No active map and/or start point found.");break;case 4:if (p.Resolution < Settings.Resolution || Settings.Resolution == 0) Settings.Resolution = p.Resolution;switch (Settings.Resolution){default:case 1024:Settings.Resolution = 1024;CMain.SetResolution(1024, 768);break;case 1280:CMain.SetResolution(1280, 800);break;case 1366:CMain.SetResolution(1366, 768);break;case 1920:CMain.SetResolution(1920, 1080);break;}ActiveScene = new GameScene();Dispose();break;}
}

在 GameScene 中客户端会处理来自服务端的角色信息、地图数据以及 NPC 和其他玩家数据等,例如在收到游戏开始时服务端发送的 S.UserInformation 后会创建当前玩家的角色:

// GameScene.cs
public override void ProcessPacket(Packet p)
{switch (p.Index){// ...case (short)ServerPacketIds.UserInformation:UserInformation((S.UserInformation)p);break;// ...}
}private void UserInformation(S.UserInformation p)
{User = new UserObject(p.ObjectID);User.Load(p);MainDialog.PModeLabel.Visible = User.Class == MirClass.Wizard || User.Class == MirClass.Taoist;Gold = p.Gold;Credit = p.Credit;InventoryDialog.RefreshInventory();foreach (SkillBarDialog Bar in SkillBarDialogs)Bar.Update();
}

下一步

到这里整个客户端的启动流程就分析完了,接下来的逻辑主要集中在服务端向客户端同步状态和客户端发送角色行为,在接下来的文章中我们将深入分析这些交互的处理过程。

相关文章:

韩版传奇 2 源码分析与 Unity 重制(二)客户端启动与交互流程

专题介绍 该专题将会分析 LOMCN 基于韩版传奇 2&#xff0c;使用 .NET 重写的传奇源码&#xff08;服务端 客户端&#xff09;&#xff0c;分析数据交互、状态管理和客户端渲染等技术&#xff0c;此外笔者还会分享将客户端部分移植到 Unity 和服务端用现代编程语言重写的全过…...

JVM面试——运行时数据区

一&#xff1a;JVM的运行时内存区域是怎样的? 根据Java虚拟机规范的定义&#xff0c;JVM的运行时内存区域主要由程序计数器、虚拟机栈、本地方法 栈、Java堆、方法区和以及运行时常量池组成。其中堆、方法区以及运行时常量池是线程之间共享的区域&#xff0c;而栈&#xff08…...

ssh工具 向指定的ssh服务器配置公钥

此文分享一个python脚本,用于向指定的ssh服务器配置公钥,以达到免密登录ssh服务器的目的。 效果演示 🔥完整演示效果 👇第一步,显然,我们需要选择功能 👇第二步,确认 or 选择ssh服务器 👇第三步,输入ssh登录密码,以完成公钥配置 👇验证,我们通过ssh登录…...

uni-app pages.json之globalStyle全局页面样式配置

锋哥原创的uni-app视频教程&#xff1a; 2023版uniapp从入门到上天视频教程(Java后端无废话版)&#xff0c;火爆更新中..._哔哩哔哩_bilibili2023版uniapp从入门到上天视频教程(Java后端无废话版)&#xff0c;火爆更新中...共计23条视频&#xff0c;包括&#xff1a;第1讲 uni…...

Blazor 混合开发_MAUI+Vue_WPF+Vue

Blazor 混合开发_MAUIVue_WPFVue 背景混合开发的核心为什么必须使用 wwwroot 文件夹放置 Web 项目文件 创建 MAUI 项目创建 wwwroot 文件夹服务注册创建 _import.razor添加 Main.razor 组件修改 MainPage.xaml 文件 创建 WPF 项目创建 wwwroot 文件夹服务注册创建 _import.razo…...

udp异步方式接收消息

C#实现 //定义结构体 public struct UdpState { public UdpClient u; public IPEndPoint e; } private UdpClient _client; //_client的初始化请参考其他资料 IPEndPoint remoteEP null; //TODO //public static bool mess…...

【RocketMQ笔记01】安装RocketMQ消息队列运行环境

这篇文章&#xff0c;主要介绍如何安装RocketMQ消息队列运行环境。 目录 一、RocketMQ消息队列 1.1、下载RocketMQ 1.2、解压安装包 1.3、配置RocketMQ环境变量 1.4、修改启动脚本 1.5、启动RocketMQ &#xff08;1&#xff09;启动NameServer &#xff08;2&#xff0…...

使用 Privoxy 实现对多域名的定向转发

需求与思路 内网一台主机想要访问公网的两个不同站点, 想要实现访问两个站点时表现出不同的公网 IP 地址. 即在公网的站点服务器端看到的客户端 IP 是不同的. 思路是搭建两台具有不同公网 IP 的服务器, 分别安装配置 Privoxy 后进行串联, 并将其中一台作为主服务器暴露给内网…...

《PySpark大数据分析实战》-19.NumPy介绍ndarray介绍

&#x1f4cb; 博主简介 &#x1f496; 作者简介&#xff1a;大家好&#xff0c;我是wux_labs。&#x1f61c; 热衷于各种主流技术&#xff0c;热爱数据科学、机器学习、云计算、人工智能。 通过了TiDB数据库专员&#xff08;PCTA&#xff09;、TiDB数据库专家&#xff08;PCTP…...

图解LRU缓存

图解LRU缓存 OJ链接 介绍 LRU 缓存机制可以通过哈希表辅以双向链表实现&#xff0c;我们用一个哈希表和一个双向链表维护所有在缓存中的键值对。 双向链表按照被使用的顺序存储了这些键值对&#xff0c;靠近尾部的键值对是最近使用的&#xff0c;而靠近头部的键值对是最久未…...

FFmpeg常见命令行

1、ffmpeg命令行 视频生成图片 ffmpeg -i test.mp4 -r 25 -f image2 data/image%3d.jpg这个命令行使用FFmpeg工具将视频文件&#xff08;test.mp4&#xff09;转换为一系列图像文件。 让我们逐个解释每个参数的含义&#xff1a; -i test.mp4: 指定输入文件为test.mp4。-i是F…...

智能优化算法应用:基于斑马算法3D无线传感器网络(WSN)覆盖优化 - 附代码

智能优化算法应用&#xff1a;基于斑马算法3D无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用&#xff1a;基于斑马算法3D无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.斑马算法4.实验参数设定5.算法结果6.参考文献7.MA…...

《C++避坑神器·二十五》简单搞懂json文件的读写之遍历json文件读写

json.hpp库放在文章末尾 1、遍历json文件读写 &#xff08;1&#xff09;插入新键值对到json之情形1 原来json文件如下所示&#xff1a; {"Connection": {"IpAddress": "192.168.20.1","Rock": 0,"Solt": 1}, "Data…...

使用 fixture 机制重构 appium_helloworld

一、前置说明 在 pytest 基础讲解 章节,介绍了 pytest 的特性和基本用法,现在我们可以使用 pytest 的一些机制,来重构 appium_helloworld 。 appium_helloworld 链接: 编写第一个APP自动化脚本 appium_helloworld ,将脚本跑起来 代码目录结构: pytest.ini 设置: [pyt…...

基于python的excel检查和读写软件

软件版本&#xff1a;python3.6 窗口和界面gui代码&#xff1a; class mygui:def _init_(self):passdef run(self):root Tkinter.Tk()root.title(ExcelRun)max_w, max_h root.maxsize()root.geometry(f500x500{int((max_w - 500) / 2)}{int((max_h - 300) / 2)}) # 居中显示…...

Podman配置mongodb

文章目录 查询镜像拉取镜像查看镜像运行容器创建root用户 查询镜像 podman search mongo拉取镜像 podman pull docker.io/library/mongo查看镜像 podman images运行容器 podman run -d -p 27017:27017 --namemongodb-test docker.io/library/mongo创建root用户 podman exe…...

java实现矩阵谱峰搜索算法

矩阵谱峰搜索算法&#xff0c;也称为矩阵谱峰查找算法&#xff0c;是一种用于搜索二维矩阵中谱峰的方法。谱峰是指在矩阵中的一个元素&#xff0c;它比其上下左右四个相邻元素都大或相等。 该算法的基本思想是从矩阵的中间列开始&#xff0c;找到该列中的最大元素&#xff0c;…...

Jenkins的特殊操作定时自动执行任务以及测试报告调优

java -Dhudson.model.DirectoryBrowserSupport.CSP -jar Jenkins.war 测试报告 不美丽 执行上面的代码 重启jenkins 就好了...

【Grafana】Grafana匿名访问以及与LDAP连接

上一篇文章利用Docker快速部署了Grafana用来展示Zabbix得监控数据&#xff0c;但还需要给用户去创建账号允许他们登录后才能看展示得数据&#xff0c;那有什么办法让非管理员更方便得去访问Grafana呢&#xff1f;下面介绍两个比较方便实现的&#xff1a; 在开始设置前&#xff…...

elasticsearch-py 8.x的一些优势

​ 早在 2022 年 2 月,当 Elasticsearch 8.0 发布时,Python 客户端也发布了 8.0 版本。它是对 7.x 客户端的部分重写,并带有许多不错的功能(如下所述),但也带有弃用警告和重大更改。今天,客户端的 7.17 版本仍然相对流行,每月下载量超过 100 万次,占 8.x 下载量的 ~50…...

RK3588平台开发系列讲解(AI 篇)RKNN 数据结构详解

文章目录 一、rknn_sdk_version二、rknn_input_output_num三、rknn_tensor_attr四、rknn_perf_detail五、rknn_perf_run六、rknn_mem_size七、rknn_tensor_mem八、rknn_input九、rknn_output沉淀、分享、成长,让自己和他人都能有所收获!😄 📢本篇章主要讲解 RKNN 相关的数…...

2023版本QT学习记录 -6- UDP通信之UDP接收端

———————UDP接收端——————— &#x1f384;动图演示 &#x1f384;发送端通信步骤思维导图 &#x1f384;添加组件 QT core gui network&#x1f384;添加头文件 #include "qudpsocket.h"&#x1f384;创建接收对象 QUdpSocket *recvsocket;&…...

C预处理 | pragma详解

欢迎关注博主 Mindtechnist 或加入【Linux C/C/Python社区】一起学习和分享Linux、C、C、Python、Matlab&#xff0c;机器人运动控制、多机器人协作&#xff0c;智能优化算法&#xff0c;滤波估计、多传感器信息融合&#xff0c;机器学习&#xff0c;人工智能等相关领域的知识和…...

轻松搭建知识付费小程序:让知识传播更便捷

明理信息科技saas知识付费平台 在当今数字化时代&#xff0c;知识付费已经成为一种趋势&#xff0c;越来越多的人愿意为有价值的知识付费。然而&#xff0c;公共知识付费平台虽然内容丰富&#xff0c;但难以满足个人或企业个性化的需求和品牌打造。同时&#xff0c;开发和维护…...

沉浸式go-cache源码阅读!

大家好&#xff0c;我是豆小匠。 这期来阅读go-cache的源码&#xff0c;了解本地缓存的实现方式&#xff0c;同时掌握一些阅读源码的技巧~ 1. 源码获取 git clone https://github.com/patrickmn/go-cache.git用Goland打开可以看到真正实现功能的也就两个go文件&#xff0c;ca…...

伪协议和反序列化 [ZJCTF 2019]NiZhuanSiWei

打开题目 代码审计 第一层绕过 if(isset($text)&&(file_get_contents($text,r)"welcome to the zjctf")){ echo "<br><h1>".file_get_contents($text,r)."</h1></br>"; 要求我们get传参的text内容必须为w…...

性能优化之资源优化

性能优化之资源优化 资源优化性能关键检测流程。浅析一下基于Unity3D 美术规则约束一、模型层面二、贴图层面三、动画层面四、声音层面&#xff1a;&#xff08;音频通用设置&#xff09;五、UI层面&#xff1a; 题外点&#xff1a;诚然在优化中&#xff0c;美术占比是很重要的…...

ChatGPT免费 | 8个免费使用GPT-4的方法

这篇文章为寻找免费使用GPT-4技术的读者提供了一份实用的指南。 每个推荐的平台都包括了简要的描述和链接&#xff0c;方便读者直接访问。 以下是根据你提供的内容&#xff0c;稍作整理的文章结构&#xff1a; 1. HuggingFace 描述: 提供GPT-4等多种语言模型的平台。 如何使用:…...

解决Qt“报无法定位程序输入点xxx于动态连接库“问题

今天&#xff0c;在使用QtVS2019编译工程时&#xff0c;弹出"无法定位程序输入点xxx于动态链接库"问题&#xff0c;如图(1)所示&#xff1a; 图(1) 报"无法定位程序输入点xxx于动态链接库"问题 出现这种问题的原因有很多&#xff1a; (1) 工程Release/Deb…...

wpf-MVVM绑定时可能出现的内存泄漏问题

文章速览 引言错误示范示例1示例2 坚持记录实属不易&#xff0c;希望友善多金的码友能够随手点一个赞。 共同创建氛围更加良好的开发者社区&#xff01; 谢谢~ 引言 正确结构&#xff1a; Model <——> ViewModel <——> View 但很多时候&#xff0c;很容易出现…...

【飞凌 OK113i-C 全志T113-i开发板】一些有用的常用的命令测试

一些有用的常用的命令测试 一、系统信息查询 可以查询板子的内核信息、CPU处理器信息、环境变量等 二、CPU频率 从上面的系统信息查询到&#xff0c;这是一颗具有两个ARMv7结构A7内核的处理器&#xff0c;主频最高1.2GHz 可以通过命令查看当前支持的频率以及目前所使用主频 …...

基于iOS平台的车牌识别表情识别项目

基于iOS平台的车牌识别&&表情识别项目 简介 ​ 该项目客户端搭载于iOS平台&#xff0c;服务端搭载于阿里云服务器&#xff0c;主要功能是通过拍照或选取相册图片来进行车牌的识别以及人脸表情识别。本文便是对项目整体流程设计思路和具体实现做一个详细介绍。 整体实…...

Matlab仿真2ASK/OOK、2FSK、2PSK、QPSK、4QAM在加性高斯白噪声信道中的误码率与归一化信噪比的关系

本文为学习所用&#xff0c;严禁转载。 本文参考链接 https://zhuanlan.zhihu.com/p/667382398 QPSK代码及高斯白噪声如何产生 https://ww2.mathworks.cn/help/signal/ref/butter.html 滤波器 https://www.python100.com/html/4LEF79KQK398.html 低通滤波器 本实验使用matlab仿…...

九:爬虫-MongoDB基础

MongoDB介绍 MongoDB是一个介于关系数据库和非关系数据库之间的产品&#xff0c;是非关系数据库当中功能最丰富&#xff0c;最像关系数据库的。它支持的数据结构非常松散&#xff0c;因此可以存储比较复杂的数据类型。Mongo最大的特点是它支持的查询语言非常强大&#xff0c;其…...

机器学习之实验过程01

import pandas as pd import numpy as np import matplotlib.pyplot as plt data_path /home/py/Work/labs/data/SD.csv # 请确保您的数据文件路径是正确的 df pd.read_csv(data_path) df.head() # 创建散点图 # 创建散点图 plt.figure(figsize(10, 6)) plt.scatter…...

【【迭代16次的CORDIC算法-verilog实现】】

迭代16次的CORDIC算法-verilog实现 -32位迭代16次verilog代码实现 CORDIC.v module cordic32#(parameter DATA_WIDTH 8d32 , // we set data widthparameter PIPELINE 5d16 // Optimize waveform)(input …...

IntelliJ IDEA 2023.3 安装教程

引言 IntelliJ IDEA&#xff0c;通常简称为 IDEA&#xff0c;是由 JetBrains 开发的一款强大的集成开发环境&#xff0c;专为提升开发者的生产力而设计。它支持多种编程语言&#xff0c;包括 Java、Kotlin、Scala 和其他 JVM 语言&#xff0c;同时也为前端开发和移动应用开发提…...

Go 错误处理

Go 错误处理 Go 语言通过内置的错误接口提供了非常简单的错误处理机制。 error类型是一个接口类型&#xff0c;这是它的定义&#xff1a; type error interface {Error() string }我们可以在编码中通过实现 error 接口类型来生成错误信息。 函数通常在最后的返回值中返回错误…...

HarmonyOS构建第一个ArkTS应用(Stage模型)

构建第一个ArkTS应用&#xff08;Stage模型&#xff09; 创建ArkTS工程 若首次打开DevEco Studio&#xff0c;请点击Create Project创建工程。如果已经打开了一个工程&#xff0c;请在菜单栏选择File > New > Create Project来创建一个新工程。 选择Application应用开发…...

故障排查利器-错误日志详解

目录 什么是错误日志 错误日志的作用 错误日志的内容 错误日志的格式 错误日志的生成方式 错误日志的解析和处理 错误日志的最佳实践 小结 错误日志是软件开发和运维中非常重要的一部分&#xff0c;记录了应用程序运行过程中发生的错误和异常信息&#xff0c;如错误类型…...

微信小程序(uniapp)api讲解

Uniapp是一个基于Vue.js的跨平台开发框架&#xff0c;可以同时开发微信小程序、H5、App等多个平台的应用。下面是Uniapp常用的API讲解&#xff1a; Vue.js的API Uniapp采用了Vue.js框架&#xff0c;因此可以直接使用Vue.js的API。例如&#xff1a;v-show、v-if、v-for、comput…...

overtureDNS使用介绍

Overture是一个定制的DNS中继服务器。 在此下在二进制版本 https://github.com/shawn1m/overture/releases默认配置文件./config.yml bindAddress: :53 debugHTTPAddress: 127.0.0.1:5555 dohEnabled: false primaryDNS:- name: DNSPodaddress: 119.29.29.29:53protocol: udp…...

平衡二叉树的构建(递归

目录 1.概念&#xff1a;2.特点&#xff1a;3.构建方法&#xff1a;4.代码&#xff1a;小结&#xff1a; 1.概念&#xff1a; 平衡二叉树&#xff08;Balanced Binary Tree&#xff09;&#xff0c;也称为AVL树&#xff0c;是一种二叉树&#xff0c;它满足每个节点的左子树和右…...

flutter开发实战-设置bottomNavigationBar中间按钮悬浮效果

flutter开发实战-设置bottomNavigationBar中间按钮悬浮的效果 在使用tabbar时候&#xff0c;可以使用bottomNavigationBar来设置中间凸起的按钮&#xff0c;如下 一、效果图 中间按钮凸起的效果图如下 二、实现代码 我们使用BottomAppBar 一个容器&#xff0c;通常与[Sscaf…...

不同参数规模大语言模型在不同微调方法下所需要的显存总结

原文来自DataLearnerAI官方网站&#xff1a; 不同参数规模大语言模型在不同微调方法下所需要的显存总结 | 数据学习者官方网站(Datalearner)https://www.datalearner.com/blog/1051703254378255 大模型的微调是当前很多人都在做的事情。微调可以让大语言模型适应特定领域的任…...

Crow:Middlewares 庖丁解牛6 middleware_call_helper

Crow:http请求到Rule绑定的handler_的调用链-CSDN博客 介绍了handler_的调用顺序,其中的一个调用过程是Connection::->handle void handle() {...ctx_ = detail::context<Middlewares...>();req_.middleware_context = static_cast<void*>(&ctx_);req_.m…...

MyBatis:Generator

MyBatis Generator附批量操作分页查询存储过程 Generator 介绍网址&#xff1a;Introduction to MyBatis Generator Generator &#xff0c;一个用于 MyBatis 的代码生成工具&#xff0c;可以根据数据库表结构自动生成对应的实体类、DAO 接口和 SQL 映射文件&#xff0c;提高…...

rabbitmq的事务实现、消费者的事务实现

RabbitMQ提供了事务机制&#xff0c;可以确保消息在发送和确认过程中的一致性。使用事务机制可以将一系列的消息操作&#xff08;发送、确认、回滚&#xff09;作为一个原子操作&#xff0c;要么全部执行成功&#xff0c;要么全部回滚。 下面是使用RabbitMQ事务的一般步骤&…...

龙芯杯个人赛串口——做一个 UART串口——RS-232

文章目录 Async transmitterAsync receiver1. RS-232 串行接口的工作原理DB-9 connectorAsynchronous communicationHow fast can we send data? 2.波特率时钟生成器Parameterized FPGA baud generator 3.RS-232 transmitter数据序列化完整代码&#xff1a; 4.RS-232 receiver…...

验证码服务使用指南

验证码服务使用指南 1 部署验证码服务 1.1 基础环境 Java 1.8 Maven3.3.9 1.2 安装Redis 参考“Redis安装指南” 1.3 部署验证码服务 1.3.1 下载源码 使用git从远程下载验证码服务代码(开源)。 1.3.2 使用idea打开项目 使用idea打开上一步下载的sailing目录&#xf…...