1、操作系统
(1)进程、线程、协程
三者对比
|
进程 |
线程 |
协程 |
上级单位 |
操作系统 |
CPU |
可手动 |
含义 |
操作系统资源分配的最小单位 |
CPU调度的最小单位 |
程序控制的比线程更轻量级的存在 |
特质 |
一个程序必须要有一个以上进程 独立内存单元 有自己的程序运行入口出口等 |
一个进程必须有一个以上线程 可以共享内存 必须依附于程序 |
|
主要作用 |
操作系统中分配系统资源 |
程序的实际执行者 |
单线程下实现并发效果 |
物理内存 |
硬盘内存空间中有自己独立的地址空间,有自己的堆 |
线程的切换会保存在CPU的栈里 |
有自己的寄存器上下文和栈 |
通信 |
管道 消息队列 共享内存 信号量 信号 Socket |
共享内存 消息传递 管道流 |
|
(2)多线程
- 多线程共享的资源:
- 堆
- 全局变量
- 静态变量
- 文件等共用资源
- 线程独享的资源:栈、寄存器
- 多线程的好处:更高的并发性
- 多线程带来的问题:
- 导致了线程争用的安全问题
(3)死锁
- 含义:两个以上的线程由于竞争资源或者彼此通信造成的阻塞现象。
- 产生的条件:
- 互斥:一个资源同一时间只能被一个线程占用
- 不可剥夺:一个资源被占用之后,除非占用者主动释放,其他线程不能向其争夺。
- 请求和保持条件:当一个线程拥有了某个资源之后,还在请求其他的资源。
- 循环等待条件:链中每一个已经占用的资源同时被链中的下一个进程所请求。
- 如何预防(逐一击破):
- 互斥:系统特性,不能被破坏。
- 不可剥夺:允许对于资源进行抢占,并要求占有者释放资源。
- 请求和保持条件:阻止进程在占有资源的同时申请其他的资源。
- 一次性分配方案:创建进程的时候申请所需的全部资源。
- 要求每个进程申请资源之前需要释放掉已经占有的资源。
- 轮询锁:如果有一个锁获取失败,则释放当前线程拥有的所有锁,等待下一轮再尝试获取。
- 循环等待:将资源统一编号,进程可以任何时候进行资源申请,但是资源申请必须按照编号。
2、编译原理
(1)内存
地址对齐
不同硬件平台对于存储空间的处理是很不相同的,如果不按照平台的要求进行对齐,会在存取效率上有损失。通常来说,编译器会自动选择适合目标平台的对齐策略。
字节对齐
大多数计算机使用字节作为最小可寻址的存储器单位。存储器中的每一个字节都有唯一的数字标识,称为该字节的地址,所有可能的地址的集合称为存储器空间。
ARM处理器工作状态有两种:执行字对齐的32位ARM指令;执行半字对齐的16位Thumb指令。以字对齐为例,是指ARM指令占3位,而一个字节8位,所以每一个指令需要占据4个字节,所以这些指令的起始位置都是0、4、8、16……二进制中这些起始位置的末尾两位都是0.
内存对齐
不同硬件平台的CPU有些可以访问内存地址上的任意数据,有些只能访问特定地址的数据,这使得代码不具有移植性。CPU是以字长为单位访问数据的,所以数据结构应该尽可能的对齐自然边缘,如果访问未对齐的内存,处理器就需要做两次内存访问,浪费更多时间。
结构体的内存对齐:
结构体的第一个成员位于0的位置;后续每个成员相对于第一个值的偏移量都能被其自身的大小整除,编译器会自动填充字节;结构体的大小能够被最宽的成员的大小整除。
使用sizeof函数能得出内存的长度。
分页 & 分段
分页:内存空间中划分大小相同的基本单位,称为“块”,称为页框。每一页可以不相邻存储,这是磁盘和内存之间传输数据块的最小单位。
分段:将用户程序地址按照自身的逻辑关系划分为若干个大小不等的区域,称为段。比分页中的区域大。占据连续的内存空间,但是各段之间可以不相邻。
|
分页 |
分段 |
大小 |
区域大小相等 |
不相等 |
含义 |
物理地址,数据传输的最小单位 |
逻辑地址,相对于信息划分 |
地址 |
一维 |
二维(段号和段的长度) |
是否连续 |
每一页可以非连续存储 |
每一段可以不连续 |
操作主体 |
系统自己 |
用户确定 |
碎片 |
会产生内存碎片,不会有外存碎片 |
不会有内存碎片,会有外存碎片 |
(2)虚拟内存
每个进程创建加载的时候,都会被分配一个4G的连续虚拟地址空间,实际上是用多少操作系统就从磁盘上划出多少空间的。
访问虚拟地址空间的数据:
找到进程对应的页表中的条目,判断有效位,如果不为空则进行访问;如果为空,触发缺页异常;系统将物理内存中的数据拷贝到磁盘上,在进行访问的话就能访问到了。
(3)静态编译 & 动态编译
|
静态编译 |
动态编译 |
含义 |
装载之前就完成所有符号引用 |
程序运行的时候才进行符号的重定位 |
优点 |
简单 |
只需要维护一份公共类库代码 模块更新无需重新打包 |
缺点 |
浪费内存空间(可能存在多个相同的公共类库;只要有模块更新就需要重新编译打包) |
结构复杂 引入了安全问题 |
3、网络
(1)网络连接
硬件
客户端和服务端通过集线器连接着路由器,路由器连接到互联网服务提供商。
(2)网络协议
TCP
参考链接:https://www.cnblogs.com/AhuntSun-blog/p/12028636.html
三次握手:TCP连接的建立
- Step 1: 客户端向服务端发送报文
- Step2: 服务端接收之后结束Listen状态,返回一段报文
- Step3:客户端接收之后,明确数据传输是正常的;返回一段TCP报文
TCP协议不一定是客户端先发出的,服务端也可以先发出。
涉及到的字段
- Seq:序号,32位,发起方发送数据的时候对此进行标记
- Ack:确认号,32位,只有为1的时候,确认序号字段才有效
意义
为了防止服务器端开启一些无用的连接增加服务器开销以及防止已失效的连接请求报文段突然又传送到了服务端,因而产生错误。
只有两次的话无法确认客户端收到了服务端的消息,如果四次以上,就多做了无谓的确认。
只有两次的一个错误case:如果之前有一个包阻塞之后又建立起了连接,那么服务端就会认为是新发过来的包,从而造成客户端和服务端的状态不一致。
四次挥手:TCP连接的解除
- Step1:客户端关闭ESTABLISHED状态,向服务端发送一段TCP报文
- Step2:服务端接收到客户端发出的报文之后,确认了客户端想释放连接;服务器通知应用进程关闭ESTABLISHED状态,进入Close-Wait状态(半关闭状态),返回一段TCP报文
- Step3:服务端经过close-await阶段之后,做好了释放连接的准备,再次向服务端发出一段TCP报文
- Step4:客户端收到服务端的报文之后,结束fin-wait2阶段,进入Time-Wait阶段;向服务端发出TCP报文:
- ACK:标记接收到服务器准备好释放连接的信号
- 然后客户端在Time-Wait阶段等待2MSL
- 服务端收到报文之后,结束LAST-ACK阶段,进入Closed阶段。服务端的连接正式关闭
- 客户端等待玩2MSL之后,结束Time-Wait阶段,进入Closed阶段,由此完成四次挥手。
三次握手 & 四次挥手
(1)为什么是三次和四次?
建立连接时,被动方服务器端结束CLOSED阶段进入“握手”阶段并不需要任何准备,可以直接返回SYN和ACK报文,开始建立连接。
释放连接时,被动方服务器,突然收到主动方客户端释放连接的请求时并不能立即释放连接,因为还有必要的数据需要处理,所以服务器先返回ACK确认收到报文,经过CLOSE-WAIT阶段准备好释放连接之后,才能返回FIN释放连接报文。
(2)四次挥手的时候为什么要等2MSL
客户端最后发送报文之后不能确定服务端能收到,所以会设置一个时长为2MSL的计时器。MSL(Maximum Segment Lifetime)一段TCP报文在传输中的最大生命周期,2MSL即为服务端发出报文和客户端发出ACK确认报文保持有效的最大时长。
1)如果服务端在1MSL的时候没有收到客户端发出的ACK确认报文,会再次向客户端发出FIN报文。此时处于Time-Wait状态的客户端就可以对Fin报文做出回复。
2)保证让迟来的报文段有足够的时间被识别和丢弃。连接结束了,那么网络中延迟报文也应该被丢弃掉,以免影响立刻建立新的连接。
TCP/IP的五个层级
- 物理层:广电信号的传输
- 数据链路层:设备之间数据帧的传输和识别。
- 网络层:地址管理和路由选择。路由器工作的层级。
- 传输层:负责两台主机之间的数据传输。
- 应用层:网络编程是在这个层级
数据链路层 |
传输层 |
|
功能 |
通过各种协议保证建立可靠的数据链路。 |
是通信子网和资源子网的接口和桥梁。 |
作用 |
|
|
环境 |
两个分组交换结点直接通过一条物理信道进行通信。 |
两个主机和整个子网作为通信信道进行通信 |
数据 |
信息帧 |
报文 |
稳定性
TCP是稳定的,因为提供了乱序重排、应答确认、报文重传、流量控制等四种机制。
(1)乱序重排:由于网络或者多线程的因素,接收到的数据段可能是乱序的,但是每个TCP报文中都封装了序号,所以重组起来很容易。
(2)应答确认:如果客户端发送了多个数据段,那么服务端接收到的时候只需要在多个数据段的和上加1作为回应报文的确认号就可以了。
(3)报文重传:
超时重传:TCP会根据网络速度的不同调整重传时间(RTO);每发送一个报文就会开始计时,如果超时就会自动的重新发送。
快速重传:如果接收方发送序号为2的报文,表示希望收到序号为2的报文,但是接下来却收到了3、4、5等,那立即再发送序号为2的报文。
(4)流量控制:
发送或者接受数据的时候,都会把数据先放进缓冲区,所以缓冲区的大小就决定了一次能处理的数据的能力。如果处理速度比发送速度慢,就会导致溢出。TCP建立连接的时候客户端和服务端会协商缓冲区window的大小,通过报文中的window字段来进行确认。发送方会根据服务端发回来的报文中的window值来调整每次发多少报文,发送的窗口会向左滑动、大小也会变化,这叫做窗口滑动。
流量控制、拥塞控制、断线重连
(1)流量控制:TCP中使用滑动窗口机制
目的:让发送的速率不要过快,以至于接收方来不及处理。
原理:利用报文中的window字段来控制窗口,使得发送方的发送窗口小于接收方发回的窗口大小。
Bad case:如果接收方没有缓存使用,发送的报文中window大小为0,那么发送方停止发送;当接收方的缓存恢复,重新发送了窗口非0的报文,如果这个报文丢失,那么发送方会一直锁在window为0的状态。
解决方式:TCP为每一个连接设置一个持续计时(persistence timer),只要有一方收到零窗口的报文,就启动这个计时器,周期性的发送零窗口探测报文段。对方在确认这个报文的时候给出现在的窗口的大小。
(2)拥塞控制
拥塞的发生:路由器溢出,拥塞会导致丢包,但是丢包不一定会引发拥塞。
拥塞控制是快速传输的基础。
拥塞控制算法包括:慢启动法、拥塞避免算法、快速重传算法、快速恢复算法。
(3)断线重连
检测到客户端断线:断开客户端的Socket;重新根据IP和端口号重建新的Socket。
当连接上服务器网关之后,携带Token,向服务器发送重连的协议。
UDP & TCP
共同点:都是工作在传输层的,都是用来传输数据的。
区别:TCP基于连接,UDP是无连接的
UDP:
- 优势:性能损耗小、资源占用少
- 劣势:可能会丢包,稳定性弱;不提供有序的保证。
- 适用场景:对于网络的实时性要求高,少量丢包问题可以先忽略的情况;语音、视频直播、域名查询,也可以适用于隧道网络(VPN等)
TCP:
- 优势:稳定可靠
- 适用场景:文件传输、电子邮件、网页浏览等
TCP |
UDP |
|
连接 |
基于连接 |
无连接 |
可靠性 |
提供交付保证,丢失的话就会重发 |
不提供交付保证 |
数据边界 |
不保证数据边界 |
UDP中数据包是单独发送的,只有到达之后才会再次集成。 |
速度 |
慢 |
快,可应用于直播、多人在线游戏等 |
发送消耗 |
重量级 |
轻量级 |
报头大小 |
大,报头是20字节 |
小,报头是8个字节,仅包含:长度、源端口号、目的端口、校验码 |
拥塞或者流量控制 |
有流量控制 |
不进行流量控制 |
应用场景 |
状态同步,MMO |
帧同步,FPS竞技等 |
Https
Https是在HTTP基础上用TLS/SSL进行加密之后的加密版本。
Http是在应用层的,基于TCP
(1)安全性:防止传输过程被监听,防止数据被窃取
(2)传输过程:
- 客户端发起请求,服务端返回证书
- 客户端对证书进行验证,验证后通过证书中的公钥进行加密,传输到服务端,服务端接收之后通过私钥解密
- 数据交换:使用对称加密算法进行解密
(3)证书:防止中间人入侵,同时为网站提供身份证明
(4)抓包:会被抓包;如果用户主动授信,可以构建中间人网络,代理软件就可以对传输内容进行jiami(不知道为啥是敏感词)。
端口号
0-1023:知名端口号
1024-65535:操作系统动态分配的端口号:
- 程序注册端口号:1024-49151
- 动态私有端口号:49152-65535
大小端
- 大端是高字节存放到内存的低地址,小端是高字节存放到内存的高地址。
- 目前大多数是小端,少数是大端。
网络同步
状态同步 & 帧同步
状态同步 |
帧同步 |
|
核心游戏逻辑 |
服务端 |
客户端 |
安全性 |
强 |
弱 |
回放机制 |
在服务端开辟一块内容保存局内操作 |
保存局内的操作,直接回放操作 |
帧同步
(1)案例:王者荣耀是帧同步的
(2)定点数:帧同步中为了保证各平台的同步,就必须使用定点数物理库,避免一切需要使用浮点数的计算
(3)反作弊:多人竞技中通过客户端上传关键的游戏数据通过hash投票来找到作弊玩家;人数较少的帧同步可以结算之后服务器再加速跑一遍(刀塔传奇)
(4)帧同步中不同步的原因
所谓不同步:客户端计算,通过服务器转发给所有的客户端。服务端是不知道逻辑的,如果客户端运算的时候某一帧的变量不一致,就会滚雪球般的越来越严重。呈现的效果就是,不同的客户端显示的结果都是不一致的。
- 1)数值的随机性:因为帧同步中逻辑在客户端进行,不同的客户端得到的随机数值是不一样的,就会得到不同的战斗结果。解决方案是做伪随机算法。
- 2)逻辑执行顺序不一致:一些第三方插件的Update是没办法用帧同步去控制的。解决方案:避免使用这些不成熟稳定的三方插件。
- 3)数学计算中的精度丢失:主要是指浮点数。解决方案:用定点数代替浮点数。
- 4)接收的网络数据不一样:UDP容易出现网络波动。解决方案:对每一个发出的消息做一个自增的编号,根据编号的连续性确定消息的顺序。
(5)定点数
- 解决的问题:浮点数在不同的平台会有运算结果的不一致,会导致不同步
- 方案:定点数物理库+定点物理计算。
预测 & 和解 & 插值
这3个概念都是用来解决有冲突的同步的。
预测
定义:就是将玩家的输入立即应用到本地状态,无需等待服务端的返回。这种情况下当服务端返回之后,我们会看到抖动和拉扯,所以引入一个重要概念:和解。
和解
预测状态 = 权威状态(从服务器接收的输入)+预测输入(客户端接收的输入)
- 权威输入:从服务器接收到的输入
- 预测输入:客户端发出一个输入,但是还没有得到服务端的返回确认的,也叫做非权威输入。
- 如果网络连接通畅,那么预测输入最终都会变成权威输入;也就是说,状态同步和帧同步最终都会变成权威输入。
和解的过程:
- 状态同步:
- (1)收到服务端同步来的权威状态;
- (2)将本地状态设为这个权威状态;
- (3)在权威状态的基础上,应用当前的所有预测输入
- 帧同步:
- (1)收到服务端同步来的权威输入;
- (2)将本地状态回滚到上一次的权威状态;
- (3)将权威输入应用到当前的状态,得到这次的权威状态;
- (4)在权威状态的基础上,应用预测输入
插值
用来平滑过渡
游戏中逻辑、渲染分离,游戏通常被分为逻辑层和表现层,这两个层最终都是面向数据的。
- 表现层:游戏画面的显示和用户输入的获取;
- 逻辑层:定义所有的状态和输入,然后实现状态变更;逻辑层本质上就是一个状态机。
和解 |
插值 |
|
发生的位置 |
逻辑层 |
表现层 |
对象 |
自己 |
其他人 |
网络同步的优化
主要是5个方面:
- 表现层:表现优化、帧率优化
- 应用层:延迟对抗、帧率优化
- 传输层:
- 网络层:带宽优化
- 数据链路层:丢包对抗
以下是具体的措施:
- (1)表现优化
- 插值:差值优化
- 预测 & 和解:客户端预测+回滚
- (2)延迟对抗
- 延迟补偿
- 命令缓冲区
- 是线上对抗延迟
- (3)丢包对抗
- TCP不会丢包,所以一般采用TCP来对抗丢包
- 冗余UDP数据包,一次性发送多个帧的数据来对抗丢包
- (4)带宽优化:目的是减小同步压力
- 同步对象裁剪
- 分区、分房间
- 数据压缩和裁剪
- 减少遍历以及细粒度的优化
- (5)帧率优化
- 提升帧率
- 保持帧率稳定和匹配
- 计算压力分担:把一部分计算资源交给客户端来计算,比如物理运算、自动寻路、AI逻辑运算等
逻辑在客户端和服务端的区别
客户端:服务器压力小;但是可能会导致作弊(waigua)
服务端:更好复用,降低了整体的计算量;降低了序列化和反序列化的成本,延迟更低;