unity3d:GameFramework+xLua+Protobuf+lua-protobuf,与服务器交互收发协议
概述
1.cs收发协议,通过protobuf序列化
2.lua收发协议,通过lua-protobuf序列化
一条协议字节流组成

C#协议基类
CSPacketBase,SCPacketBaseC#用协议基类
proto生成的CS类,基于这两个基类。分别为CSPacketBase是客户端发送至服务器,SCPacketBase是服务器发送至客户端
Q:为什么要区分这2个
A:反射注册所有SCPacketBase类,为C#接收协议反序列化候选
一个类示例
[global::System.Serializable, global::ProtoBuf.ProtoContract(Name=@"CSLogin")]public partial class CSLogin : CSPacketBase{public CSLogin() {}private string _account = "";[global::ProtoBuf.ProtoMember(1, IsRequired = false, Name=@"account", DataFormat = global::ProtoBuf.DataFormat.Default)][global::System.ComponentModel.DefaultValue("")]public string account{get { return _account; }set { _account = value; }}private string _password = "";[global::ProtoBuf.ProtoMember(2, IsRequired = false, Name=@"password", DataFormat = global::ProtoBuf.DataFormat.Default)][global::System.ComponentModel.DefaultValue("")]public string password{get { return _password; }set { _password = value; }} //网络协议Idpublic override int Id { get {return (int)Network.NetMsgID.CSLogin;} } //回到引用池,变量设置初始化。如果是引用型成员变量也要回到引用池public override void Clear(){_account = "" ;_password = "" ;}}
SCPacketLua
C#中接收包,传递给Lua处理
其中m_id为协议id,m_len为字节数组长度
public class SCPacketLua : PacketBase
{public int m_id;public int m_len;public PacketBuffer m_bytes;
在网络事件使用完毕GameFramework.EventPool.HandleEvent 中
ReferencePool.Release(e);
触发回收
public override void Clear(){ReferencePool.Release(m_bytes);m_bytes = null; //要去引用,不然引用池那释放不了
CSPacketLua
C#中用于接收从lua传递过来的字节流,发送给服务器
public class CSPacketBaseLua : PacketBase{public PacketBuffer m_bytes; //发送字节流public int m_id = 0; //协议idpublic ushort Len //字节流长度
C#中发送协议
CSLogin csLogin = ReferencePool.Acquire<CSLogin>();
csLogin.account = "123";
csLogin.password = "456";
GameEntry.Network.Send(csLogin);
主线程遍历发送队列
每有一个发送,把packet放入到发送队列中,unity主线程中遍历发送队列,把当前帧的全部待发送包,序列化到一个流中
GameFramework.Network.NetworkManager.NetworkChannelBase.ProcessSend
if (m_SendState.Stream.Length > 0 || m_SendPacketPool.Count <= 0)
{//发送流中还有未发送完的或者没有待发送的包return false;
}
//所有在发送队列中,都序列化到流中
while (m_SendPacketPool.Count > 0)
{Packet packet = null;lock (m_SendPacketPool){//每次从发送队列中取一个packet = m_SendPacketPool.Dequeue();}bool serializeResult = false;try{serializeResult = m_NetworkChannelHelper.Serialize(packet, m_SendState.Stream);}
}
//流的操作,写完,要回到起点
m_SendState.Stream.Position = 0L;
序列化消息包
StarForce.NetworkChannelHelper.Serialize
/// <summary>
/// 序列化消息包。
/// </summary>
/// <typeparam name="T">消息包类型。</typeparam>
/// <param name="packet">要序列化的消息包。</param>
/// <param name="destination">要序列化的目标流。</param>
/// <returns>是否序列化成功。</returns>
public bool Serialize<T>(T packet, Stream destination) where T : Packet
{//todo:频繁is as 会有性能消耗if (packet is CSPacketBase){PacketBase packetImpl = packet as CSPacketBase;//先写入bodym_CachedStream.SetLength(1024*8); // 此行防止 Array.Copy 的数据无法写入m_CachedStream.Position = PacketHeaderLength;Serializer.Serialize(m_CachedStream, packet);//获得body长度,再写入消息头,id,body长度ushort bodyLen = (ushort)((int)m_CachedStream.Position - PacketHeaderLength);arrID = BitConverter.GetBytes(packetImpl.Id);arrBodyLen = BitConverter.GetBytes(bodyLen);m_CachedStream.Position = 0;m_CachedStream.Write(arrID, 0, 4);m_CachedStream.Write(arrBodyLen, 0, 2);m_CachedStream.SetLength(PacketHeaderLength + bodyLen);byte[] arrBytes = m_CachedStream.ToArray();Log.Info("序列化字节流:{0}", BitConverter.ToString(arrBytes));m_CachedStream.WriteTo(destination);ReferencePool.Release(packet);return true;}//else if (packetImpl.PacketType == PacketType.ClientToServerLua)else if (packet is CSPacketLua){m_CachedStream.SetLength(1024 * 8); // 此行防止 Array.Copy 的数据无法写入CSPacketLua packetLua = packet as CSPacketLua; arrID = BitConverter.GetBytes(packetLua.Id);arrBodyLen = BitConverter.GetBytes(packetLua.Len);m_CachedStream.Position = 0; //每次开始写流,流位置要设置为0,代表起始位置。每次写byte,pos会自动增加m_CachedStream.Write(arrID, 0, 4);m_CachedStream.Write(arrBodyLen, 0, 2);m_CachedStream.Write(packetLua.m_bytes.Buffer, 0, packetLua.Len);m_CachedStream.SetLength(PacketHeaderLength + packetLua.Len);//写完流,要设置下流的真实长度。因为流是复用的,不然不会截断byte[] arrBytes = m_CachedStream.ToArray();Log.Info("序列化字节流Lua:{0}", BitConverter.ToString(arrBytes));m_CachedStream.WriteTo(destination);//缓存流写入到发送流中ReferencePool.Release(packet);return true;}Log.Warning("Send packet invalid.");return false;
}
对于CSPacketBase类型
1.m_CachedStream是每个packet序列化的流,每次使用前需要设置position
2.先设置m_CachedStream.Position = PacketHeaderLength; 先跳过id,bodyLen位置,先写入body
3.protobuf序列化Serializer.Serialize(m_CachedStream, packet);后得到ushort bodyLen = (ushort)((int)m_CachedStream.Position - PacketHeaderLength);即为bodyLen长度
4.再设置m_CachedStream.Position = 0;,写入id字节流,bodyLen字节流
5.m_CachedStream.SetLength(PacketHeaderLength + bodyLen);写完后要设置长度截断,因为m_CachedStream是复用的,可能上次使用后面还有字节数据
6. m_CachedStream.WriteTo(destination);即为发送流,每次会添加到发送流的末尾
对于CSPacketLua类型
1.由于byte是在lua中序列化好的传递到C#的,只需要按照顺序写入到m_CachedStream中,其他流程与CSPacketBase一致
发送流
GameFramework.Network.NetworkManager.TcpWithSyncReceiveNetworkChannel.SendAsync
private void SendAsync()
{try{m_Socket.BeginSend(m_SendState.Stream.GetBuffer(), (int)m_SendState.Stream.Position, (int)(m_SendState.Stream.Length - m_SendState.Stream.Position), SocketFlags.None, m_SendCallback, m_Socket);}
每次从流中取position到length部分。每次发送一个完整流,从0开始。进入到发送回调
GameFramework.Network.NetworkManager.TcpWithSyncReceiveNetworkChannel.SendCallback
private void SendCallback(IAsyncResult ar)
{Socket socket = (Socket)ar.AsyncState;int bytesSent = 0;try{bytesSent = socket.EndSend(ar);}m_SendState.Stream.Position += bytesSent;if (m_SendState.Stream.Position < m_SendState.Stream.Length){SendAsync();return;}
发送了一段,设置流位置
如果位置<Length,接着调用发送,直到把流全部发送完毕
C#中接收协议
初始化时反射注册协议id对应type,协议id对应处理Handle
StarForce.NetworkChannelHelper.Initialize
// 反射注册包和包处理函数。
Type packetBaseType = typeof(SCPacketBase);
Type packetHandlerBaseType = typeof(PacketHandlerBase);
Assembly assembly = Assembly.GetExecutingAssembly();
Type[] types = assembly.GetTypes();
for (int i = 0; i < types.Length; i++)
{if (!types[i].IsClass || types[i].IsAbstract){continue;}if (types[i].BaseType == packetBaseType){//确定msgID反序列化的类结构PacketBase packetBase = (PacketBase)Activator.CreateInstance(types[i]);Type packetType = GetServerToClientPacketType(packetBase.Id);//防止监听的协议id重复if (packetType != null){Log.Warning("Already exist packet type '{0}', check '{1}' or '{2}'?.", packetBase.Id.ToString(), packetType.Name, packetBase.GetType().Name);continue;}m_ServerToClientPacketTypes.Add(packetBase.Id, types[i]);}else if (types[i].BaseType == packetHandlerBaseType){//网络事件handle处理IPacketHandler packetHandler = (IPacketHandler)Activator.CreateInstance(types[i]);m_NetworkChannel.RegisterHandler(packetHandler);}
}
异步接收流
连接成功,开始异步接收流
GameFramework.Network.NetworkManager.TcpNetworkChannel.ReceiveAsync
//每次获取到完整头/body。流会pos = 0,len为头长,或者body长
//每次拆包都是读取pos累积,剩余要读的为len-pos
m_Socket.BeginReceive(m_ReceiveState.Stream.GetBuffer(), (int)m_ReceiveState.Stream.Position, (int)(m_ReceiveState.Stream.Length - m_ReceiveState.Stream.Position), SocketFlags.None, m_ReceiveCallback, m_Socket);
有服务器下发协议,进入到接收回调中
GameFramework.Network.NetworkManager.TcpNetworkChannel.ReceiveCallback
private void ReceiveCallback(IAsyncResult ar)
{Socket socket = (Socket)ar.AsyncState;int bytesReceived = 0;try{bytesReceived = socket.EndReceive(ar);}//每次读取pos会累加m_ReceiveState.Stream.Position += bytesReceived;if (m_ReceiveState.Stream.Position < m_ReceiveState.Stream.Length){//未读满上次设定长度,接着读。上次设定长度为HeadLen,BodyLenReceiveAsync();return;}//开始解析,流要置为0位置开始解析m_ReceiveState.Stream.Position = 0L;bool processSuccess = false;if (m_ReceiveState.PacketHeader != null){//上次获取到头,这次反序列处理bodyprocessSuccess = ProcessPacket();m_ReceivedPacketCount++;}else{//未获取到头,反序列化头processSuccess = ProcessPacketHeader();}if (processSuccess){//处理成功,接着接收ReceiveAsync();return;}
}
1.初始化时,设置第一次流接收Length为6(协议id int+ bodyLen ushort)。即待接收包头
2.如果接收满了Length,进入到处理包头,解析出协议id,bodyLen
3.设置下一次接收为Length为bodyLen。即待接收包体
4.如果接收满Length,此时进入到处理包体,解析出body对应的对象。设置下次接收为包头Length6,循环到第一步
注意事项
如果有拆包黏包,在接收回调中处理,并且接满一个模式,再解析
m_ReceiveState.Stream.Position += bytesReceived;if (m_ReceiveState.Stream.Position < m_ReceiveState.Stream.Length){//未读满上次设定长度,接着读。上次设定长度为HeadLen,BodyLenReceiveAsync();return;}
每次开始解析前,需要流postion = 0开始,因为随着接收,position到了末尾,无法解析
//开始解析,流要置为0位置开始解析m_ReceiveState.Stream.Position = 0L;
解析包头
StarForce.NetworkChannelHelper.DeserializePacketHeader
source.Position = 0;
SCPacketHeader scHead = ReferencePool.Acquire<SCPacketHeader>();
source.Read(arrID, 0, 4);
scHead.Id = BitConverter.ToInt32(arrID,0);
source.Read(arrBodyLen, 0, 2);
scHead.PacketLength = BitConverter.ToUInt16(arrBodyLen, 0);
得到协议id,bodyLen
解析包体
StarForce.NetworkChannelHelper.DeserializePacket
Packet packet = null;
if (scPacketHeader.IsValid)
{Type packetType = GetServerToClientPacketType(scPacketHeader.Id);if (packetType != null){//source 为接收流,每次接收一整条消息前,设置了Lenpacket = (Packet)ReferencePool.Acquire(packetType);packet = (Packet)RuntimeTypeModel.Default.Deserialize(source, packet, packetType);}else{//如果id找不到字节流传递给Lua中反序列化为tableSCPacketLua luaPacket = ReferencePool.Acquire<SCPacketLua>();//引用池中使用,后续会在使用事件通知后,回到引用池luaPacket.m_id = scPacketHeader.Id;luaPacket.m_len = scPacketHeader.PacketLength;luaPacket.m_bytes = PacketBuffer.GetBuffer(scPacketHeader.PacketLength);source.Read(luaPacket.m_bytes.Buffer, 0, scPacketHeader.PacketLength);packet = luaPacket;
1.根据包体id(协议id),找到初始化反射注册的协议id,type
2.如果有,说明是C#用协议,protobuf反序列化为对象,加入到事件队列中,等待分发,这样做事为了从其他线程中转回主线程处理
3.如果不存在type,说明是Lua用协议,把字节流保存到SCPacketLua,传递到Lua处理字节流转为Lua中table
网络消息分发
GameFramework.EventPool.HandleEvent
if (m_EventHandlers.TryGetValue(e.Id, out range))
{LinkedListNode<EventHandler<T>> current = range.First;while (current != null && current != range.Terminal){m_CachedNodes[e] = current.Next != range.Terminal ? current.Next : null;if (m_NoSenderDict.ContainsKey(e.Id) && m_NoSenderDict[e.Id] == current.Value){current.Value(null, e);}else{//这里会传递网络handlecurrent.Value(sender, e);}current = m_CachedNodes[e];}m_CachedNodes.Remove(e);
}
else if (m_DefaultHandler != null)
{m_DefaultHandler(sender, e);
}
else if ((m_EventPoolMode & EventPoolMode.AllowNoHandler) == 0)
{noHandlerException = true;
}ReferencePool.Release(e);
1.C#中初始化时反射注册协议id对应handle,例如handle中内容
public class SCLoginHandler : PacketHandlerBase
{public override int Id{get{return (int)Network.NetMsgID.SCLogin;}}public override void Handle(object sender, Packet packet){SCLogin packetImpl = (SCLogin)packet;Log.Info("SCLoginHandler name:{0}-passeword{1}", packetImpl.account, packetImpl.password);}
}
会在current.Value(sender, e);中调用到 Handle,这里可以把协议转换好的对象,进一步处理
2.未找到协议id对应handle,执行m_DefaultHandler(sender, e);,这里可以在初始化设置委托在lua中执行,把SCPacketLua传递到Lua进一步处理
Lua中发送协议
lua中
function TestSendPlayerInfo()--每次协议都是全手写table,未看到可生成协议格式.lua文件,这样不可复用,每次都需要看proto描述,写全部字段。--需要在业务module中手写一遍全部协议send函数,确定发送的参数,组装成一个table再发送。这样相当于手动写了一遍local data = {name = "789",level = 123}LuaEntry.NetworkModule:Send(NetMsgID.CSPlayerInfo,data)
endfunction NetworkModule:Send(msgID,data)local sProto = MsgID2Proto[msgID]if sProto == nil thenLog.Info("消息ID:{0}找不到需要序列化的proto对象",msgID)returnendlocal bytes = assert(pb.encode(sProto, data))--返回值虽然为string,但是这是字节数组在lua中表达,可以直接传递给C#的byte[]local sHex = pb.tohex(bytes)Log.Info("Send Hex:{0}",sHex)GameEntry.Network:SendByLua(msgID,bytes) --有时候调用不到,生成一次wrap,再清除掉
end
1.lua中声明一个table为packet,里面字段为proto中描述
2.assert(pb.encode(sProto, data),table序列化二进制数组,返回值虽然为string,但这是字节数组在lua中的表达,可以直接传递到c#的byte[]中https://www.jianshu.com/p/63987134c1ba
C#处理
public static void SendByLua(this NetworkComponent networkComponent, int msgID, byte[] bytes)
{CSPacketLua packet = ReferencePool.Acquire<CSPacketLua>();packet.m_id = msgID;packet.m_bytes = PacketBuffer.GetBufferAndCopyBytes(bytes,bytes.Length);packet.Len = (ushort)bytes.Length;networkComponent.Send<CSPacketLua>(packet);
}
Lua中接收协议
C#中注册网络委托
public static Action<SCPacketLua> CreateFunc = null;
m_NetworkChannel.SetDefaultHandler(LuaPacketHandler);
在网络消息分发时,未找打id对应handle,再进入到网络委托中处理
GameFramework.EventPool.HandleEvent
m_DefaultHandler(sender, e);
Lua中执行网络委托内容
SF.NetworkChannelHelper.CreateFunc = handler(self,self.CreatePacket)function NetworkModule:CreatePacket(packet)local luaPacket = packetlocal sProto = MsgID2Proto[luaPacket.Id]if sProto == nil thenLog.Info("消息ID:{0}找不到需要序列化的proto对象",luaPacket.Id)returnendlocal data = assert(pb.decode(sProto, luaPacket.m_bytes.Buffer))PrintTable(data,false,true,"CreatePacket")
end
把C#中传递过来的SCPacketLua中的字节流使用lua-protobuf反序列化为table
流程图
GFxLuaProto发送协议流程图

GFxLuaProto接收协议流程图

遇到错误
字节流长度不对
ProtoBuf.ProtoException: Invalid wire-type; this usually means you have over-written a file without truncating or setting the length
使用字节流反序列化错误,检查长度之类
复用流,每次使用完要进行截断SetLength,否则会带入上次长度
流每次写入,都会改变position位置
lua-protobuf中反序列化,默认值问题
如果protobuf的成员值为默认值,序列化后会缺省这部分字节流。
lua中反序列化不出这个member。需要设置lua-protobuf中使用默认值
pb.option "use_default_values" --将默认值表复制到解码目标表中来
安卓测试
从C#发送,C#接收处打印

从Lua发送,Lua接收处打印

相关文章:
unity3d:GameFramework+xLua+Protobuf+lua-protobuf,与服务器交互收发协议
概述 1.cs收发协议,通过protobuf序列化 2.lua收发协议,通过lua-protobuf序列化 一条协议字节流组成 C#协议基类 CSPacketBase,SCPacketBaseC#用协议基类 proto生成的CS类,基于这两个基类。分别为CSPacketBase是客户端发送至服…...
二刷算法训练营Day30 | 回溯算法(6/6)
目录 详细布置: 1. 回溯总结 2. 332. 重新安排行程 3. 51. N 皇后 4. 37. 解数独 详细布置: 1. 回溯总结 回溯是递归的副产品,只要有递归就会有回溯,所以回溯法也经常和二叉树遍历,深度优先搜索混在一起&#x…...
【车载AI音视频电脑】200万像素迷你一体机
产品主要特点: -设备安装方便简洁,可通过3M胶直接将设备粘 贴到车前挡风玻璃上 -支持IE预览,手机,PAD实时预览, 支持电脑客 户端实时预览功能 -内置2路模拟高清, 每路均可达到200万像素。另 外可扩充2路1080P模拟…...
齐普夫定律在循环神经网络中的语言模型的应用
目录 齐普夫定律解释公式解释图与公式的关系代码与图的分析结论 使用对数表达方式的原因1. 线性化非线性关系2. 方便数据可视化和分析3. 降低数值范围4. 方便参数估计公式详细解释结论 来自:https://zh-v2.d2l.ai/chapter_recurrent-neural-networks/language-model…...
如何在Android Studio上发布Flutter应用
发布Flutter应用到Android平台是一个多步骤的过程,涉及配置应用、生成签名密钥、配置Gradle文件、构建发布版本APK等步骤。本文将详细介绍这些步骤,帮助你顺利发布Flutter应用。 1. 准备你的应用 在发布之前,确保你的应用在开发环境中运行良…...
C++ 字符串处理4-根据指定的分隔符将字符串分割为多个子串根据指定的分隔符将多个子串连接成一个字符串
1. 关键词 C 字符串处理 分割字符串 连接字符串 跨平台 2. strutil.h #pragma once#include <string> #include <vector>namespace cutl {/*** brief The type of vector strings used in this library.**/using strvec std::vector<std::string>;/*** b…...
微信小程序请求request封装
公共基础路径封装 // config.js module.exports {// 测试BASE_URL: https://cloud.chejj.cn,// 正式// BASE_URL: https://cloud.mycjj.com };请求封装 // request.js import config from ../config/baseUrl// 请求未返回时的loading const showLoading () > wx.showLoadi…...
Web前端不挂科:深入探索与实战指南
Web前端不挂科:深入探索与实战指南 在数字化时代的浪潮中,Web前端开发已成为一项炙手可热的技能。然而,对于许多初学者来说,如何避免在Web前端课程中挂科却成为了一道难题。本文将从四个方面、五个方面、六个方面和七个方面&…...
Golang | Leetcode Golang题解之第149题直线上最多的点数
题目: 题解: func maxPoints(points [][]int) (ans int) {n : len(points)if n < 2 {return n}for i, p : range points {if ans > n-i || ans > n/2 {break}cnt : map[int]int{}for _, q : range points[i1:] {x, y : p[0]-q[0], p[1]-q[1]if…...
京准电钟 NTP时间同步服务器助力水库水坝水利自动化建设
京准电钟 NTP时间同步服务器助力水库水坝水利自动化建设 京准电钟 NTP时间同步服务器助力水库水坝水利自动化建设 水库大坝监测系统主要包括渗流监测系统、流量监测系统、雨量监测系统、沉降监测系统组成。每一个监测系统由监测仪器及自动化数据采集装置(内置通信装…...
程序员应该具备什么职业素养?
程序员应该有什么职业素养? 作为一个程序员,拥有以下职业素养是非常重要的: 扎实的技术功底:作为程序员,首先要具备扎实的技术基础,包括编程语言、算法、数据结构等方面的知识,能够熟练地解决问…...
linux 安装sftp及使用sftp上传和下载
一、centos7 安装sftp 1.安装 OpenSSH 服务: sudo yum install openssh-server2.启动 SSH 服务,并设置为开机启动: sudo systemctl start sshd sudo systemctl enable sshd3.创建一个新用户,用于SFTP连接(替换your_…...
AI虚拟试穿技术:开启高保真、多场景、多样化服装组合的试穿应用
随着电子商务的快速发展,消费者对于在线购物体验的要求越来越高。特别是在服装领域,消费者渴望能够在购买前直观地了解服装的试穿效果。传统的虚拟试穿技术虽然已有一定的发展,但在不同场景下的高保真度和鲁棒性方面仍面临挑战。为此,我们研发了一种全新的AI虚拟试穿技术,…...
数栈xAI:轻量化、专业化、模块化,四大功能革新 SQL 开发体验
在这个数据如潮的时代,SQL 已远远超越了简单的查询语言范畴,它已成为数据分析和决策制定的基石,成为撬动企业智慧决策的关键杠杆。SQL 的编写和执行效率直接关系到数据处理的速度和分析结果的深度,对企业洞察市场动态、优化业务流…...
oppo手机精简包名列表
oppo广告机,coloros为13.0,测试机为oppo a1x 5g。 手机第一次开机后就全屏广告,被恶心了好几个月。现使用universal Android debolater进行卸载测试,其中: 不可卸载的: 开机广告:com.coloros.…...
Cisco Packet Tracer实验(二)
二、用交换机构建 LAN 构建物件如下: 四个PC 两个交换机 一个Multi Switch多功能拓展控制器 连线必须是这个直线!!!不是虚线 最后实现效果如下: 全部的线是绿的,就表示是通的。 尝试一下,看PC…...
Julia 数学函数
Julia 数学函数 Julia 是一种高性能的动态编程语言,特别适合于数值计算和科学计算。在数学领域,Julia 提供了丰富的内置函数,这些函数涵盖了从基本运算到高级数学运算的各个方面。本文将详细介绍 Julia 中的数学函数,并提供一些示例,帮助读者更好地理解和使用这些函数。 …...
[next.js] svgr/webpack
nextjs如何配置svg文件,使其像react组件一样导入? 当前next.js 开发环境我使用了--turbo 来开启turbopack加速文件构建,所以之前的一些webpack loader之类的无法正常工作。通过搜索发现一般都是使用svgr/webpack来处理svg,打开svgr官网发现…...
vue页面和 iframe多页面无刷新方案和并行存在解决方案
面临问题 : back的后台以jsp嵌套iframe为主, 所以在前端框架要把iframe无刷新嵌套和vue页面进行并行使用,vue的keep-alive只能对虚拟dom树 vtree 进行缓存无法缓存iframe,所以要对iframe进行处理 tab标签的切换效果具体参考若依框架的tab切换,可以去若依看源码,若依源码没有实…...
Leetcode498. 对角线遍历
Every day a Leetcode 题目来源:498. 对角线遍历 解法1:模拟 根据题目要求,矩阵按照对角线进行遍历。设矩阵的行数为 m,矩阵的列数为 n,我们仔细观察对角线遍历的规律可以得到如下信息: 一共有 mn−1 条…...
手游刚开服就被攻击怎么办?如何防御DDoS?
开服初期是手游最脆弱的阶段,极易成为DDoS攻击的目标。一旦遭遇攻击,可能导致服务器瘫痪、玩家流失,甚至造成巨大经济损失。本文为开发者提供一套简洁有效的应急与防御方案,帮助快速应对并构建长期防护体系。 一、遭遇攻击的紧急应…...
Linux 文件类型,目录与路径,文件与目录管理
文件类型 后面的字符表示文件类型标志 普通文件:-(纯文本文件,二进制文件,数据格式文件) 如文本文件、图片、程序文件等。 目录文件:d(directory) 用来存放其他文件或子目录。 设备…...
莫兰迪高级灰总结计划简约商务通用PPT模版
莫兰迪高级灰总结计划简约商务通用PPT模版,莫兰迪调色板清新简约工作汇报PPT模版,莫兰迪时尚风极简设计PPT模版,大学生毕业论文答辩PPT模版,莫兰迪配色总结计划简约商务通用PPT模版,莫兰迪商务汇报PPT模版,…...
C++课设:简易日历程序(支持传统节假日 + 二十四节气 + 个人纪念日管理)
名人说:路漫漫其修远兮,吾将上下而求索。—— 屈原《离骚》 创作者:Code_流苏(CSDN)(一个喜欢古诗词和编程的Coder😊) 专栏介绍:《编程项目实战》 目录 一、为什么要开发一个日历程序?1. 深入理解时间算法2. 练习面向对象设计3. 学习数据结构应用二、核心算法深度解析…...
【Linux】Linux安装并配置RabbitMQ
目录 1. 安装 Erlang 2. 安装 RabbitMQ 2.1.添加 RabbitMQ 仓库 2.2.安装 RabbitMQ 3.配置 3.1.启动和管理服务 4. 访问管理界面 5.安装问题 6.修改密码 7.修改端口 7.1.找到文件 7.2.修改文件 1. 安装 Erlang 由于 RabbitMQ 是用 Erlang 编写的,需要先安…...
CSS3相关知识点
CSS3相关知识点 CSS3私有前缀私有前缀私有前缀存在的意义常见浏览器的私有前缀 CSS3基本语法CSS3 新增长度单位CSS3 新增颜色设置方式CSS3 新增选择器CSS3 新增盒模型相关属性box-sizing 怪异盒模型resize调整盒子大小box-shadow 盒子阴影opacity 不透明度 CSS3 新增背景属性ba…...
2025年低延迟业务DDoS防护全攻略:高可用架构与实战方案
一、延迟敏感行业面临的DDoS攻击新挑战 2025年,金融交易、实时竞技游戏、工业物联网等低延迟业务成为DDoS攻击的首要目标。攻击呈现三大特征: AI驱动的自适应攻击:攻击流量模拟真实用户行为,差异率低至0.5%,传统规则引…...
Java多线程实现之Runnable接口深度解析
Java多线程实现之Runnable接口深度解析 一、Runnable接口概述1.1 接口定义1.2 与Thread类的关系1.3 使用Runnable接口的优势 二、Runnable接口的基本实现方式2.1 传统方式实现Runnable接口2.2 使用匿名内部类实现Runnable接口2.3 使用Lambda表达式实现Runnable接口 三、Runnabl…...
Cursor AI 账号纯净度维护与高效注册指南
Cursor AI 账号纯净度维护与高效注册指南:解决限制问题的实战方案 风车无限免费邮箱系统网页端使用说明|快速获取邮箱|cursor|windsurf|augment 问题背景 在成功解决 Cursor 环境配置问题后,许多开发者仍面临账号纯净度不足导致的限制问题。无论使用 16…...
SOC-ESP32S3部分:30-I2S音频-麦克风扬声器驱动
飞书文档https://x509p6c8to.feishu.cn/wiki/SKZzwIRH3i7lsckUOlzcuJsdnVf I2S简介 I2S(Inter-Integrated Circuit Sound)是一种用于传输数字音频数据的通信协议,广泛应用于音频设备中。 ESP32-S3 包含 2 个 I2S 外设,通过配置…...
