C#基础篇
- 请简述拆箱和装箱
答:
装箱操作:
值类型隐式转换为object类型或由此值类型实现的任何接口类型的过程。
1.在堆中开辟内存空间。
2.将值类型的数据复制到堆中。
3.返回堆中新分配对象的地址。
拆箱操作:
object类型显示转换为值类型或从接口类型到实现该接口值类型的过程。
1.判断给定类型是否是装箱时的类型。
2.返回已装箱实例中属于原值类型字段的地址。
- C#中,string str = null 与 string str = "",说明区别。
答:
string str = "" 初始化对象分配空间。
string str = null 表示一个空引用,没有占用空间。
- ref与out关键字
答:
ref 关键字使参数按引用传递。其效果是,当控制权传递回调用方法时,在方法中对参数所做的任何更改都将反映在该变量中。若要使用 ref 参数,则方法定义和调用方法都必须显式使用 ref 关键字。
out 关键字会导致参数通过引用来传递。这与 ref 关键字类似,不同之处在于 ref 要求变量必须在传递之前进行初始化。若要使用 out 参数,方法定义和调用方法都必须显式使用 out 关键字。 相同之处:都可以改变外部的变量的值,并且在函数内进行赋值都可以都可以把值给带出来。 不同之处:使用out关键字,在外部可以不进行赋值初始值,但是在函数内部必须进行赋值,并且要确保有值返回。使用ref关键字在传递之前必须进行初始化,函数内部可以不进行赋值。
- 什么是序列化?
答:
序列化是将对象状态转换为可保持或传输的格式的过程。与序列化相对的是反序列化,它将流转换为对象。这两个过程结合起来,可以轻松地存储和传输数据。
- sealed 修饰符有什么特点
答:
- sealed 修饰符可以应用于类、实例方法和属性。密封类不能被继承。密封方法会重写基类中的方法,但其本身不能在任何派生类中进一步重写。当应用于方法或属性时,sealed 修饰符必须始终与 override一起使用。
- 将密封类用作基类或将 abstract 修饰符与密封类一起使用是错误的。
- 结构是隐式密封的;因此它们不能被继承。
- class和struct的异同
答:
相同点:
- 语法类似。
不同点:- class是引用类型,继承自System.Object类; struct是值类型,继承自System.ValueType类,因此不具多态性。但是注意,System.ValueType是个引用类型。
- 从职能观点来看,class表现为行为; 而struct常用于存储数据。
- class支持继承,可以继承自类和接口; 而struct没有继承性,struct不能从class继承,也不能作为class的基类,但struct支持接口继承。
- 实例化时,class要使用new关键字; 而struct可以不使用new关键字,struct在声明时就进行了初始化过程,所有的成员变量均默认为0或null。
- 如何选择结构还是类。
答:
- 堆栈的空间有限,对于大量的逻辑的对象,创建类要比创建结构好一些。
- 结构表示如点、矩形和颜色这样的轻量对象。例如,如果声明一个含有 1000 个点对象的数组,则将为引用每个对象分配附加的内存。在此情况下,结构的成本较低。
- 在表现抽象和多级别的对象层次时,类是最好的选择。
- 大多数情况下该类型只是一些数据时,结构时最佳的选择。
- 抽象类(abstract class)和接口(interface)的区别。
答:
抽象类:
- 抽象方法只作声明,而不包含实现,可以看成是没有实现体的虚方法。
- 抽象类不能被实例化。
- 抽象类可以但不是必须有抽象属性和抽象方法,但是一旦有了抽象方法,就一定要把这个类声明为抽象类。
- 具体派生类必须覆盖基类的抽象方法。
- 抽象派生类可以覆盖基类的抽象方法,也可以不覆盖。如果不覆盖,则其具体派生类必须覆盖它们。
接口:- 接口不能被实例化。
- 接口只能包含方法声明。
- 接口的成员包括方法、属性、索引器、事件。
- 接口中不能包含常量、字段(域)、构造函数、析构函数、静态成员。
- 接口中的所有成员默认为public,因此接口中不能有private修饰符。
- 派生类必须实现接口的所有成员。
- 一个类可以直接实现多个接口,接口之间用逗号隔开。
- 一个接口可以有多个父接口,实现该接口的类必须实现所有父接口中的所有成员。
抽象类和接口的异同:
相同点:- 都可以被继承。
- 都不能被实例化。
- 都可以包含方法声明。
- 派生类必须实现未实现的方法。
区别:- 抽象基类可以定义字段、属性、方法实现。接口只能定义属性、索引器、事件、和方法声明,不能包含字段。
- 抽象类是一个不完整的类,需要进一步细化,而接口是一个行为规范。微软的自定义接口总是后带able字段,证明其是表述一类“我能做。。。”。
- 接口可以被多重实现,抽象类只能被单一继承。
- 抽象类更多的是定义在一系列紧密相关的类间,而接口大多数是关系疏松但都实现某一功能的类中。
- 抽象类是从一系列相关对象中抽象出来的概念,因此反映的是事物的内部共性;接口是为了满足外部调用而定义的一个功能约定,因此反映的是事物的外部特性。
- 接口基本上不具备继承的任何具体特点,它仅仅承诺了能够调用的方法。
- 接口可以用于支持回调,而继承并不具备这个特点。
- 抽象类实现的具体方法默认为虚的,但实现接口的类中的接口方法却默认为非虚的,当然您也可以声明为虚的。
- 如果抽象类实现接口,则可以把接口中方法映射到抽象类中作为抽象方法而不必实现,而在抽象类的子类中实现接口中方法。
- 什么是强类型。
答:
为所有变量指定数据类型称为“强类型”。C#是强类型语言。
- 什么是托管代码。
答:
使用基于公共语言运行库的语言编译器开发的代码称为托管代码;托管代码具有许多优点,例如:跨语言集成、跨语言异常处理、增强的安全性、版本控制和部署支持、简化的组件交互模型、调试和分析服务等。
- 什么是CLR?
答:
CLR:公共语言运行库 Common Language Runtime。是一个运行时环境,它负责资源管理(内存分配和垃圾收集),并保证应用和底层操作系统之间必要的分离。
- 什么是委托?
答:
- 委托是一种引用方法的类型。
- 委托类似于 C++ 函数指针,但它是类型安全的。
- 委托允许将方法作为参数进行传递。
- 委托可用于定义回调方法。
- 值类型和引用类型的区别。
答:
- 值类型通常被分配在栈上,它的变量直接包含变量的实例,使用效率比较高。
- 引用类型分配在托管堆上,引用类型的变量通常包含一个指向实例的指针,变量通过该指针来引用实例。
- 一个是值COPY,一个是地址COPY。
Unity基础篇
- 一个角色要用到unity中寻路系统,应该添加哪个组件?
答:NavMeshAgent。
1.寻路算法(A*算法)的核心思想:
自动寻路是在一些如MMORPG等类型游戏中常见的一种功能,其给了玩家良好的游戏体验,使得玩家在游戏过程中省去了大量游戏坐标点的记录以及长时间的键盘操作,不必记忆坐标,不必担心迷路,用最快捷的方法移动到指定地点。
寻路算法(自动寻路算法,下同),其实可以看作是一种路径查找算法以及图搜索算法,图搜索(Graph Search)算法是用于在图上进行一般性发现或显式地搜索的算法。这些算法在图上找到出路径,但没有期望这些路径是在计算意义上是最优的。
路径查找算法(Pathfinding)是建立在图搜索算法的基础上,它探索节点之间的路径,从一个节点开始,遍历关系,直到到达目的节点。这些算法用于识别图中的最优路由,算法可以用于诸如物流规划、最低成本呼叫或IP路由以及游戏模拟等用途。
常见的路径搜索算法和图搜索算法有:A(A Star)算法、Dijkstra算法、广(深)度优先搜索、最佳优先搜索、Jump Point Search算法**等,今天本文主要讲解的是A(A Star)算法的原理以及实现。
迪杰斯特拉算法(Dijkstra)是由荷兰计算机科学家狄克斯特拉于1959 年提出的,因此又叫狄克斯特拉算法。是从一个顶点到其余各顶点的最短路径算法,解决的是有权图中最短路径问题。迪杰斯特拉算法主要特点是从起始点开始,采用贪心算法的策略,每次遍历到始点距离最近且未访问过的顶点的邻接节点,直到扩展到终点为止。
在游戏中(仅以2D为例),我们把某个场景的地图按照一定的规则划分成一个个的小格子,每个格子代表一个可用的坐标点,该坐标点有两种状态分别为:有效、无效。其中有效状态代表为该坐标点玩家可以正常通过,无效为该坐标点玩家无法到达,即当前坐标点可能存在阻挡物,如河流、山川、npc等,并且,以每个格子(以正方形为例)为中心可以分别向8个不同方向前进,在像每个方向前进时,所需的代价是不同的
在游戏中(仅以2D为例),我们把某个场景的地图按照一定的规则划分成一个个的小格子,每个格子代表一个可用的坐标点,该坐标点有两种状态分别为:有效、无效。其中有效状态代表为该坐标点玩家可以正常通过,无效为该坐标点玩家无法到达,即当前坐标点可能存在阻挡物,如河流、山川、npc等,并且,以每个格子(以正方形为例)为中心可以分别向8个不同方向前进,在像每个方向前进时,所需的代价是不同的。
举个例子(后续的所有移动代价皆以此计算)
当玩家向自身8个方向中的横轴以及纵轴进行移动时(即向上、下、左、右),移动代价为10(别问为啥为10,拍脑门决定的)。
当玩家向自身八个方向中的对角方向进行移动时(即左上、左下、右上、右下),移动代价为横(纵)轴移动的1.4倍(因为正方形的对角线是边长约1.4倍)。
在使用该算法的时候需要选择一个周围8个方向中,距离起点总移动代价最低的节点作为下一个要遍历的节点,一直到当前节点为终点或者无可用节点为止。那么此时就需要一个优先队列来保存当前节点的8个方向中的可用节点、以方便的查找到移动代价最低的节点,应当注意,绝大多数的迪杰斯特拉算法不能有效处理移动代价为负的。
2.最佳优先搜索算法(Best-First-Search)
最佳优先搜索算法是一种启发式搜索算法(Heuristic Algorithm),其基于广度优先搜索算法,不同点是其依赖于估价函数对将要遍历的节点进行估价,选择代价小的节点进行遍历,直到找到目标点为止。BFS算法不能保证找到的路径是一条最短路径,但是其计算过程相对于Dijkstra算法会快很多3。
所谓的启发式搜索算法,就是针对游戏地图中的每一个位置节点进行评估,从而得到相对来说最优的位置,而后再从这个最优为止再次进行搜索,直到符合终止条件或者超出地图范围为止,这样可以省略大量无谓的搜索路径,提高了效率。在启发式搜索中,对位置的预估是十分重要的。因此就需要使用启发函数来进行位置预估。
这个算法和Dijkstra算法差不多,同样也使用一个优先队列,但此时以每个节点的移动代价作为优先级去比较,每次都选去移动代价最小的,因为此时距离终点越来越近,所以说这种算法称之为最佳优先(Best First)算法
注意的是当地图中存在障碍物的时候,最佳优先搜索算法并不能找到最短路径。
3.A Star 算法
A Star算法是一种在游戏开发过程中很常用的自动寻路算法。它有较好的性能和准确度。A搜索算法(A search algorithm)是一种在图形平面上,有多个节点的路径,求出最低通过成本的算法。常用于游戏中的NPC的移动计算,或网络游戏的BOT的移动计算上。该算法综合了最佳优先搜索和Dijkstra算法的优点:在进行启发式搜索提高算法效率的同时,可以保证找到一条基于启发函数的最优路径
启发公式 A Star算法也是一种启发式搜索算法那么其与最佳优先搜索算法一样需要一个启发函数左右计算移动代价的方法,那么A Star的启发函数公式为:
f(n)=g(n)+h(n)
解释下这个公式的各项的含义:
h(n):从当前节点n到终点的预估的代价。
g(n):从起始点到达当前节点n的代价。
f(n):为当前节点n的综合的代价,在选择下一个节点的时候,其值越低,优先级就越高(因为代价小)。
为了简化问题,我们把要搜寻的区域划分成了正方形的格子。这是寻路的第一步,简化搜索区域,就像推箱子游戏一样。这样就把我们的搜索区域简化为了 2 维数组。数组的每一项代表一个格子,它的状态就是可走 (walkalbe) 和不可走(unwalkable) 。通过计算出从起点到终点需要走过哪些方格,就找到了路径。一旦路径找到了,人物便从一个方格的中心移动到另一个方格的中心,直至到达目的地。
A Star算法执行过程:
在搜索过程中,A Star算法需要遍历周围8方向的8个节点,并将之有效的节点(即可通过的节点)计算f(n)后放入一个优先级队列中,以便下次循环可以直接取出优先级最高的节点继续遍历,此时我们把这个优先级队列称呼为OpenList,另外A Star算法中也需要一个容器用来存储已经遍历过的节点、以防止重复的遍历,那么此时我们把这个容器称之为CloseList。
Star算法算是一个很简单的用于游戏中自动寻路的算法,但是其性能还有有些问题,主要问题在于其需要把每个节点的周围所有可用节点均插入至OpenList中,因此相对来说性能损耗几乎都在这OpenList中,因此A Star算法有很多对其可以优化的算法。
JPS(jump point search)算法,该算法实际上是对A Star寻路算法的一个改进,A Star 算法在扩展节点时会把节点所有邻居都考虑进去,这样OpenList中点的数量会很多,搜索效率较慢。而JPS在A Star算法模型的基础之上,优化了搜索后继节点的操作。A Star的处理是把周边能搜索到的格子,加进OpenList,然后在OpenList中弹出最小值。JPS也是这样的操作,但相对于A Star来说,JPS操作OpenList的次数很少,它会先用一种更高效的方法来搜索需要加进OpenList的节点,从而减少OpenList中节点的数量。
Unity内置的自动导航寻路系统:
Unity 中的导航寻路系统是能够让我们在游戏世界当中,让角色能够从一个起点准确的到达另一个终点,并且能够自动避开两个点之间的障碍物选择最近最合理的路径进行前往,
Unity中的导航寻路系统的本质,就是在 A 星寻路算法的基础上进行了拓展和优化
导航网格(NavMesh)的生成——要想角色能够在场景中自动寻路产生行进路径,那么必须得先有场景地形数据,导航网格生成就是生成用于寻路的地形数据
导航网格寻路组件(NavMesh Agent)——寻路组件就是帮助我们根据地形数据计算路径让角色动起来的关键
导航网格连接组件(Off-Mesh Link)——当地形中间有断层,想让角色能从一个平面跳向另一个平面,网格连接组件时关键
导航网格动态障碍物组件(NavMesh Obstacle)——地形中可能存在的可以移动或动态销毁的障碍物需要挂载的组件
寻路组件的本质就是根据烘焙出的网格寻路信息,通过基于 A 星寻路的算法计算出行进路径让我们在该路径上移动起来
参考文档:blog.csdn.net/weixin_5316…
- NavMeshObstacle组件的作用?
答:寻路网格动态碰撞组件,用于运动的物体阻碍寻路物体效果。
- 射线中RaycastHit代表什么?
答:射线碰到的碰撞信息
- 通过什么可以区分射线碰到的游戏对象?
答:通过LayerMask(层的遮罩),RaycastHit返回的碰撞的标签。
- 怎么判断两个平面是否相交?不能用碰撞体,说出计算方法。
答:在两个平面上分别取一个向量,然后看是否相交。
- MeshCollider和其他Collider的一个主要不同点?
答:
MeshCollider是网格碰撞器,对于复杂网状模型上的碰撞检测,比其他的碰撞检测精确的多,但是相对其他的碰撞检测计算也增多了,所以一般使用网格碰撞也不会在面数比较高的模型上添加,而会做出两个模型,一个超简模能表示物体的形状用于做碰撞检测,一个用于显示。
- Unity3d中的碰撞器和触发器的区别?
答:
- 碰撞器物体不能互相进入到对方内部,触发器可以。
- 触发器角色控制器可以使用,碰撞器中不能使用。
- 触发器没有物理属性了,碰撞器可以有力存在。
- 碰撞器调用OnCollisionEnter/Stay/Exit函数,触发器调用OnTriggerEnter/Stay/Exit函数。
- 物体发生碰撞的必要条件
答:两个物体都必须带有碰撞器(Collider),其中一个物体还必须带有Rigidbody刚体。
- 当一个细小的高速物体撞向另一个较大的物体时,会出现什么情况?如何避免?
答:穿透(碰撞检测失败)。
避免的方法:把刚体的实时碰撞检测打开Collision Detection修改为Continuous Dynamic
数据结构与算法篇
- 请使用冒泡排序,对一维数组进行降序排列
答:
冒泡排序:它重复地走访过要排序的数列,一次比较两个元素,如果它们的顺序错误就把他们交换过来。走访数列的工作是重复地进行直到没有再需要交换。
冒泡排序算法的运作如下:
比较相邻元素。如果第一个比第二个大,就交换它们。
对每一对相邻元素做同样的工作,从开始第一队到结尾的最后一对。在这一点,最后的元素会使最大的数。
针对所有的元素重复以上的步骤,除了最后一个。
持续每次对越来越少的元素重复上面的步骤,知道没有任何一对数字需要比较。
写法1
public void Bubble1(int[] a)
{
bool b;
int tmp;
for (int i = 0; i < a.Length; i++)
{
b = false;
for (int j = i+1; j < a.Length ; j++)
{
if (a[j] > a[i])
{
tmp = a[j];
a[j] = a[i];
a[i] = tmp;
b = true;
}
}
if(!b)break;
}
}
写法2
void Bubble2(int[] a)
{
bool b=false;
int tmp;
for(int i=1;i<a.Length;i++)
{
b=false;
for(int j=0;j<a.Length-i;j++)
{
if(a[j]<a[j+1])
{
tmp=a[j];
a[j]=a[j+1];
a[j+1]=tmp;
b=true;
}
}
if(!b) break;
}
}
写法3
void Bubble3(int[] a)
{
bool b=true;
int j=0;
int temp;
do
{
b=false;
for(int i;i<a.Length-j;i++)
{
if(a[i]<a[i+1])
{
temp=a[i];
a[i]=a[i+1];
a[i+1]=temp;
b=true;
}
}
j++;
}
while(b);
}
对数组进行升序的冒泡排序:
public static int[] BubbleSort(int[] arr)
{
int temp = 0;
for (int i = 1; i < arr.Length; i++)
{
for (int j = 0; j < arr.Length - i; j++)
{
if (arr[j] > arr[j + 1])
{
temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
return arr;
}
- 说出你所了解的数据结构,并说明它们的特点。
答:
数组:长度固定,存储空间连续,读取速度快,插入,删除速度慢
链表:长度不定,存储空间可能不连续,读取速度慢,插入,删除速度快
栈:后进先出
队列:先进先出
字典:键/值对集合,无序,一一对应,查找速度快
树:可形成无限级层次,父子节点。
面向对象篇
- 请描述你所了解的设计模式,并说明在你的项目中哪里使用过?
答:
单例: 对象池,游戏管理器
抽象工厂,
状态:有限状态机,
桥接:有限状态机
策略:AI自动行为操控中每种操控算法的独例
- 请说出4中面向对象的设计原则,并分别简述它们的含义。
答:
1)单一职责原则:一个类,最好只做一件事,只有一个引起它的变化。
2)开放-封闭原则:对于扩展是开放的,对于更改是封闭的。
3)里氏替换原则:子类必须能够替换其基类。
4)依赖倒置原则:设计应该依赖于抽象而不是具体实现。
5)接口隔离原则:使用多个小的专门的接口而不要使用一个大的总接口。
网络篇
1.请简述TCP与UDP的区别。
一、TCP和UDP都属于TCP/IP协议族
TCP/IP模型是一些列协议的总称(比如:TCP,UDP,IP,FTP,HTTP,ICMP,SMTP)。这些协议可以划分为四层:链路层、网络层、传输层、应用层。
二、UDP和TCP的不同
1.TCP是面向连接的,UDP面向无连接的
UDP不需要像TCP一样建立三次握手连接的,而TCP在通信前是需要进行三次握手的。
具体就是:应用层将数据传递给传输层的UDP协议,UDP只会给数据增加一个UDP头,标识就是UDP协议,然后就传递给网络层了。
在接收端,网络层将数据传递给传输层,UDP只会去除IP报文头就传递给应用层了,不做任何的拼接操作。
注意点:在TCP协议中使用了接收确认和重传机制,使得每一个信息数据都能保证正确到达,是可靠的。
而UDP是尽力传送,没有应答和重传机制,UDP只是将信息发送出去,对方收不收到也不进行应答,所以UDP协议是不可靠的。
2.TCP是可靠的,UDP是不可靠的
1.由于UDP通信是不需要建立连接的,并且发送数据也不会关系对方是否已经正确接收到数据了。
2.其次,UDP是没有拥塞控制的,网络环境有可能不好的时候,UDP会一直以恒定的速度去发送数据,也就是说就算网络不好,也不会对发送速率作出调整,这样就会在网络不好的时候,可能产生丢包的问题。
但是这个问题有时候也适用于其他应用场景,比如对于一些实时性要求高的场景(电话会议)就需要UDP,因为远程视频的时候,你丢一些数据并并不影响视频的内容。
3.TCP是面向字节流的,UDP是面向报文的
TCP是基于流的传输表示TCP不认为消息是一条一条的,是无保护边界的(保护消息边界:指传输协议把数据当做一条独立的消息在网络传输,接收端一次只能接收一条独立的消息)。
UDP是面向报文的,是具有保护消息边界的,接收方一次只能接受一条独立的消息,也就是说UDP不存在粘包问题。
举个例子:有三个数据包,大小分别为2k、4k、6k,如果采用UDP发送的话,不管接受方的接收缓存有多大,我们必须要进行至少三次以上的发送才能把数据包发送完,但是使用TCP协议发送的话,我们只需要接受方的接收缓存有12k的大小,就可以一次把这3个数据包全部发送完毕,那么就会造成接收方一次会接收不止一条消息,这就是粘包。
4.TCP只有一对一的传输方式,而UDP不仅可以一对一,还可以一对多,多对多
UDP不仅支持一对一的传输方式,同样也支持一对多,多对多,多对一方式,也就是说UDP提供了单播,多播,广播的功能。
TCP不能一对多的原因是因为TCP通信前需要跟一台主机进行三次握手连接。
5.TCP头部开销大,UDP头部开销小
TCP头部至少需要20个字节,而UDP头部只有8个字节
下图是一个UDP数据包:可以看到,头部数据由4部分组成:源端口号、目的端口号、总长度(即UDP头部+数据的长度)、校验和。
6.TCP会产生粘包问题,UDP会产生丢包问题
TCP产生粘包问题的主要原因是:TCP是面向连接的,所以在TCP看来,并没有把消息看成一条条的,而是全部消息在TCP眼里都是字节流,因此A、B消息混在一起后,TCP就分不清了。
来一个图展示一下TCP粘包情况:
可以看到,第一次read数据,读了5个字节,但是第一个数据有10个字节啊,没办法,就只能下次再读,第二次read数据,读25个字节,就会把后面的数据也一起读了,这就是粘包情况。
粘包问题的最本质原因在于接收方无法分辨消息与消息之间的边界在哪。我们通过使用某种方案给出边界,例如:
包头加上包体长度。包头是定长的4个字节,说明了包体的长度。接收对先接收包体长度,依据包体长度来接收包体。
UDP丢包问题:
由于UDP是没有应答和重传机制,因此包很容易传丢了也不知道。
主要丢包原因:接收端处理时间过长导致丢包:调用recv方法接收端收到数据后,处理数据花了一些时间,处理完后再次调用recv方法,在这二次调用间隔里,发过来的包可能丢失。对于这种情况可以修改接收端,将包接收后存入一个缓冲区,然后迅速返回继续recv。
发送的包巨大丢包:虽然send方法会帮你做大包切割成小包发送的事情,但包太大也不行。例如超过50K的一个udp包,不切割直接通过send方法发送也会导致这个包丢失。这种情况需要切割成小包再逐个send。
发送的包较大,超过接收者缓存导致丢包:包超过mtu size(mtu表示最大传输单元)数倍,几个大的udp包可能会超过接收者的缓冲,导致丢包。这种情况可以设置socket接收缓冲。以前遇到过这种问题,我把接收缓冲设置成64K就解决了。
发送的包频率太快:虽然每个包的大小都小于mtu size 但是频率太快,例如40多个mut size的包连续发送中间不sleep,也有可能导致丢包。
总结
2.为什么建立连接协议是三次握手,而关闭连接却是四次握手呢?
答:
TCP 协议?传输控制协议( Transmission Control Protocol, TCP )是种面向连接、确保数据在端到端间可靠传输的协议。面向连接是插在发送数据前,需要先建立一条虚拟的链路,然后让数据在这条链路上“流动”完成传输。 UDP是无连接的传输协议,称为用户数据报协议。UDP 为提供了一种无需建立连接就可以发送封装的 IP 数据包的方法。 内容非常的丰富,跟我们今天要讨论的连接协议相关的就是中间那六个状态位: URG、ACK、PSH、RST、SYN、FIN ,都置为 1 表示有效,在这六个当中,我们主要关注重点关注 ACK、SYN、FIN 这三个。下面解释一下这三个状态位:
ACK:用于对收到的数据进行确认,所确认的数据由确认序列号表示。
SYN:用作建立连接时的同步信号
FIN:表示后面没有数据需要发送,通常意味着所建立的连接需要关闭了。
好了,到这里,TCP 的基础知识我们就知道了,下面我们就来看看为什么
是三次握手,而不是四次或者两次,
两次和四次都会出现问题,三次就刚刚好。
总体来说就是呼叫、应答、回应,我们来详细的介绍每一步:
- **第一步:** A 机器向 B机器发出一个数据包并将 SYN 设置为 1 ,表示希望建立连接。这个包中的序列号假设是 X。
- **第二步:** B 机器收到 A机器发过来的数据包后,通过 SYN 得知这是一个建立连接的请求,于是发送一个响应包并将 SYN 、ACK 标记都置为 1。假设这个包中的序列号是 y ,而确认序列号必须是 x+l ,表示收到了 发过来的 SYN,TCP 中, SYN 被当作数据部分的一个字节。
- **第三步:** A 收到 的响应包后需进行确认,确认包中将 ACK ,并将确认序列号设置为 y+ ,表示收到了来自B 的 SYN
经过这三步之后,两台服务器就建立连接了,可以进行通信数据传输了。为什么要三次握手呢?主要是为了信息对等和防止出现请求超时导致脏连接。
关闭的时候四次握手的原因是:
- 因为当Server端收到Client端的SYN连接请求报文后,可以直接发送SYN+ACK报文。其中ACK报文是用来应答的,SYN报文是用来同步的。
- 但是关闭连接时,当Server端收到FIN报文时,很可能并不会立即关闭SOCKET,所以只能先回复一个ACK报文,告诉Client端,“你发的FIN报文我收到了”。
- 只有等到我Server端所有的报文都发送完了,我才能发送FIN报文,因此不能一起发送。故需要四步握手。
操作系统篇
1.进程与线程的区别与联系
进程和线程基本概念
1.进程:进程是操作系统中资源分配的基本单位。进程是操作系统对正在运行程序的一种抽象,可以将进程看作程序的一次运行。
2.线程:线程是操作系统中调度执行的基本单位。一个线程是一个“执行流”,每个线程之间都可以按照顺序执行自己的代码,多个线程“同时”执行多份代码。
进程和线程之间的区别与联系
1.一个进程可以包含多个线程,线程在进程的内部。
2.进程之间的资源是独立的,线程之间的资源则是共享的。
每个进程都有独立的虚拟地址空间,也有之间独立的文件描述符表,同一进程的多个线程之间则共用这一份虚拟地址空间和文件描述符表。
3.进程是操作系统中资源分配的基本单位,线程是操作系统中调度执行的基本单位。
4.多个进程同时执行时,如果一个进程崩溃,一般不会影响其他进程,而同一进程内的多个线程之间,如果一个线程崩溃,很可能使得整个进程崩溃。
5.进程的上下文切换速度比较慢,而线程的上下文切换速度比较快。
6.进程的创建/销毁/调度开销大,线程的创建/销毁/调度开销相对少很多。
3D数学篇
- 四元数是什么?主要作用什么?对欧拉角的优点是什么?
答:
所谓四元数,就是把4个实数组合起来的东西。4个元素中,一个是实部,其余3个是虚部
作用:四元数用于表示旋转
优点:
1)能进行增量旋转
2)避免万向锁
3)给定方位的表达方式有两种,互为负(欧拉角有无数种表达方式)
四元数不会有欧拉角存在的 gimbal lock 问题[万向节死锁]
四元数由4个数组成,旋转矩阵需要9个数
两个四元数之间更容易插值
四元数、矩阵在多次运算后会积攒误差,需要分别对其做规范化(normalize)和正交化 (orthogonalize),对四元数规范化更容易
与旋转矩阵类似,两个四元组相乘可表示两次旋转
- 向量的点乘、叉乘以及归一化的意义?
答:
1)点乘计算两个向量之间的夹角,还可表示某一方向的投影。
2)叉乘得到的是法向量。
3)标准化向量:用在只关系方向,不关心大小的时候。
Shader篇
- 写出光照计算中的diffuse的计算公式
答:
实际光照强度l=环境光(lambient)+漫反射光(Idiffuse)+镜面高光(lspecular)
环境光:lambient=环境光强度(Aintensity)*环境光颜色(Acolor)
漫反射光:ldiffuse=镜面光照强度(Dintensity)镜面光颜色(Scolor)(光的反射向量(R).观察者向量(V))^镜面光指数(n)
- MeshRender中material和shader的区别?
答:
MeshRender是模型渲染的组件,有此组件物体才能显示出来
Material是材质球,实际就是shader的实例,并进行赋值,贴图、纹理、颜色等。
Shader是着色器,实际上是一段程序,还可以用来实现一些仅靠贴图不容易实现的效果,如玻璃。
Shader大致分为:
1.表面着色器
2.顶点和片元着色器
3.固定功能着色器
- alpha blend工作原理
答:
Alpha Blend是 实现透明效果,Color = 原颜色alpha/255+目标色(255-alpha)/255
- 光照贴图 的优势是什么?
答:
1.使用光照贴图比使用实时光源渲染要快
2.可以降低游戏内存消耗
3.多个物体可以使用同一张光照贴图