unity3d:GameFramework+xLua+Protobuf+lua-protobuf,与服务器交互收发协议

news2024/11/26 7:37:22

概述

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; }
    } 
	//网络协议Id
	public 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; //协议id
        public 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;
        //先写入body
        m_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,BodyLen
        ReceiveAsync();
        return;
    }
    //开始解析,流要置为0位置开始解析
    m_ReceiveState.Stream.Position = 0L;

    bool processSuccess = false;
    if (m_ReceiveState.PacketHeader != null)
    {
        //上次获取到头,这次反序列处理body
        processSuccess = 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,BodyLen
        ReceiveAsync();
        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 为接收流,每次接收一整条消息前,设置了Len
        packet = (Packet)ReferencePool.Acquire(packetType);
        packet = (Packet)RuntimeTypeModel.Default.Deserialize(source, packet, packetType);
    }
    else
    {
        //如果id找不到字节流传递给Lua中反序列化为table
        SCPacketLua 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
        {
            //这里会传递网络handle
            current.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)
end

function NetworkModule:Send(msgID,data)
    local sProto = MsgID2Proto[msgID]
    if sProto == nil then
        Log.Info("消息ID:{0}找不到需要序列化的proto对象",msgID)
        return
    end
    local 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 = packet
    local sProto = MsgID2Proto[luaPacket.Id]
    if sProto == nil then
        Log.Info("消息ID:{0}找不到需要序列化的proto对象",luaPacket.Id)
        return
    end
    local 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.coloradmin.cn/o/1805698.html

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

相关文章

真空衰变,真正的宇宙级灾难,它到底有多可怕?

真空衰变&#xff0c;真正的宇宙级灾难&#xff0c;它到底有多可怕&#xff1f; 真空衰变 真空衰变&#xff08;Vacuum decay&#xff09;是物理学家根据量子场论推测出的一种宇宙中可能会发生的现象&#xff0c;这种现象被称为真正的宇宙级灾难&#xff0c;它到底有多可怕呢…

野火FPGA跟练(四)——串口RS232、亚稳态

目录 简介接口与引脚通信协议亚稳态RS232接收模块模块框图时序波形RTL 代码易错点Testbench 代码仿真 RS232发送模块模块框图时序波形RTL 代码Testbench 代码仿真 简介 UART&#xff1a;Universal Asynchronous Receiver/Transmitter&#xff0c;异步串行通信接口。发送数据时…

sick0s1.1 靶机实战

sick0s1.1 信息收集 nmap存活及端口&#xff1a; nmap服务扫描&#xff1a; web 80和8080都没有开放&#xff0c;&#xff0c;无法访问&#xff0c;gobuster等工具也跑不了&#xff0c;访问一下3128试试 根据端口服务扫描也能得知这是个http的代理服务器&#xff0c;&#x…

机器学习常见知识点 2:决策树

文章目录 决策树算法1、决策树树状图2、选择最优决策条件3、决策树算法过程→白话决策树原理决策树构建的基本步骤常见的决策树算法决策树的优缺点 【五分钟机器学习】可视化的决策过程&#xff1a;决策树 Decision Tree 关键词记忆&#xff1a; 纯度、选择最优特征分裂、熵、基…

SLAM小题目

1、最小二乘题目&#xff1a; 假设有三个WIFI热点&#xff0c;位置分别在(x1,y1), (x2,y2), (x3,y3), 移动端测量到每一个热点的距离L1,L2和L3&#xff0c;要求解移动端的位置. #include <iostream> #include <vector> #include <cmath> class Point { pub…

数据结构笔记 4 树和二叉树

二叉树和完全二叉树的区别&#xff1f; 二叉树和完全二叉树的主要区别在于它们的结构特性和节点排列方式&#xff1a; 1. **二叉树**&#xff1a; - 是一种数据结构&#xff0c;其中每个节点最多有两个子节点&#xff0c;通常称为左子节点和右子节点。 - 节点的子节点数量…

海思SD3403,SS928/926,hi3519dv500,hi3516dv500移植yolov7,yolov8(21)Yolov9s测试

四天前yolov9的作者终于开源了yolov9s和yolov9t模型。这个作者之前一直没开源t,s,只有c开始的,而且onnx转换后数据大小特别大,当时直接就放弃测试了。 另外之前代码有很明显的抄v5的痕迹。所以印象很不好。 现在总算是开源t,s模型,而且这里评估的结果上来看是好于yolov8的…

两款好用的IOS、Android图片处理应用

GIF 小助手 GIF工具包是一个简单实用的GIF动画编辑器&#xff0c;目前仅支持IOS平台。 使用该软件&#xff0c;可以将多个图像、视频和现场照片创建为gif。 主要功能&#xff1a; 多种输入源&#xff1a;用户可以将多个图片、视频或Livephoto转换成GIF动图。 编辑功能&#…

RDK X3(aarch64) 测试激光雷达思岚A1

0. 环境 - 亚博智能的ROSMASTER-X3 - RDK X3 1.0 0.1 资料 文档资料 https://www.slamtec.com/cn/Support#rplidar-a-series SDK https://github.com/slamtec/rplidar_sdk ROS https://github.com/slamtec/rplidar_ros https://github.com/Slamtec/sllidar_ros2 1. robostu…

windows上安装MongoDB,springboot整合MongoDB

上一篇文章已经通过在Ubuntu上安装MongoDB详细介绍了MongoDB的各种命令用法。 Ubuntu上安装、使用MongoDB详细教程https://blog.csdn.net/heyl163_/article/details/133781878 这篇文章介绍一下在windows上安装MongoDB&#xff0c;并通过在springboot项目中使用MongoDB记录用户…

Java:112-SpringMVC的底层原理(下篇)

这里继续续写上一章博客&#xff08;111章博客&#xff09;&#xff1a; Spring MVC 源码深度剖析&#xff1a; 既然我们自行写出了一个&#xff0c;那么我们可以选择看看mvc源码&#xff1a; 前端控制器 DispatcherServlet 继承结构&#xff1a; 前面我们知道mvc是操作同…

实验六、IPv4 地址的子网划分,第 2 部分《计算机网络》

你有没有发现&#xff0c;困的时候真的清醒不了。 目录 一、实验目的 二、实验内容 三、实验小结 一、实验目的 完成本练习之后&#xff0c;您应该能够确定给定 IP 地址和子网掩码的子网信息。 知道 IP 地址、网络掩码和子网掩码后&#xff0c;您应该能够确定有关该 IP 地…

【学术小白成长之路】02三方演化博弈(基于复制动态方程)期望与复制动态方程

从本专栏开始&#xff0c;笔者正式研究演化博弈分析&#xff0c;其中涉及到双方演化博弈分析&#xff0c;三方演化博弈分析&#xff0c;复杂网络博弈分析等等。 先阅读了大量相关的博弈分析的文献&#xff0c;总结了现有的研究常用的研究流程&#xff0c;针对每个流程进行拆解。…

cmake使用make和Ninja构建对比

前提 make和Ninja是两个常见的构建工具&#xff0c;在网上查阅了一些资料&#xff0c;说是Ninja比make构建速度要快很多。但是具体不知道快多少&#xff0c;所以趁着这次编译clang的机会&#xff0c;分享下它们在时间方面差多少。 步骤 下载llvm 参考llvm官网&#xff0c;这…

Shell脚本学习_内置命令

目录 1.内置命令介绍&#xff1a; 2.Shell内置命令&#xff1a;alias设置别名 3.Shell内置命令&#xff1a;echo输出字符串 4.Shell内置命令&#xff1a;read读取控制台输入 5.Shell内置命令&#xff1a;exit退出 6.Shell内置命令&#xff1a;declare设置变量 1.内置命令…

详解SM3算法加密流程(SM3加密算法一)

1、SM3 算法简介 SM3是中国国家密码管理局发布的消息摘要算法&#xff0c;首次发布于2010年&#xff0c;并于2016年发布了正式的国家标准GB/T 32905-2016。类似于国际上广泛应用的SHA-256算法&#xff0c;但有其独特的设计和实现细节。 该算法应用于各种数据加密和验证场景&…

【NI国产替代】产线综测仪:锂电池保护板测试仪,支持快速定制

• 精度等级01% • 支持直流电压、电流、nA 级待机电流电阻等&#xff0c;常规测试 • 支持过压、欠压、过冲、过放、过温,短路等&#xff0c;保护测试 • 通讯总线电平可编程&#xff0c;兼容多种 • 支持 SWD 或IIC 固件烧录 • 测试速度快&#xff0c;支持最多 24 通道…

Windows关闭自动更新最有效的方法

按WR打开电脑命令框输入“regedit”进入注册表 依次点击以下几个 右击新建一个“DWORD(32位)值”&#xff0c;命名为”FlightSettingsMaxPauseDays“ 右边选择十进制&#xff0c;左边填写暂停更新的天数 打开windows更新&#xff0c;进入高级选项 选择暂停更新的天数&#xff…

数据库(27)——多表查询——自连接

语法 SELECT 字段列表 FROM 表A 别名A JOIN 表A 别名B ON 条件...; 自连接可以是内连接查询也可以是外连接查询。 演示 我新增了字段friend便于演示 查询所有人的名字以及他们的friend的人的名字&#xff1a; select a.name,b.name from user a,user b where a.friendb.id; 其…

L48---1637. 两点之间不包含任何点的最宽垂直区域(排序)---Java版

1.题目描述 2.思路 &#xff08;1&#xff09;返回两点之间内部不包含任何点的 最宽垂直区域 的宽度。 我的理解是相邻两个点&#xff0c;按照等差数列那样&#xff0c;后一个数减去相邻的前一个数&#xff0c;才能保证两数之间不含其他数字。 &#xff08;2&#xff09;所以&…