手把手教你实现Unity网络同步

2023-05-16

现如今,网络同步的技术在各种游戏里被广泛应用和发展,那么,如何在Unity中搭建网络模块?如何使服务器和客户端之间通信?如何做到网络同步?本文作者烂笔头-27将从自身经验出发,为大家一一解答这些疑问。

一、在Unity中搭建网络模块

1.在Unity中配置网络

虽然在Unity中有个自带的挂在GameObject上的Network组件,但这篇文章为了讲解清楚,就不介绍这些组件了,我们使用C#代码来搭建网络模块,Unity网络编程相关需要引入UnityEngine.Networking 命名空间。

1.1 启动服务端

以下是创建一个Server的代码:
 



仅仅几行代码,服务端的启动功能就完成了,成功启动的话,会返回true,如果启动失败,可以查看一下端口是否被占用了。此外,服务端还有一些配置相关的东西,在此就不做细讲了。

1.2 启动客户端

接下来,创建一个客户端,其实跟Server的很类似,代码如下:
 



客户端调用Connect方法向服务器发送连接请求,当连接成功时,服务端会回调OnClientConnected方法,客户端会回调OnConnectedServer方法。

需要注意的是,当连接成功时,需要把NetworkMessage中的NetworkConnection保存起来,这个对象是 客户端与服务端的连接的封装,后续接收消息包/发送消息包需要用到。
 



1.3 效果图

开启两个Unity,一个作为服务端,另一个作为客户端。制作两个按钮,一个启动服务端,另一个启动客户端。
 



首先启动服务端,然后再启动客户端。服务端打印如下:
 

 



客户端打印如下:
 



就这样,很轻松的在Unity中搭建了网络模块。

二、服务端与客户端之间通信

介绍了如何在Unity中启动服务端和客户端以及客户端连接服务端的方法之后,接下来,将通过自定义消息类型,实现服务端与客户端之间的通信。

1.自定义消息类型(CustomMsgTypes)

其实在上一章中,已经知道如何注册消息回调了,就是使用RegisterHandler方法,这个方法的第一个参数short类型的变量,第二个参数是NetworkMessageDelegate类型的委托,如果消息注册方法,我们就可以自定义MsgType,来处理游戏内的逻辑。代码如下:
 



这个消息的定义,需要注意UnityEngine.Networking下内置的MsgType已经占用了一些数值,避免与Unity自带的MsgType重复。然后把CustomMsgTypes注册到网络组件的消息回调:
 



2.定义自定义消息

已经定义了消息类型,接下来,要定义这个消息类型对应的消息内容,在UnityEngine.Networking组件下,定义的Message要继承自MessageBase,基类有两个方法 Deserialize 和 Serialize 需要重写,也就是消息的序列化和反序列化。代码如下:
 



需要注意的是 Serialize 方法是msg在发送的时候自动调用的,而Deserialize方法需要在收到消息包的时候主动调用:
 



3. 发送自定义消息

好了,现在自定义消息的类型与消息内容都有了,现在我们只需要在客户端往消息里填充数据,执行NetworkConnection.Send方法就可以发送消息了。
 



在服务端,就可以接受到消息,通过Deserialize可以获取到消息内容。
 



4.小结

通过上述的例子,实现 服务端注册自定义消息 => 客户端构造消息并发送 => 服务端接收解析获取数据,如果想要把服务端的消息发到客户端,也是这个流程。

三、网络消息包的封装(Packet)

1.原因

因为使用UnityEngine.Networking,发送或接受的消息都要继承自MessageBase,而MessageBase的读写操作,使用的是(读)NetworkReader,(写)NetworkWriter,查看文档会发现,写入操作都是直接按照基本数据类型所占字节长度来整个写入的,读取也是如此。比如:一个int类型,占4个字节,但是如果这个int变量是10的话,二进制表示为1010,只需要写入4个位(半个字节)就行了,为了节约网络消息包的大小(毕竟带宽有限~),有的时候完全没必要写入全部的字节长度。所以需要对网络消息包进行一下封装,实现按照自己的需要读写指定长度网络数据。

2.思路

计算机系统中一切数据的本质都是0和1,一个0或1表示1个位(bit),每8个位表示1个字节(byte),基本的数据类型所占位数:

>bool = 1位
float = 32位
double = 64位
short, ushort = 16位
int, uint = 32位
long, ulong = 64位
string = 1个ASCII字符占8位,中文字符占16位

另外还涉及到浮点数(科学计数法)与负数(最高位为1)0的二进制表示形式,网上有很多讲解,在此不再赘述。

既然要实现按己所需读写指定长度的数据,那么就需要一个类似游标(指针)的变量来标记整个数据包写到哪了,数据包读到哪了。

3.编码

Packet类
 



每个数组类型的写入其实都可以当成是将一个字节按照指定位数从某个位置开始写。
 



有了这个最基本的实现方法,那么其他的基本类型都很好写了:
 

 



其他类型的写入方式大体相似,就不一一举例了。对应的读取方式,最根本的方法:
 



与写入方式类似,其他基本类型的读取,就将需要读的位数传入,拿到返回的字节以后,组合成对应的数据类型即可。

4.结语

这样,对网络消息包的封装基本就完成了,使用这个类,可以更灵活的构造网络传输中的数据,包括数据压缩、解压,检测数据溢出,截断数据流等等,都可以很方便了。

四、如何实现确定性的网络同步

1.服务端与客户端相同频率模拟(Simulate)

在Unity中,有三个更新方法:Update、LateUpdate、FixedUpdate。

Update和LateUpdate属于渲染帧,它们每帧间隔的时间会受到渲染物体的时间影响(LateUpdate是在所有的Update方法执行完后再执行),打个比方说:相同的游戏,在性能好的机器上可以跑60帧每秒,但是在差的机器上,可能只能跑30帧每秒。两者相差了1倍,甚至更多。

FixedUpdate是固定频率更新,常常用来处理Unity中物理相关的东西,它不受渲染效率的影响,以固定的时间间隔调用。

所以为了保证服务端和客户端的模拟频率一致,那么在Unity中,就选用FixedUpdate方法。在Unity中可以在Edit-&gtroject Setting->time中找到Fixed timestep进行修改,也可以在代码中设置Time.fixedDeltaTime的值。
 



这样,服务器和客户端的FixedUpdate方法都会按照相同的频率调用,然后把操作的模拟(Simulate)放在里面执行。

2.相同的状态 + 相同的操作指令 = 相同的新状态

为了让服务端和客户端模拟的结果相同,首先必须保证服务端和客户端的模拟逻辑代码一致,尽量减少使用默认的物理模拟(引擎的物理模拟有些会带有随机数,一旦服务器和客户端的随机数不一致,会导致结果不一致),先来定义操作指令类(Command)。
 



Simulate方法需要做的应该就是收集操作指令CommandInput,然后执行,得到CommandResult。
 



CollectCommandInput和ExecuteCommand方法中,客户端和服务端的代码应该是一致的。

服务器和客户端,执行完Command以后,填充result需要的数据。这样,一个Command就完成了,经过网络同步以后,利用sequence(指令序号)来对比操作的结果是否一致。

操作结果如果:
 



3.小结

有了这个基本的Command的结构和相同频率的Simulate,后续就要考虑服务端和客户端如何去同步这些Command。

五、服务器将状态同步给客户端

在上一章中,已经可以在服务器上直接根据服务器自己的操作指令,模拟得出结果,修改球的位置了。接下来,将要考虑如何将服务器模拟的位置如何同步到客户端。

1.服务器向客户端发送单位实体(Entity)状态

首先需要设定一个发包的频率(SendRate),目前设置的是每10个模拟帧发送一次,对于60模拟帧每秒的游戏世界来说,这也相当于6个包每秒。这个包的数据应该是描述Entity在当前模拟帧的状态。
 



发送的方法:
 



这样就把Entity的状态打包发向所有的客户端了。

2.客户端接收到服务端的状态包

客户端接收到服务端的数据包,然后从卖二手手机数据包中拿到描述Entity状态的数据后,需要考虑的是,如果是第一个状态,可以直接拿来应用到Entity上,如果不是第一个状态的话,那就不能直接应用,因为网络传输抖动的因素,服务端虽然是每隔10帧发一个包,但是客户端收包频率不一定是每隔10帧就收到的,如果直接应用的话,必然会导致抖动。这个时候,我们就需要在客户端对服务器端进行状态缓存(StateBuffer)和状态插值(StateInterpolation)。

为什么需要状态缓存和状态插值?

客户端收到的状态包都是带帧号(Frame),帧号表示了这个状态是服务器在那帧模拟得到的状态,客户端想要,去除抖动,平滑的渡过的状态之间的时间的话,就需要在State_A与State_B进行插值计算,插值计算的公式应该是这样:

> Current = MathUtils.Interpolate(State_A, State_B, ???? / (State_B.frame -State_A.frame ))

在公式右侧,除了????,其他都是已知的,想要得到插值结果,那么????应该是什么呢?

因为分母的两个状态的帧号差,所以分子应该也是帧号才对,客户端的帧号跟服务端帧号不一致(因为服务器肯定早就启动了,客户端是后来才连接服务器的),这个时候就要新增一个变量用来表示客户端估算出来的服务器帧(RemoteEstimatedFrame)。

这个估算帧用来表示客户端在本地估测服务器模拟的帧号,它的第一次赋值应该是客户端收到服务器的帧号时。



估算帧也是按照模拟频率一直累加的,但是估算不一定总是准的,有时提前收到包,有时延迟收到包,甚至丢包。所以如果收到的包帧号跟估算帧相差太大的时候,就需要对估算帧重新调整。
 



效果如下:
 



从这个图可以看出,服务器移动很平滑,但是客户端移动可以明显看出抖动的情况,问题在哪呢?其实问题是出在估算帧的设置问题,从状态A插值到状态B的过程,由于估算帧等于(或者接近)状态A的帧号,而状态B的包客户端还没有收到,这就造成了在状态B到来之前,客户端没办法插值,只好原地等待,当状态B的包到来的时候。立即设置了位置,所以造成了抖动,那么如何解决这个问题呢?

做法是故意让估算帧的帧号在实际的状态包帧号之前,让客户端滞后:
 



将delay = 10(因为服务器每10帧发个包)这样尽可能的预留出一个状态包用来做插值计算了,看看效果:
 



可以看到客户端的抖动几乎看不出来了,但是代价是延迟比较大了(为了更好的表现,这个牺牲是必要的)。

3.小结

服务端模拟结果,下发状态给客户端基本就完成了,需要补充的是,在估算帧的计算中,可以根据估算帧和实际帧的差距动态的调整本地模拟的频率,比如:

>如果估算帧滞后太多了,那客户端就每帧加2,甚至加3(默认是每个模拟帧加1)来追赶。

如果估算帧超前很多,那客户端就估算帧的累加可以暂停来等待,通过这样的方式来缓和。

现在客户端通过插值,实现了比较平滑的表现,但是有比较明显的延迟,这个可以通过加大发包的频率来缓解这个问题。

后续实现了客户端的预表现后,这个问题也就不那么重要了。

六、客户端本地预表现

在上章已经介绍完在服务端控制的物体通过把状态发到客户端,客户端去”追赶”服务器的状态来实现同步的,现在来谈谈如何在客户端做本地预表现。

1.什么要本地预表现?为什么要本地预表现?

本地预表现(本地预测),就是玩家操作游戏角色时,按下按键立刻得到操作的反馈。

有些竞技游戏尤其FPS游戏,讲究及时的操作响应性,试想,如果没有本地预表现,那么玩家按下一个按键想要释放技能,却要等待服务器的回包之后才释放得出来,由于网络波动延迟的影响,回包的时间还不确定,如果延迟很低的话可能还可以接受,对于延迟很高的玩家就比较难受了。为了解决这样的用户体验,最好是能实现客户端的本地预表现。

2.客户端生成操作指令并且本地模拟,向服务器发送操作指令。

对于需要本地预表现的单位来说,当它得到了操作输入指令(CommandInput)的时候,应该立即把这个指令拿去执行,而不需要等服务器的回包。
 



这样客户端就是一直获取操作输入,然后执行操作指令,然后就需要把操作指令上传到服务端,客户端发包应该也有一个发包频率(ClientSendRate),因为客户端只跟服务器通信,所以它可以比服务器的发包频率快。

因为本地的模拟频率是60帧/秒,相当于每秒产生了60个Command,客户端需要按ClientSendRate把指令上传到服务端,所以需要把Command缓存进队列。
 



客户端执行过的操作指令都缓存在队列里,然后就要队列指令都发送给服务端了。
 



3.服务器接收到客户端的操作指令并且逐帧模拟,向客户端发送模拟结果。
 



服务器拿到客户端的操作输入之后,接下来就要为客户端模拟输入指令。
 



服务器把客户端的指令模拟完了以后,模拟的结果还是缓存在commandQueue中的(因为Command类包含了Input和Result),那么在服务器向客户端发包的时候,就需要把Result给发送到客户端了。
 



4.客户端与服务端发来的模拟结果对比
 



终于到这里了,因为客户端也维护了一个指令队列(commandQueue),它包含了客户端本地预表现的所有执行过的指令输入和结果,当客户端收到了服务器下发的指令结果以后,就可以本地模拟的结果和服务器模拟的结果做对比。在如何实现确定性的网络同步中,定义的Command类中是有个sequence变量来表示指令序号的。
 



那么现在,客户端的指令队列(commandQueue)中包含了很多指令,因为上一章服务器将状态同步给客户端说明了,服务器会将状态发给客户端,对于本地模拟的客户端来说,收到的状态包可以直接设置。这里就会出现一个问题了,如果直接设置的话,因为客户端本地预表现了,收到的状态是旧的。直接设置不就造成抖动了吗?所以解决的办法就是客户端在一帧把之前所有的执行过的指令(除了服务器验证过的)重新执行一遍。

一旦从服务器回包发现预测失败,我们把你的全部输入都重播一遍直至追上当前时刻。

当客户端收到描述角色状态的数据包时,我们基本上就得把移动状态及时恢复到最近一次经过服务器验证过状态上去,而且必须重新计算之后所有的输入操作,直至追上当前时刻。
 



对于预表现的客户端,需要在模拟之前OnSimulateBefore()的时候直接应用服务器下发的状态,每个模拟帧,客户端都把本地已经执行过而且没有被服务确认过的指令都执行一遍,然后再生成新的指令。如此,预表现的实现就基本完成了。
 



总的流程应该是这样:


 



5.小结

对于客户端的预表现,核心在于要遵循确定性的原则,一个状态 + N个指令 = 新的状态,客户端跟服务器的模拟结果应该是一致的,这样就能保持稳定的同步。

对于丢包导致的预测失败,需要在客户端做丢包重发的机制,而服务器也可以适当的从之前的指令来推测客户端操作来模拟,以缓和丢包的情况。

七、物理状态的网络同步

最近看“GDC2018演讲《火箭联盟》的物理与网络细节(需要科学上网)”,在《Rocket League》(以下简称RT)中,汽车和足球用的都是真实物理模拟,开发的引擎是Unreal Engine 3,使用的物理引擎是Bullet,在游戏中,客户端操控的东西都是带预测的,包括汽车的运动、球的运动,那么如何在Unity实现物理状态的网络同步呢?接下来就尝试着实现一下。

1.根据操作输入,改变物理状态

在场景中创建一个Cube,然后挂上Rigidbody组件,这样就拥有一个很简单的带物理的物体了。

接收按键输入W、S、A、D,按下一个键就对Cube施加一个力。代码如下:
 



通过对刚体施加力,基本的物理操作就完成了。

2.Unity下的确定性物理模拟

在Unity中,内置的物理引擎是PhysX,依据确定性原则,首先得了解PhysX是确定性的吗?可以预测吗?我在网上找了很多文章,也没找到一个确定的结果,有的说法说PhysX是确定性的,测试过很多次的结果都一致(文章Deterministic physics options),又有的说法说PhysX为了实现高效,牺牲了确定性,在不同平台下可能导致物理模拟结果不同等等,反正还没有得到确定的答案(毕竟PhysX的物理组件在Unity中没有开源,只有访问接口)。

Any way,先暂且把Unity中的物理模拟是确定性的(如果认为它是不确定的,那这篇就没必要写了),要实现物理的预测,需要自己手动调用物理模拟,Unity提供了一个API-Physics.Simulate,通过将来Physics.autoSimulation = false关闭Unity内部的自动物理模拟,然后手动执行Physics.Simulate(fixedDeltaTime)来模拟一次物理.在调用结束后,就可以拿到模拟的结果了。
 



3.服务器将物理状态同步给客户端

在第五章中,物体的状态只同步了position和rotation,现在把rigidbody的velocity和angularVelocity加上。
 



按照第五章的流程,服务器将状态同步给客户端的表现如下(6packet/second ):
 



4.客户端上传操作给服务端,同时预测物理模拟。

跟第六章的流程一样,因为客户端调用了手动物理模拟Physics.Simulate(Time.fixedDeltaTime),每次收到服务器下发新的状态后,预测的客户端都重置状态,然后把缓存的指令执行一遍,同时调用Physics.Simulate(Time.fixedDeltaTime)。客户端预测的表现如下(6packet/second ):
 



5.小结

从上面的结果来看,Unity中物理模拟在网络同步中都用挺不错的表现,但是,其实还是存在问题的:

>手动物理模拟方法Physics.Simulate()是全局的,这就意味着调用一次,游戏内所有的物理物体都会模拟一次,没办法仅对单个物体进行模拟,想要改进的话,只有自己在代码逻辑中对物体的状态做备份。

如果涉及到游戏内很多的物体,频繁的单帧调用多次Physics.Simulate()尚不明确会带来多严重的效率问题。

所以如果真的想在Unity中实现物理模拟的网络同步,建议最好能够使用自己可控的,确定性的物理模拟,比如说自己实现的简单物理模拟逻辑,或者使用插件Bullet Physics For Unity(独立于Unity之外,开放源代码,确定性的物理引擎)等等。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

手把手教你实现Unity网络同步 的相关文章

  • 大端序发送数据

    需要发送数字 151510 大端序发送就是 xff1a 00 02 4F D6 string smallData 61 textBox1 Text 小端数据 if smallData 61 61 34 34 MessageBox Show
  • DSP28335笔记--SCI篇

    采用FIFO来实现数据的发送与接收 xff0c 一般就是指采用FIFO中断 在标准SCI模式下通过中断方式来接收或者发送数据可以发现 xff0c 每接收或者发送一个字符就要进一次中断 xff0c 如果发送的字符比较多的话 xff0c 很明显
  • 无人机实验笔记(2019电赛)

    刚看到题目的时候自然想到的是巡空中电缆线 思路 xff1a 用一个摄像头架高在无人机上 xff0c 与地理坐标Z成一定角度 如图 xff0c 无人机看到的电缆线是实际电缆线在地面上的投影 xff0c 而投影线在 无人机视野的位置 和 无人机
  • [CMake教程](四)CMake 配置生成lib或者so的库文件

    CMake教程 xff08 四 xff09 CMake 配置生成lib或者so的库文件 xff08 1 xff09 系列教程介绍 上面几个教程我们的程序都是生成可执行文件 但是我们在合作开发算法的时候经常需要交付的是一个模块 xff0c 该
  • SLAM问题汇总

    Issue dropped 100 00 of messages so far Resolve tf transform wrong for me change scan to robot1 scan to resolve https an
  • STL基础4:STL7个常用容器的比较

    1 STL容器分类 xff1a STL的容器可以分为以下几个大类 一 顺序 xff08 序列 xff09 容器 xff0c 有vector list deque string stack 适配器类 queue 适配器类 priority q
  • PCB布局技巧

    1 布局前丝印放器件中间 结果 xff1a 布局布线之前 xff0c 把标号位置批量修改到器件中心 丝印既不会阻挡视线 也可以分辨出丝印对应的元件 问题描述 xff1a 在PCB布局时候 xff0c 我们会发现 xff0c 刚导入PCB的元
  • 第四次游戏革命:全息游戏

    最近一个月 xff0c 把国内外十数款单机 网游大作横扫一遍 xff0c 感慨颇多 国内游戏 xff0c 抄袭遍地 xff0c 十足的坑爹 xff0c 浪费青春 反观国外 xff0c 韩国网游经典而耐玩 xff0c C9 43 洛奇英雄传
  • UART, IIC, SCI, SPI, 232, 485, 422, CAN, SDIO, GPIO, MODBUS, TCP/IP汇总简介

    UART IIC SCI SPI 232 485 422 CAN SDIO GPIO MODBUS TCP IP汇总简介 UART xff1a Universal Asynchronous Receiver Transmitter xff1
  • 二维数组的定义

    1 概念 二维数组就是一种数组的数组 xff0c 其本质上还是一个一维数组 xff0c 只是它的数据元素又是一个一维数组 如果你对这个概念想象不出来 xff0c 给大家举个栗子 xff0c 相信吸烟的同学一下子就会明白 一根烟 61 一个变
  • nginx不转发http header问题解决

    文章整理自网络 作者 64 loongshawn xff1a http blog csdn net loongshawn article details 78199977 xff0c 建议读者阅读原文 xff0c 确保获得完整的信息 1 问
  • SimpleFOC(二)—— 快速入门 (开环控制)

    目录 一 硬件介绍 1 驱动板版本说明 2 驱动板跳线 3 硬件准备 4 硬件连接 二 软件操作 1 安装Arduino IDE 2 安装SimpleFOC library 3 打开示例程序 三 电机控制 1 程序下载 2 开环速度控制 3
  • curl参数详解

    原文 xff1a http blog csdn net yanhui wei article details 21530811 cURL可以使用URL的语法模拟浏览器来传输数据 xff0c 因为它是模拟浏览器 xff0c 因此它同样支持多种
  • 嵌入式单片机基础篇(十八)之ILI9341 液晶控制器

    ILI9341 液晶控制器详解 1 ILI9341 液晶控制器简介 xff1a ILI9341 液晶控制器自带显存 xff0c 其显存总大小为 172800 xff08 24032018 8 xff09 xff0c 即 18 位模式 xff
  • LOTO 示波器软件功能演示——RS232串口解码

    LOTO 示波器软件功能演示 RS232串口解码 我们今天演示一下怎么用LOTO示波器对串口进行解码 xff0c 使用了一个USB转串口的设备 xff0c 来产生串口数据 xff0c 用OSCA02 LOTO示波器演示 示波器我们只需要建立
  • ROS 下实现相机图像采集与图像传输到服务器,socket图传

    前言 本文介绍一种Qt下进行ROS开发的完美方案 xff0c 同时给出一个使用TCPROS进行图像传输的一个例子 xff0c 使用的是ros industrial的Levi Armstrong在2015年12月开发的一个Qt插件ros qt
  • Digest来验证

    Apache默认使用basic模块验证 xff0c 但它只是明文验证 Digest验证 xff0c 是用md5摘要值进行对比 httpRequest getAuthType 方法 xff0c 可以得到网页的验证方式request BASIC
  • 使用vscode创建C++工程

    1 推荐文件目录 即一个C 43 43 工程文件中包含 vscode文件夹 build文件夹 include文件夹 src文件夹以及一个CMakeLists txt文件 2 vscode文件夹 vscode文件夹一般应该包含三个配置文件 x
  • 游戏中所存在的“真随机”与“伪随机”

    写这篇随笔的动机 xff0c 在于最近看了不少对于游戏中概率事件的提问 在这些相关讨论里 xff0c 总是能频繁看到 真随机 和 伪随机 这两个词汇 其中最常见的句子则莫过于宝典一般的 程序里没有真随机 这句话本身当然是没有问题的 但是大多
  • C语言socket编程----实现TCP通信

    TCP IP协议 xff08 Transmission Control Protocol Internet Protocol xff09 叫做传输控制 网际协议 xff0c 又叫网络通信协议 实际上 xff0c 它包含上百个功能的协议 xf

随机推荐

  • rtthread移植实现uorb

    uORB Micro Object Request Broker 微对象请求代理器 是PX4 Pixhawk系统中非常重要且关键的一个模块 xff0c 它肩负了整个系统的数据传输任务 xff0c 所有的传感器数据 GPS PPM信号等都要从
  • 环形缓冲区(ringbuffer)

    环形缓冲区 xff08 ringbuffer xff09 环形缓冲区是嵌入式系统中十分重要的一种数据结构 xff0c 比如在串口处理中 xff0c 串口中断接收数据直接往环形缓冲区丢数据 xff0c 而应用可以从环形缓冲区取数据进行处理 x
  • 大屏可视化数据面板分格渐变进度条、数字翻牌器及其刷新动效实现

    数据可视化大屏是当前可视化领域的一项热门应用 xff0c 通常可以分为信息展示类 数据分析类及监控预警类 这类应用的视觉设计通常效果炫酷 xff0c 动效丰富 xff0c 有时候一些页面布局和动画实现会对前端人员有一定的挑战性 xff0c
  • Fiddler系列教程1:初识Http协议抓包工具

    1 Fiddler简介 Fiddler是用一款使用C 编写的http协议调试代理工具 它支持众多的http调试任务 xff0c 能够记录并检查所有你的电脑和互联网之间的http通讯 xff0c 可以设置断点 xff0c 查看所有的 进出 F
  • http 认证 basic 和 digest

    HTTP协议 RFC2616 的两种认证机制 Basic和Digest SIP类似Http协议 其认证模式也一样 Http协议 xff08 RFC 2616 xff09 规定可以采用Basic模式和摘要模式 xff08 Digest sch
  • 优秀的C/C++框架和库整理,值得收藏

    1 ACE 庞大 复杂 xff0c 适合大型项目 开源 免费 xff0c 不依赖第三方库 xff0c 支持跨平台 http www cs wustl edu schmidt ACE html 2 Asio Asio基于Boost开发的异步I
  • linux socket select(tcp)

    1 概述 xff1a 该demo主要实现了linux下通过select tcp 方式的socket并发通讯 xff0c 相关接口介绍可以参考 lt lt UNIX环境高级编程 gt gt 2 场景 服务端 一 lt 多 客户端 xff1a
  • 对于char*、char[]和string的转换

    1 char char 可以直接转为string 直接赋值 2 string转为char c str 会返回一个指向c字符串类型的指针 xff0c 所以 const char ch 61 str c str 也可以用str data C 4
  • 十分详细的数码管电子时钟(基于51单片机)

    数码管由于内部由多段LED灯构成 xff0c 也被称为多段式LED数码管 从数码管里面包含的LED个数来分 xff0c 可以分为七段式 八段式 十四段式等 七段式数码管 xff1a 八段式数码管 xff08 比七段式右下角多了一个小点 xf
  • 如何用Unity3D实现游戏中的角色换装?

    换装系统是游戏中较为常见的功能 xff0c 我们给它一个专业词avatar xff0c 可以做到装备与人物分离 xff0c 实现自由换装效果 我们可以将头部 身体 手 脚 武器独立建模 贴图 xff0c 利用avatar来动态换装 xff0
  • stm32学习(3)——NVIC中断优先级分组

    相信大多数铁汁在学习stm32的时候都了解过51单片机的基本内容 xff0c 对于51单片机来说 xff0c 中断就那么几个 xff1a 外部中断0定时器 计数器0中断外部中断1定时器 计数器1中断串口中断 它们在51单片机中的优先级也是按
  • STM32F407系统时钟配置不准确导致串口发送数据乱码、定时器定时不准问题

    前言 在用原子的F407探索者开发板时 xff0c 由于是用的野火的工程模板 xff0c 导致了一些串口发送乱码 定时器定时不准的问题 如果你也有类似的问题 xff0c 这个或许可以帮到你 原因 SYSCLK 系统时钟来源有三个方面 xff
  • 巡线PID算法

    相信很多电子专业的同学都做过循迹小车这个小玩意儿 xff0c 而在我们刚刚接触巡线的时候都是用的两个循迹模块 xff08 如下图 xff09 左边的模块检测到黑线了就说明我车子的方向偏右了就需要往左转 xff0c 同理 xff0c 右边检测
  • MSP432(Keil5)——9.ADC驱动

    本次例程驱动了板载的ADC来读取一个模拟的角度传感器 xff0c 大家可以在ADC读取中断里面换成其他的计算 xff0c 具体引脚见程序代码 adc c span class token macro property span class
  • C语言宏函数妙用——1

    span class token macro property span class token directive hash span span class token directive keyword include span spa
  • union和struct的区别

    union只配置一个空间来放置共用体中内存最大的数据 xff0c 而结构体则给其中每个变量内存空间 union常用来压缩数据空间 xff0c 其中两个变量不能同时使用时用union
  • 二极管基础知识

  • Java学习之多线程复制文件

    1 一个线程复制一个文件 import java io FileInputStream import java io FileOutputStream import java io IOException public class MyTh
  • 对于HTTP请求头及响应头的详解

    对于HTTP协议的请求头的详解 标签 xff1a CSDN博文 http协议由两部分组成 xff1a 请求和响应 当你在浏览器中输入一个URL时 xff0c 浏览器将按照你的请求创建并且发送请求 xff0c 该请求包含的所输入的URL以及一
  • 手把手教你实现Unity网络同步

    现如今 xff0c 网络同步的技术在各种游戏里被广泛应用和发展 xff0c 那么 xff0c 如何在Unity中搭建网络模块 xff1f 如何使服务器和客户端之间通信 xff1f 如何做到网络同步 xff1f 本文作者烂笔头 27将从自身经