游戏框架搭建

使用框架的目标:低耦合,高内聚,表现和数据分离
耦合:对象,类的双向引用,循环引用
内聚:相同类型的代码放在一起
表现和数据分离:需要共享的数据放在Model里

对象之间的交互一般有三种

  1. 方法调用,A持有B才能调用B的方法
  2. 委托或回调,A持有B才能注册B的委托,尽量避免嵌套调用
  3. 消息或事件,A不需要持有B

A调用B的方法,A就必须持有B,形成单向引用关系,为了避免耦合,B不应该引用A,如果B想调用A的方法,使用委托或回调。
总结:父节点调用子节点可以直接方法调用,子节点通知父节点用委托或事件,跨模块通信用事件

模块化一般有三种

  1. 单例,例如: Manager Of Managers
  2. IOC,例如: Extenject,uFrame的 Container,StrangelOC的绑定等等
  3. 分层,例如: MVC、三层架构、领域驱动分层等等

交互逻辑和表现逻辑

在这里插入图片描述
以计数器为例,用户操作界面修改数据叫交互逻辑,当数据变更之后或者初始化时,从Model里查询数据在View上显示叫表现逻辑
交互逻辑:View -> Model
表现逻辑:Model -> View

很多时候,我们不会真的去用 MVC 开发架构,而是使用表现(View)和数据(Model)分离这样的思想,我们只要知道 View 和 Model 之间有两种逻辑,即交互逻辑 和 表现逻辑,我们就不用管中间到底是 Controller、还是 ViewModel、还是 Presenter。只需要想清楚交互逻辑 和 交互逻辑如何实现的就可以了。

View和Model怎样交互比较好,或者说交互逻辑和表现逻辑怎样实现比较好?

<1> 直接方法调用,表现逻辑是在交互逻辑完成之后主动调用,伪代码如下

public class CounterViewController : MonoBehaviour
{void Start(){transform.Find("BtnAdd").GetComponent<Button>().onClick.AddListener(() =>{// 交互逻辑CounterModel.Count++;// 表现逻辑UpdateView();});transform.Find("BtnSub").GetComponent<Button>().onClick.AddListener(() =>{// 交互逻辑CounterModel.Count--;// 表现逻辑UpdateView();});// 表现逻辑UpdateView();}void UpdateView(){transform.Find("CountText").GetComponent<Text>().text = CounterModel.Count.ToString();}
}public static class CounterModel
{public static int Count = 0;
}

<2> 使用委托

public class CounterViewController : MonoBehaviour
{void Start(){// 注册CounterModel.OnCountChanged += OnCountChanged;transform.Find("BtnAdd").GetComponent<Button>().onClick.AddListener(() =>{// 交互逻辑:这个会自动触发表现逻辑CounterModel.Count++;});transform.Find("BtnSub").GetComponent<Button>().onClick.AddListener(() =>{// 交互逻辑:这个会自动触发表现逻辑CounterModel.Count--;});OnCountChanged(CounterModel.Count);}// 表现逻辑private void OnCountChanged(int newCount){transform.Find("CountText").GetComponent<Text>().text = newCount.ToString();}private void OnDestroy(){// 注销CounterModel.OnCountChanged -= OnCountChanged;}
}public static class CounterModel
{private static int mCount = 0;public static event Action<int> OnCountChanged ;public static int Count{get => mCount;set{if (value != mCount){mCount = value;OnCountChanged?.Invoke(value);}}}
}

<3> 使用事件,事件管理器写法差不多,这里忽略具体实现

public class CounterViewController : MonoBehaviour
{void Start(){// 注册EventManager.Instance.RegisterEvent(EventId, OnCountChanged);transform.Find("BtnAdd").GetComponent<Button>().onClick.AddListener(() =>{// 交互逻辑:这个会自动触发表现逻辑CounterModel.Count++;});transform.Find("BtnSub").GetComponent<Button>().onClick.AddListener(() =>{// 交互逻辑:这个会自动触发表现逻辑CounterModel.Count--;});OnCountChanged();}// 表现逻辑private void OnCountChanged(){transform.Find("CountText").GetComponent<Text>().text = CounterModel.Count.ToString();}private void OnDestroy(){// 注销EventManager.Instance.UnRegisterEvent(EventId, OnCountChanged);}
}public static class CounterModel
{private static int mCount = 0;public static int Count{get => mCount;set{if (value != mCount){mCount = value;// 触发事件EventManager.Instance.FireEvent(EventId);}}}
}

比较上面3种实现方式,当数据量很多的时候,使用第1种方法调用会写很多重复代码调用,代码臃肿,容易造成疏忽,使用委托或事件代码更精简,当数据变化时会自动触发表现逻辑,这就是所谓的数据驱动。

所以表现逻辑使用委托或事件更合适,如果是单个数值变化,用委托的方式更合适,比如金币、分数、等级、经验值等等,如果是颗粒度较大的更新用事件比较合适,比如从服务器拉取了一个任务列表数据,然后任务列表数据存到了Model

BindableProperty

上面的Model类,每新增一个数据就要写一遍类似的代码,很繁琐,我们使用泛型来简化代码

public class BindableProperty<T> where T : IEquatable<T>
{private T mValue;public T Value{get => mValue;set{if (!mValue.Equals(value)){mValue = value;OnValueChanged?.Invoke(value);}}}public Action<T> OnValueChanged;
}

BindableProperty 也就是可绑定的属性,是 数据 + 数据变更事件 的合体,它既存储了数据充当 C# 中的 属性这样的角色,也可以让别的地方监听它的数据变更事件,这样会减少大量的样板代码

public class CounterViewController : MonoBehaviour
{void Start(){// 注册CounterModel.Count.OnValueChanged += OnCountChanged;transform.Find("BtnAdd").GetComponent<Button>().onClick.AddListener(() =>{// 交互逻辑:这个会自动触发表现逻辑CounterModel.Count.Value++;});transform.Find("BtnSub").GetComponent<Button>().onClick.AddListener(() =>{// 交互逻辑:这个会自动触发表现逻辑CounterModel.Count.Value--;});OnCountChanged(CounterModel.Count.Value);}// 表现逻辑private void OnCountChanged(int newValue){transform.Find("CountText").GetComponent<Text>().text = newValue.ToString();}private void OnDestroy(){// 注销CounterModel.Count.OnValueChanged -= OnCountChanged;}
}public static class CounterModel
{public static BindableProperty<int> Count = new BindableProperty<int>(){Value = 0};
}

总结:

  • 自顶向下的逻辑使用方法调用
  • 自底向上的逻辑使用委托或事件,Model和View是底层和上层的关系,所以用委托或事件更合适

Command

实际的开发中交互逻辑的代码是很多的,随着功能需求越来越多,Controller的代码会越来越臃肿,解决办法是引入命令模式(Command),命令模式参考另一篇博客:Unity常用设计模式
先定义一个接口

public interface ICommand
{void Execute();
}

添加一个命令,实现数据加一操作,注意这里是用 struct 实现的,而不是用的 class,这是因为游戏里边的交互逻辑有很多,如果每一个都用去 new 一个 class 的话,会造成很多性能消耗,比如 new 一个对象所需要的寻址操作、比如对象回收需要的 gc 等等,而 struct 内存管理效率要高很多

public struct AddCountCommand : ICommand
{public void Execute(){CounterModel.Count.Value++;}
}

实现数据减一操作

public struct SubCountCommand : ICommand
{public void Execute(){CounterModel.Count.Value--;}
}

更新交互逻辑的代码

    public class CounterViewController : MonoBehaviour{void Start(){// 注册CounterModel.Count.OnValueChanged += OnCountChanged;transform.Find("BtnAdd").GetComponent<Button>().onClick.AddListener(() =>{// 交互逻辑new AddCountCommand().Execute();});transform.Find("BtnSub").GetComponent<Button>().onClick.AddListener(() =>{// 交互逻辑new SubCountCommand().Execute();});OnCountChanged(CounterModel.Count.Value);}// 表现逻辑private void OnCountChanged(int newValue){transform.Find("CountText").GetComponent<Text>().text = newValue.ToString();}private void OnDestroy(){// 注销CounterModel.Count.OnValueChanged -= OnCountChanged;}}public static class CounterModel{public static BindableProperty<int> Count = new BindableProperty<int>(){Value = 0};}

使用 Command 符合读写分离原则(Comand Query Responsibility Segregation),简写为 CQRS ,这个概念在 StrangeIOC、uFrame、PureMVC、Loxodon Framework 都有实现,而在微服务领域比较火的 DDD(领域驱动设计)的实现一般也会实现 CQRS。它是一种软件架构模式,旨在将应用程序的读取和写入操作分离为不同的模型。在CQRS中,写操作通常由命令模型(Command Model)来处理,它负责处理业务逻辑和状态更改。而读操作则由查询模型(Query Model)来处理,它专门用于支持数据查询和读取展示。

Command 模式就是逻辑的调用和执行是分离的,我们知道一个方法的调用和执行是不分离的,因为一旦你调用方法了,方法也就执行了,而 Command 模式能够做到调用和执行在空间和时间上是能分离的。

空间分离的方法就是调用的地方和执行的地方放在两个文件里。
时间分离的方法就是调用的之后,Command 过了一点时间才被执行。

Command 分担 Controller 的交互逻辑,由于有了调用和执行分离这个特点,所以我们可以用不同的数据结构去组织 Command 调用,比如列表,队列,栈

在这里插入图片描述
底层系统层是可以共享给别的展现层使用的,切换表现层非常方便,表现层到系统层用 Command 改变底层系统的状态(数据),系统层通过事件或者委托通知表现层,在通知的时候可以推送数据,也可以让表现层收到通知后自己去查询数据。

模块化

使用单例

单例比静态类好一点就是其生命周期相对可控,而且访问单例对象比访问静态类多了一点限制,也就是需要通过 Instance 获取

每个模块继承 Singleton

public class Singleton<T> where T : class
{public static T Instance{get{if (mInstance == null){// 通过反射获取构造var ctors = typeof(T).GetConstructors(BindingFlags.Instance | BindingFlags.NonPublic);// 获取无参非 public 的构造var ctor = Array.Find(ctors, c => c.GetParameters().Length == 0);if (ctor == null){throw new Exception("Non-Public Constructor() not found in " + typeof(T));}mInstance = ctor.Invoke(null) as T;}return mInstance;}}private static T mInstance;
}

问题:单例没有访问限制,容易造成模块之间互相引用,关系混乱

IOC容器

IOC 容器可以理解为是一个字典,这个字典以 Type 为 key,以对象即 Instance 为 value,IOC 容器最少有两个核心的 API,即根据 Type 注册实例,根据 Type 获取实例

public class IOCContainer
{/// <summary>/// 实例/// </summary>public Dictionary<Type, object> mInstances = new Dictionary<Type, object>();/// <summary>/// 注册/// </summary>/// <param name="instance"></param>/// <typeparam name="T"></typeparam>public void Register<T>(T instance){var key = typeof(T);if (mInstances.ContainsKey(key)){mInstances[key] = instance;}else{mInstances.Add(key,instance);}}/// <summary>/// 获取/// </summary>public T Get<T>() where T : class{var key = typeof(T);object retObj;if(mInstances.TryGetValue(key,out retObj)){return retObj as T;}return null;}
}

下面是一个简单的示例,IOC 容器创建,注册实际应当写在游戏初始化时,这里为了方便演示都写在一起了

public class IOCExample : MonoBehaviour
{void Start(){// 创建一个 IOC 容器var container = new IOCContainer();// 注册一个蓝牙管理器的实例container.Register(new BluetoothManager());// 根据类型获取蓝牙管理器的实例var bluetoothManager = container.Get<BluetoothManager>();//连接蓝牙bluetoothManager.Connect();}public class BluetoothManager{public void Connect(){Debug.Log("蓝牙连接成功");}}
}

为了避免样板代码,这里创建一个抽象类

/// <summary>
/// 架构
/// </summary>
public abstract class Architecture<T> where T : Architecture<T>, new()
{#region 类似单例模式 但是仅在内部课访问private static T mArchitecture = null;// 确保 Container 是有实例的static void MakeSureArchitecture(){if (mArchitecture == null){mArchitecture = new T();mArchitecture.Init();}}#endregionprivate IOCContainer mContainer = new IOCContainer();// 留给子类注册模块protected abstract void Init();// 提供一个注册模块的 APIpublic void Register<T>(T instance){MakeSureArchitecture();mArchitecture.mContainer.Register<T>(instance);}// 提供一个获取模块的 APIpublic static T Get<T>() where T : class{MakeSureArchitecture();return mArchitecture.mContainer.Get<T>();}
}

子类注册多个模块

public class PointGame : Architecture<PointGame>
{// 这里注册模块protected override void Init(){Register(new GameModel1());Register(new GameModel2());Register(new GameModel3());Register(new GameModel4());}
}

使用 IOC 容器的目的是增加模块访问的限制

除了可以用来注册和获取模块,IOC 容器一般还会有一个隐藏的功能,即:注册接口模块

public class IOCExample : MonoBehaviour
{void Start(){// 创建一个 IOC 容器var container = new IOCContainer();// 根据接口注册实例container.Register<IBluetoothManager>(new BluetoothManager());// 根据接口获取蓝牙管理器的实例var bluetoothManager = container.Get<IBluetoothManager>();//连接蓝牙bluetoothManager.Connect();}/// <summary>/// 定义接口/// </summary>public interface IBluetoothManager{void Connect();}/// <summary>/// 实现接口/// </summary>public class BluetoothManager : IBluetoothManager{public void Connect(){Debug.Log("蓝牙连接成功");}}
}

抽象-实现 这种形式注册和获取对象的方式是符合依赖倒置原则的。
依赖倒置原则(Dependence Inversion Principle):程序要依赖于抽象接口,不要依赖于具体实现。依赖倒置原则是 SOLID 中的字母 D。

这种设计的好处:

  • 接口设计与实现分成两个步骤,接口设计时可以专注于设计,实现时可以专注于实现。
  • 实现是可以替换的,比如一个接口叫 IStorage,其实现可以是 PlayerPrefsStorage、EdtiroPrefsStorage,等切换时候只需要一行代码就可以切换了。
  • 比较容易测试(单元测试等)
  • 降低耦合。
接口的显式实现
public interface ICanSayHello
{void SayHello();void SayOther();
}public class InterfaceDesignExample : MonoBehaviour, ICanSayHello
{/// <summary>/// 接口的隐式实现/// </summary>public void SayHello(){Debug.Log("Hello");}/// <summary>/// 接口的显式实现,不能写访问权限关键字/// </summary>void ICanSayHello.SayOther(){Debug.Log("Other");}void Start(){// 隐式实现的方法可以直接通过对象调用this.SayHello();// 显式实现的接口不能通过对象调用// this.SayOther() // 会报编译错误// 显式实现的接口必须通过接口对象调用(this as ICanSayHello).SayOther();}
}

当需要实现多个签名一致的方法时,可以通过接口的显式声明来区分到底哪个方法是属于哪个接口的
利用接口的显示实现,子类想要调用必须先转成接口,这样就增加了调用显式实现的方法的成本,所以可以理解为这个方法被阉割了

分层

前面使用 Command 分担了 Controller 的交互逻辑的部分逻辑,并不是所有的交互逻辑都适合用 Command 来分担的,还有一部分交互逻辑是需要交给 System 层来分担。这里 System 层在概念等价于游戏的各个管理类 Manager。
Command 是没有状态的,有没有状态我们可以理解为这个对象需不需要维护数据,因为 Command 类似于是一个方法,只要调用然后执行一次就可以不用了,所以 Command 是没有状态的

梳理一下当前的架构

  • 表现层:即 ViewController 或者 MonoBehaviour 脚本等,负责接受用户的输入,当状态变化时更新表现
  • System 层:系统层,有状态,在多个表现层共享的逻辑,负责即提供 API 又有状态的对象,比如网络服务、蓝牙服务、商城系统等,也支持分数统计、成就系统这种硬编码比较多又需要把代码放在一个位置的需求。
  • Model 层:管理数据,有状态,提供数据的增删改查。
  • Utility 层:工具层,无状态,提供一些必备的基础工具,比如数据存储、网络链接、蓝牙、序列化反序列化等。

表现层改变 System、Model 层级的状态用 Command,System 层 和 Model 层 通知 表现层用事件,委托或 BindableProeprty,表现层查询状态时可以直接获取 System 和 Model 层

每个层级都有一些规则:

表现层

  • 可以获取 System
  • 可以获取 Model
  • 可以发送 Command
  • 可以监听 Event

系统层

  • 可以获取其他 System
  • 可以获取 Model
  • 可以监听,发送 Event
  • 可以获取 Utility

数据层

  • 可以获取 Utility
  • 可以发送 Event

工具层

  • 啥都干不了,可以集成第三方库,或者封装 API

除了四个层级,还有一个核心概念就是 Command

Command

  • 可以获取 System
  • 可以获取 Model
  • 可以获取 Utility
  • 可以发送 Event
  • 可以发送其他 Command

贫血模型和充血模型

在这里插入图片描述
我们有一个 User 对象,伪代码如下

public class User
{public string Name {get;set;}public int Age {get;set;}public string Id {get;set;}public string NickName {get;set;public float Weight {get;set;}
}

总共有五个属性,但是在表现层的界面中,只需要显示三个属性,即:姓名 Name、年龄 Age、和 Id。
表现层查询一个用户数据的时候,返回了一个 完整的 User 对象,这种数据流向模型叫做贫血模型。就是表现层需要用到的数据结果给了一整个未经过筛选的数据对象过来。

在这里插入图片描述
定义了一个 UserInfo 类,伪代码如下;

public class UserInfo
{public string Name {get;set;}public int Age {get;set;}public string Id {get;set;}
}

充血模型就是表现层需要哪些数据,就刚好返回哪些数据

充血模型 比 贫血模型 需要做跟多的工作,写更多的代码,甚至还有跟多的性能消耗。
但是在越大规模的项目中 充血模型 的好处就会更加明显。因为充血模型,可以让我们的代码更精确地描述业务,会提高代码的可读性,而贫血模型,会让我们的数据逐渐趋于混乱。

参考

凉鞋 《框架搭建 决定版》

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

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

相关文章

RH850P1X芯片学习笔记-Generic Timer Module -ATOM

文章目录 ARU-connected Timer Output Module (ATOM)OverviewGLOBAL CHANNEL CONTROL BLOCK ATOM Channel architectureATOM Channel modesSOMP-Signal Output Mode PWMSOMP - ARUSOMC-Signal Output Mode CompareSOMC - ARUSOMC – COMPARE COMMANDSOMC – OUTPUT ACTIONATOM …

计算机设计大赛 深度学习实现行人重识别 - python opencv yolo Reid

文章目录 0 前言1 课题背景2 效果展示3 行人检测4 行人重识别5 其他工具6 最后 0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; **基于深度学习的行人重识别算法研究与实现 ** 该项目较为新颖&#xff0c;适合作为竞赛课题方向&#xff0c…

YOLOv5-Openvino和ONNXRuntime推理【CPU】

1 环境&#xff1a; CPU&#xff1a;i5-12500 Python&#xff1a;3.8.18 2 安装Openvino和ONNXRuntime 2.1 Openvino简介 Openvino是由Intel开发的专门用于优化和部署人工智能推理的半开源的工具包&#xff0c;主要用于对深度推理做优化。 Openvino内部集成了Opencv、Tens…

接口自动化测试实战经验分享,测试用例也能自动生成

作为测试&#xff0c;你可能会对以下场景感到似曾相识&#xff1a;开发改好的 BUG 反复横跳&#xff1b;版本兼容逻辑多&#xff0c;修复一个 BUG 触发了更多 BUG&#xff1b;上线时系统监控毫无异常&#xff0c;过段时间用户投诉某个页面无数据&#xff1b;改动祖传代码时如履…

靶机渗透之ConnectTheDots

对于vulnhub中的靶机&#xff0c;我们都需先下载镜像&#xff0c;然后导入VM&#xff0c;并将网络连接改为NAT模式。首先我们再来看一下靶机渗透的步骤&#xff1a;信息收集-漏洞分析-漏洞利用-提权。基本都是这个三个步骤&#xff0c;接下来开始我们今天的靶机渗透吧&#xff…

Go字符串实战操作大全!

目录 1. 引言文章结构概览 2. Go字符串基础字符串的定义与特性什么是字符串&#xff1f;Go字符串的不可变性原则 字符串的数据结构Go字符串的内部表达byte和rune的简介 3. 字符串操作与应用3.1 操作与应用字符串连接字符串切片字符串查找字符串比较字符串的替换字符串的大小写转…

计算机专业必看的十部电影

计算机专业必看的十部电影 1. 人工智能2. 黑客帝国3. 盗梦空间4. 社交网络5. Her6. 模仿游戏7. 斯诺登8. 头号玩家9. 暗网10. 网络迷踪 计算机专业必看的十部电影&#xff0c;就像一场精彩盛宴&#xff01; 《黑客帝国》让你穿越虚拟世界&#xff0c;感受高科技的魅力《模仿游戏…

(k8s中)docker netty OOM问题记录

1、首先查看docker的内存占用情况&#xff1a; docker top 容器名 -u 查看内存cpu占用率&#xff08;容器名来自kubectl describe pod xxx或者docker ps&#xff09; 可以看出内存一直增长&#xff0c;作为IO代理这是不正常的。 2、修改启动参数和配置文件 需要注意的是为了…

30天JS挑战(第十五天)------本地存储菜谱

第十五天挑战(本地存储菜谱) 地址&#xff1a;https://javascript30.com/ 所有内容均上传至gitee&#xff0c;答案不唯一&#xff0c;仅代表本人思路 中文详解&#xff1a;https://github.com/soyaine/JavaScript30 该详解是Soyaine及其团队整理编撰的&#xff0c;是对源代…

Java---文件,流✨❤️

文章目录 1.遍历文件夹2.遍历子文件夹3.练习流4.以字节流的形式读取文件内容5.以字节流的形式向文件写入数据顶折纠问6 .写入数据到文件 1.遍历文件夹 一般说来操作系统都会安装在C盘&#xff0c;所以会有一个 C:\WINDOWS目录。 遍历这个目录下所有的文件(不用遍历子目录) 找出…

【数仓】Hadoop软件安装及使用(集群配置)

一、环境准备 1、准备3台虚拟机 Hadoop131&#xff1a;192.168.56.131Hadoop132&#xff1a;192.168.56.132Hadoop133&#xff1a;192.168.56.133 本例系统版本 CentOS-7.8&#xff0c;已安装jdk1.8 2、hosts配置&#xff0c;关闭防火墙 vi /etc/hosts添加如下内容&#x…

#WEB前端(浮动与定位)

1.实验&#xff1a; 2.IDE&#xff1a;VSCODE 3.记录&#xff1a; float、position 没有应用浮动前 应用左浮动和右浮动后 应用定位 4.代码&#xff1a; <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><me…

AIGC下一步:如何用AI再度重构或优化媒体处理?

让媒资中“沉默的大多数”再次焕发光彩。 邹娟&#xff5c;演讲者 编者按 AIGC时代下&#xff0c;媒体内容生产领域随着AI的出现也涌现出更多的变化与挑战。面对AI的巨大冲击&#xff0c;如何优化或重构媒体内容生产技术架构&#xff1f;在多样的应用场景中媒体内容生产技术又…

【EAI 027】Learning Interactive Real-World Simulators

Paper Card 论文标题&#xff1a;Learning Interactive Real-World Simulators 论文作者&#xff1a;Mengjiao Yang, Yilun Du, Kamyar Ghasemipour, Jonathan Tompson, Leslie Kaelbling, Dale Schuurmans, Pieter Abbeel 作者单位&#xff1a;UC Berkeley, Google DeepMind, …

获取 Windows 通知中心弹窗通知内容(含工具汉化)

目录 前言 技术原理概述 测试代码和程序下载连接 本文出处链接&#xff1a;https://blog.csdn.net/qq_59075481/article/details/136440280。 前言 从 Windows 8.1 开始&#xff0c;Windows 通知现在以 Toast 而非 Balloon 形式显示&#xff08; Bollon 通知其实现在是应用…

中小型水库安全监测运营解决方案,筑牢水库安全防线

我国水库大坝具有“六多”的特点。第一&#xff0c;总量多。我国现有水库9.8万座&#xff0c;是世界上水库大坝最多的国家。第二&#xff0c;小水库多。我国现有水库中95%的水库是小型水库。第三&#xff0c;病险水库多。 目前&#xff0c;在我国水库管理中&#xff0c;部分地方…

异常网络下TCP的可靠服务机制(慢启动、拥塞避免、快重传、快恢复)

目录 TCP超时重传拥塞控制概述慢启动和拥塞避免下面讲解发送端如何判断拥塞发生。 快速重传和快速恢复 本文描述TCP在异常网络下的处理方式 以保证其可靠的数据传输的服务 TCP超时重传 tcp服务能够重传其超时时间内没有收到确认的TCP报文段&#xff0c;tcp模块为每一个报文段都…

认识通讯协议——TCP/IP、UDP协议的区别,HTTP通讯协议的理解

目录 引出认识通讯协议1、TCP/IP协议&#xff0c;UDP协议的区别2、HTTP通讯协议的讲解 Redis冲冲冲——缓存三兄弟&#xff1a;缓存击穿、穿透、雪崩缓存击穿缓存穿透缓存雪崩 总结 引出 认识通讯协议——TCP/IP、UDP协议的区别&#xff0c;HTTP通讯协议的理解 认识通讯协议 …

【脑科学相关合集】有关脑影像数据相关介绍的笔记及有关脑网络的笔记合集

【脑科学相关合集】有关脑影像数据相关介绍的笔记及有关脑网络的笔记合集 前言脑模板方面相关笔记清单 基于脑网络的方法方面数据基本方面 前言 这里&#xff0c;我将展开有关我自己关于脑影像数据相关介绍的笔记及有关脑网络的笔记合集。其中&#xff0c;脑网络的相关论文主要…

分享:大数据信用报告查询的价格一般要多少钱?

现在很多人都开始了解自己的大数据信用了&#xff0c;纷纷去查大数据信用报告&#xff0c;由于大数据信用与人行征信有本质的区别&#xff0c;查询方式和价格都不是固定的&#xff0c;本文就为大家详细讲讲大数据信用报告查询的价格一般要多少钱&#xff0c;希望对你有帮助。 大…