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接收处打印
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/850862.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

《python程序语言设计》2018版第5章第48题以0,0为圆心 绘制10个左右的同心圆

在0&#xff0c;0点处绘制10个圆。 其实这个题先要记住python不会0&#xff0c;0为原点进行绘画。 它是按半径来画&#xff0c;所以我们要先把turtle这个小画笔送到它应该去的起点。&#xff08;我经常有这样的错觉&#xff0c;每次都是这样想办法把自己拉回来&#xff09; 我…

AI视频教程下载:如何用ChatGPT来求职找工作?

这是一个关于使用ChatGPT找工作的课程&#xff0c;作者分享了自己的求职经验和技巧&#xff0c;介绍了如何使用人工智能来改进个人资料和简历&#xff0c;以及如何研究公司和面试。通过细节处理职业目标、分享个人兴趣和技能、寻求导师和专业发展机会&#xff0c;以及在行业内建…

各地业主们开始换着花样保房价了

不止杭州&#xff0c;还在广州、南京、成都...更多城市蔓延开来 各位有没有想过&#xff0c;为什么会有“保房价” 我想很多人最先听说这个词还是来自杭州业主 的确&#xff0c;作为曾经受房价影响最大的一个城市&#xff0c;杭州业主们可以说是最深谙房价上涨逻辑的那泼人了…

【计算机网络基础知识】

首先举一个生活化的例子&#xff0c;当你和朋友打电话时&#xff0c;你可能会使用三次握手和四次挥手的过程进行类比&#xff1a; 三次握手&#xff08;Three-Way Handshake&#xff09;&#xff1a; 你打电话给朋友&#xff1a;你首先拨打你朋友的电话号码并等待他接听。这就…

为什么在 TypeScript 中应优先使用类型而非接口

类型和接口是每个 TypeScript 程序中常用的强大功能。然而&#xff0c;由于类型和接口在功能上非常相似&#xff0c;这就引出了一个问题&#xff1a;哪一个更好&#xff1f; 今天&#xff0c;我们将评估类型和接口&#xff0c;并得出结论&#xff0c;为什么在大多数情况下你应该…

HikariCP连接池初识

HikariCP的简单介绍 hikari-光&#xff0c;hikariCP取义&#xff1a;像光一样轻和快的Connetion Pool。这个几乎只用java写的中间件连接池&#xff0c;极其轻量并注重性能&#xff0c;HikariCP目前已是SpringBoot默认的连接池&#xff0c;伴随着SpringBoot和微服务的普及&…

ssm汽车在线销售系统

摘 要 21世纪的今天&#xff0c;随着社会的不断发展与进步&#xff0c;人们对于信息科学化的认识&#xff0c;已由低层次向高层次发展&#xff0c;由原来的感性认识向理性认识提高&#xff0c;管理工作的重要性已逐渐被人们所认识&#xff0c;科学化的管理&#xff0c;使信息存…

稀疏高效扩散模型:推动扩散模型的部署与应用

数据驱动的世界中&#xff0c;生成模型扮演着至关重要的角色&#xff0c;尤其是在需要创建逼真样本的任务中。扩散模型&#xff08;Diffusion Models, DM&#xff09;&#xff0c;以其卓越的样本质量和广泛的模式覆盖能力&#xff0c;已经成为众多数据生成任务的首选。然而&…

nginx: [error] invalid PID number ““ in “/run/nginx.pid“

两种问题&#xff0c;我自己碰到的情况 ./nginx -s reload执行命令后报错 nginx: [error] invalid PID number ““ in “/run/nginx.pid“ 第一种情况&#xff0c;pid被注释了 /usr/local/nginx/conf/nginx.conf #user nobody; worker_processes 1;// 可能是这里被注释了…

【UE5:CesiumForUnreal】——从地球全景聚焦到某区域的动画制作

目录 1.添加Render Texture并和SceneCapture2D关联 1.1 场景准备 1.2 添加Render Texture 1.3 添加SceneCapture2D并关联 2.在Widget上显示Render Texture 2.1 创建Widget 2.2 配置Widget 2.3 添加控制按钮 2.4 添加窗口逻辑 3.制作Sequencer动画 3.1 创建Sequencer…

基于实验的电动汽车动力电池SOC

前言 本文为笔者在学习《基于MATLAB的新能源汽车仿真》过程中学习笔记&#xff0c;所涉及的表格数据和公式均为书籍里的。仿真数据是网上找的恒电流放电数据。本文仅作为笔者的入门学习记录。 一、分析动力电池SOC估算方法 SOC是指动力电池按照规定放电条件可以释放的容量占…

计算机网络 期末复习(谢希仁版本)第5章

**屏蔽作用&#xff1a;**运输层向高层用户屏蔽了下面网络核心的细节&#xff08;如网络拓扑、所采用的路由选择协议等&#xff09;&#xff0c;使应用进程看见的就是好像在两个运输层实体之间有一条端到端的逻辑通信信道。 10. 端口用一个 16 位端口号进行标志&#xff0c;允许…

学习使用Opentelemetry python SDK

前言 &#x1f4e2;博客主页&#xff1a;程序源⠀-CSDN博客 &#x1f4e2;欢迎点赞&#x1f44d;收藏⭐留言&#x1f4dd;如有错误敬请指正&#xff01; 一、什么是 OpenTelemetry OpenTelemetry 由 OpenTracing 和 OpenCensus 项目合并而成&#xff0c;是一组规范、工具、API…

HQChart小程序教程4-动态控制手势滚动页面

动态控制手势滚动页面 示例效果canvas 控制页面滚动属性步骤1. 使用变量绑定disable-scroll2. 在手势处理函数中控制是否滚动页面 交流QQ群HQChart代码地址 示例效果 canvas 控制页面滚动属性 根据官方文档&#xff0c;disable-scroll 属性是控制画布手势是否可以滚动页面。 h…

《python程序语言设计》2018版第5章第47题绘制随机球,在一个宽120高100的矩形里绘制随机的点

这个题其实并不难。 首先我们利用turtle功能绘制一个矩形&#xff0c;圆心点题里要求的是0&#xff0c;0 这个好办 然后我们根据宽120&#xff0c;高100计算一下。肯定是正负两个值参与其中。 坐标点如下 建立矩形代码如下 turtle.penup() turtle.goto(-60, 50) turtle.pend…

【Redis】什么是Redis缓存 雪崩、穿透、击穿?(一篇文章就够了)

目录 什么是Redis? Redis的正常存储流程&#xff1f; 什么是Redis缓存雪崩&#xff1f; 缓存雪崩 缓存预热 缓存失效时间的随机性 什么是Redis缓存穿透&#xff1f; 缓存穿透 缓存空对象 BloomFilter&#xff08;布隆过滤器&#xff09; 什么是Redis缓存击穿&#…

【Python教程】2-函数、逻辑运算与条件判断

在整理自己的笔记的时候发现了当年学习python时候整理的笔记&#xff0c;稍微整理一下&#xff0c;分享出来&#xff0c;方便记录和查看吧。个人觉得如果想简单了解一名语言或者技术&#xff0c;最简单的方式就是通过菜鸟教程去学习一下。今后会从python开始重新更新&#xff0…

TransformerFAM:革新深度学习的新型注意力机制

深度学习领域的一项突破性技术——Transformer架构&#xff0c;已经彻底改变了我们处理序列数据的方式。然而&#xff0c;Transformer在处理长序列数据时面临的二次复杂度问题&#xff0c;限制了其在某些应用场景下的潜力。针对这一挑战&#xff0c;研究者们提出了一种名为Tran…

window wsl2的ubuntu如何配置代理获取docker image

最近两天&#xff0c;docker pull一直下不来docker image, 研究了下可以通过代理pull, 我的是window电脑下的linux子系统wsl2, 装的是ubuntu跑docker. # 创建/etc/systemd/system/docker.service.d路径 sudo mkdir -p /etc/systemd/system/docker.service.d # 创建 http-proxy…

[ue5]建模场景学习笔记(5)——必修内容可交互的地形,交互沙(3)

1.需求分析&#xff1a; 我们现在已经能够让这片地形出现在任意地方&#xff0c;只要角色走在这片地形上&#xff0c;就能够产生痕迹&#xff0c;但这片区域总是需要人工指定&#xff0c;又无法把这片区域无限扩大&#xff08;显存爆炸&#xff09;&#xff0c;因此尝试使角色无…