面试问题总结

Posted by Mas9uerade on January 7, 2021

TCP/HTTP区别

TCP协议对应于传输层,而HTTP协议对应于应用层,从本质上来说,二者没有可比性。Http协议是建立在TCP协议基础之上的,当浏览器需要从服务器获取网页数据的时候,会发出一次Http请求。

Http会通过TCP建立起一个到服务器的连接通道,当本次请求需要的数据完毕后,Http会立即将TCP连接断开,这个过程是很短的。所以Http连接是一种短连接,是一种无状态的连接。所谓的无状态,是指浏览器每次向服务器发起请求的时候,不是通过一个连接,而是每次都建立一个新的连接。如果是一个连接的话,服务器进程中就能保持住这个连接并且在内存中记住一些信息状态。而每次请求结束后,连接就关闭,相关的内容就释放了,所以记不住任何状态,成为无状态连接。

随着时间的推移,html页面变得复杂了,里面可能嵌入了很多图片,这时候每次访问图片都需要建立一次tcp连接就显得低效了。因此Keep-Alive被提出用来解决效率低的问题。从HTTP/1.1起,默认都开启了Keep-Alive,保持连接特性,简单地说,当一个网页打开完成后,客户端和服务器之间用于传输HTTP数据的TCP连接不会关闭,如果客户端再次访问这个服务器上的网页,会继续使用这一条已经建立的连接Keep-Alive不会永久保持连接,它有一个保持时间,可以在不同的服务器软件(如Apache)中设定这个时间。

虽然这里使用TCP连接保持了一段时间,但是这个时间是有限范围的,到了时间点依然是会关闭的,所以我们还把其看做是每次连接完成后就会关闭。后来,通过Session, Cookie等相关技术,也能保持一些用户的状态。但是还是每次都使用一个连接,依然是无状态连接。

HTTP是一个属于应用层的面向对象的协议,由于其简捷、快速的方式,适用于分布式超媒体信息系统。它于1990年提出,经过几年的使用与发展,得到不断地完善和扩展。目前在WWW中使用的是HTTP/1.0的第六版,HTTP/1.1的规范化工作正在进行之中,而且HTTP-NG(Next Generation of HTTP)的建议已经提出。 HTTP协议的主要特点可概括如下: 1.支持客户/服务器模式。 2.简单快速:客户向服务器请求服务时,只需传送请求方法和路径。请求方法常用的有GET、HEAD、POST。每种方法规定了客户与服务器联系的类型不同。由于HTTP协议简单,使得HTTP服务器的程序规模小,因而通信速度很快。 3.灵活:HTTP允许传输任意类型的数据对象。正在传输的类型由Content-Type加以标记。 4.无连接:无连接的含义是限制每次连接只处理一个请求。服务器处理完客户的请求,并收到客户的应答后,即断开连接。采用这种方式可以节省传输时间。 5.无状态:HTTP协议是无状态协议。无状态是指协议对于事务处理没有记忆能力。缺少状态意味着如果后续处理需要前面的信息,则它必须重传,这样可能导致每次连接传送的数据量增大。另一方面,在服务器不需要先前信息时它的应答就较快。

TCP/UDP 区别

1. 对比

  UDP TCP
是否连接 无连接 面向连接
是否可靠 不可靠传输,不使用流量控制和拥塞控制 可靠传输,使用流量控制和拥塞控制
连接对象个数 支持一对一,一对多,多对一和多对多交互通信 只能是一对一通信
传输方式 面向报文 面向字节流
首部开销 首部开销小,仅8字节 首部最小20字节,最大60字节
适用场景 适用于实时应用(IP电话、视频会议、直播等) 适用于要求可靠传输的应用,例如文件传输
  1. TCP是需要进行连接的,UDP是无连接通信;
  2. UDP只保证发送的数据的完整性,没有应答机制,不保证数据的送达,因此会存在丢包,乱序的问题;
  3. TCP有拥塞控制,UDP没有;

2. 总结

  • TCP向上层提供面向连接的可靠服务 ,UDP向上层提供无连接不可靠服务。
  • 虽然 UDP 并没有 TCP 传输来的准确,但是也能在很多实时性要求高的地方有所作为
  • 对数据准确性要求高,速度可以相对较慢的,可以选用TCP

1. TCP连接过程

如下图所示,可以看到建立一个TCP连接的过程为(三次握手的过程):

img

第一次握手

客户端向服务端发送连接请求报文段。该报文段中包含自身的数据通讯初始序号。请求发送后,客户端便进入 SYN-SENT 状态。

第二次握手

服务端收到连接请求报文段后,如果同意连接,则会发送一个应答,该应答中也会包含自身的数据通讯初始序号,发送完成后便进入 SYN-RECEIVED 状态。

第三次握手

当客户端收到连接同意的应答后,还要向服务端发送一个确认报文。客户端发完这个报文段后便进入 ESTABLISHED 状态,服务端收到这个应答后也进入 ESTABLISHED 状态,此时连接建立成功。

这里可能大家会有个疑惑:为什么 TCP 建立连接需要三次握手,而不是两次?这是因为这是为了防止出现失效的连接请求报文段被服务端接收的情况,从而产生错误。

img

2. TCP断开链接

img

TCP 是全双工的,在断开连接时两端都需要发送 FIN 和 ACK。

第一次握手

若客户端 A 认为数据发送完成,则它需要向服务端 B 发送连接释放请求。

第二次握手

B 收到连接释放请求后,会告诉应用层要释放 TCP 链接。然后会发送 ACK 包,并进入 CLOSE_WAIT 状态,此时表明 A 到 B 的连接已经释放,不再接收 A 发的数据了。但是因为 TCP 连接是双向的,所以 B 仍旧可以发送数据给 A。

第三次握手

B 如果此时还有没发完的数据会继续发送,完毕后会向 A 发送连接释放请求,然后 B 便进入 LAST-ACK 状态。

第四次握手

A 收到释放请求后,向 B 发送确认应答,此时 A 进入 TIME-WAIT 状态。该状态会持续 2MSL(最大段生存期,指报文段在网络中生存的时间,超时会被抛弃) 时间,若该时间段内没有 B 的重发请求的话,就进入 CLOSED 状态。当 B 收到确认应答后,也便进入 CLOSED 状态。

TCP的滑动窗口

作者:wuxinliulei 链接:https://www.zhihu.com/question/32255109/answer/68558623 来源:知乎 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

1)TCP滑动窗口分为接受窗口,发送窗口 滑动窗口协议是传输层进行流控的一种措施,接收方通过通告发送方自己的窗口大小,从而控制发送方的发送速度,从而达到防止发送方发送速度过快而导致自己被淹没的目的。

对ACK的再认识,ack通常被理解为收到数据后给出的一个确认ACK,ACK包含两个非常重要的信息: 一是期望接收到的下一字节的序号n,该n代表接收方已经接收到了前n-1字节数据,此时如果接收方收到第n+1字节数据而不是第n字节数据,接收方是不会发送序号为n+2的ACK的。举个例子,假如接收端收到1-1024字节,它会发送一个确认号为1025的ACK,但是接下来收到的是2049-3072,它是不会发送确认号为3072的ACK,而依旧发送1025的ACK。

二是当前的窗口大小m,如此发送方在接收到ACK包含的这两个数据后就可以计算出还可以发送多少字节的数据给对方,假定当前发送方已发送到第x字节,则可以发送的字节数就是y=m-(x-n).这就是滑动窗口控制流量的基本原理

重点:发送方根据收到ACK当中的期望收到的下一个字节的序号n以及窗口m,还有当前已经发送的字节序号x,算出还可以发送的字节数。

发送端窗口的第一个字节序号一定是ACK中期望收到的下一个字节序号,比如下图: img上图52 53 54 55 字节都是可以新发送的字节序

接受端窗口的第一个字节序之前一定是已经完全接收的,后面窗口里面的数据都是希望接受的,窗口后面的数据都是不希望接受的。

http://blog.chinaunix.net/uid-20778955-id-539945.html

http://www.netis.com.cn/flows/2012/08/tcp-%E6%BB%91%E
5%8A%A8%E7%AA%97%E5%8F%A3%E7%9A%84%E7%AE%80%E4%BB%8B/

TCP的滑动窗口分为接收窗口和发送窗口 不分析这两种窗口就讨论是不妥当的。

TCP的滑动窗口主要有两个作用,一是提供TCP的可靠性,二是提供TCP的流控特性。同时滑动窗口机制还体现了TCP面向字节流的设计思路。TCP 段中窗口的相关字段。

<img src=”https://pic3.zhimg.com/50/d6b970fb6d44aafeeec4a4c9d61a9225_hd.jpg?source=1940ef5c” data-rawwidth=”620” data-rawheight=”197” class=”origin_image zh-lightbox-thumb” width=”620” data-original=”https://pic1.zhimg.com/d6b970fb6d44aafeeec4a4c9d61a9225_r.jpg?source=1940ef5c”/>img

TCP的Window是一个16bit位字段,它代表的是窗口的字节容量,也就是TCP的标准窗口最大为2^16-1=65535个字节。

另外在TCP的选项字段中还包含了一个TCP窗口扩大因子,option-kind为3,option-length为3个字节,option-data取值范围0-14。窗口扩大因子用来扩大TCP窗口,可把原来16bit的窗口,扩大为31bit。

滑动窗口基本原理

1)对于TCP会话的发送方,任何时候在其发送缓存内的数据都可以分为4类,“已经发送并得到对端ACK的”,“已经发送但还未收到对端ACK的”,“未发送但对端允许发送的”,“未发送且对端不允许发送”。“已经发送但还未收到对端ACK的”和“未发送但对端允许发送的”这两部分数据称之为发送窗口。

<img src=”https://pic1.zhimg.com/50/a1d5c050ad957880094a5f003b1ccd24_hd.jpg?source=1940ef5c” data-rawwidth=”664” data-rawheight=”177” class=”origin_image zh-lightbox-thumb” width=”664” data-original=”https://pic1.zhimg.com/a1d5c050ad957880094a5f003b1ccd24_r.jpg?source=1940ef5c”/>img

当收到接收方新的ACK对于发送窗口中后续字节的确认是,窗口滑动,滑动原理如下图。

<img src=”https://pic4.zhimg.com/50/9c21786770459afa47bfa2e4606cc454_hd.jpg?source=1940ef5c” data-rawwidth=”667” data-rawheight=”216” class=”origin_image zh-lightbox-thumb” width=”667” data-original=”https://pic4.zhimg.com/9c21786770459afa47bfa2e4606cc454_r.jpg?source=1940ef5c”/>img

当收到ACK=36时窗口滑动。

2)对于TCP的接收方,在某一时刻在它的接收缓存内存在3种。“已接收”,“未接收准备接收”,“未接收并未准备接收”(由于ACK直接由TCP协议栈回复,默认无应用延迟,不存在“已接收未回复ACK”)。其中“未接收准备接收”称之为接收窗口。

发送窗口与接收窗口关系

TCP是双工的协议,会话的双方都可以同时接收、发送数据。TCP会话的双方都各自维护一个“发送窗口”和一个“接收窗口”。其中各自的“接收窗口”大小取决于应用、系统、硬件的限制(TCP传输速率不能大于应用的数据处理速率)。各自的“发送窗口”则要求取决于对端通告的“接收窗口”,要求相同。

<img src=”https://pic4.zhimg.com/50/c798dd393fcf7c03b1db78f5bcf0304b_hd.jpg?source=1940ef5c” data-rawwidth=”675” data-rawheight=”527” class=”origin_image zh-lightbox-thumb” width=”675” data-original=”https://pic1.zhimg.com/c798dd393fcf7c03b1db78f5bcf0304b_r.jpg?source=1940ef5c”/>img

滑动窗口实现面向流的可靠性

1)最基本的传输可靠性来源于“确认重传”机制。

2)TCP的滑动窗口的可靠性也是建立在“确认重传”基础上的。

3)发送窗口只有收到对端对于本段发送窗口内字节的ACK确认,才会移动发送窗口的左边界。

4)接收窗口只有在前面所有的段都确认的情况下才会移动左边界。当在前面还有字节未接收但收到后面字节的情况下,窗口不会移动,并不对后续字节确认。以此确保对端会对这些数据重传。

滑动窗口的流控特性

TCP的滑动窗口是动态的,我们可以想象成小学常见的一个数学题,一个水池,体积V,每小时进水量V1,出水量V2。当水池满了就不允许再注入了,如果有个液压系统控制水池大小,那么就可以控制水的注入速率和量。这样的水池就类似TCP的窗口。应用根据自身的处理能力变化,通过本端TCP接收窗口大小控制来对对对端的发送窗口流量限制。

应用程序在需要(如内存不足)时,通过API通知TCP协议栈缩小TCP的接收窗口。然后TCP协议栈在下个段发送时包含新的窗口大小通知给对端,对端按通知的窗口来改变发送窗口,以此达到减缓发送速率的目的。

Unity DTOS

ECS

ECS+JOB System 可以

Brust

AssetBundle

https://www.cnblogs.com/wang-jin-fu/p/11171626.html

IL2CPP(Intermediate Language)

在Unity博客和本文中,IL和CIL表示的是同一个东西:翻译过来就是中间语言。它是一种属于 通用语言架构和.NET框架的低阶(lowest-level)的人类可读的编程语言。目标为.NET框架的语言被编译成CIL,然后汇编成字节码。 CIL类似一个面向对象的汇编语言,并且它是完全基于堆栈的,它运行在虚拟机上(.Net Framework, Mono VM)的语言。 具体过程是:C#或者VB这样遵循CLI规范的高级语言,被先被各自的编译器编译成中间语言:IL(CIL),等到需要真正执行的时候,这些IL会被加载到运行时库,也就是VM中,由VM动态的编译成汇编代码(JIT)然后在执行。

img

正是由于引入了VM,才使得很多动态代码特性得以实现。通过VM我们甚至可以由代码在运行时生成新代码并执行。这个是静态编译语言所无法做到的。回到上一 节我说的Boo和Unity Script,有了IL和VM的概念我们就不难发现,这两者并没有对应的VM虚拟机,Unity中VM只有一个:Mono VM,也就是说Boo和Unity Script是被各自的编译器编译成遵循CLI规范的IL,然后再由Mono VM解释执行的。这也是Unity Script和JavaScript的根本区别。JavaScript是最终在浏览器的JS解析器中运行的(例如大名鼎鼎的Google Chrome V8引擎),而Unity Script是在Mono VM中运行的。本质上说,到了IL这一层级,它是由哪门高级语言创建的也不是那么重要了,你可以用C#,VB,Boo,Unity Script甚至C++,只要有相应的编译器能够将其编译成IL都行!

本 文的主角终于出来了:IL2CPP。有了上面的知识,大家很容易就理解其意义了:把IL中间语言转换成CPP文件。大家如果看明白了上面动态语言的 CLI, IL以及VM,再看到IL2CPP一定心中充满了疑惑。现在的大趋势都是把语言加上动态特性,哪怕是c++这样的静态语言,也出现了适合IL的c++编译 器,为啥Unity要反其道而行之,把IL再弄回静态的CPP呢?这不是吃饱了撑着嘛。根据本文最前面给出的Unity官方博客所解释的,原因有以下几 个: 1.Mono VM在各个平台移植,维护非常耗时,有时甚至不可能完成 Mono的跨平台是通过Mono VM实现的,有几个平台,就要实现几个VM,像Unity这样支持多平台的引擎,Mono官方的VM肯定是不能满足需求的。所以针对不同的新平 台,Unity的项目组就要把VM给移植一遍,同时解决VM里面发现的bug。这非常耗时耗力。这些能移植的平台还好说,还有比如WebGL这样基于浏览 器的平台。要让WebGL支持Mono的VM几乎是不可能的。 2.Mono版本授权受限 大家有没有意识到Mono的版本已经更新到3.X了,但是在Unity中,C#的运行时版本一直停留在2.8,这也是Unity社区开发者抱怨的最多一 条:很多C#的新特性无法使用。这是因为Mono 授权受限,导致Unity无法升级Mono。如果换做是IL2CPP,IL2CPP VM这套完全自己开发的组件,就解决了这个问题。 3.提高运行效率 根据官方的实验数据,换成IL2CPP以后,程序的运行效率有了1.5-2.0倍的提升。

使用Mono的时候,脚本的编译运行如下图所示:

img

简单的来说,3大脚本被编译成IL,在游戏运行的时候,IL和项目里其他第三方兼容的DLL一起,放入Mono VM虚拟机,由虚拟机解析成机器码,并且执行 IL2CPP做的改变由下图红色部分标明:

img

在得到中间语言IL后,使用IL2CPP将他们重新变回C++代码,然后再由各个平台的C++编译器直接编译成能执行的原生汇编代码。

几点注意: 1.将IL变回CPP的目的除了CPP的执行效率快以外,另一个很重要的原因是可以利用现成的在各个平台的C++编译器对代码执行编译期优化,这样可以进一步减小最终游戏的尺寸并提高游戏运行速度。

\2. 由于动态语言的特性,他们多半无需程序员太多关心内存管理,所有的内存分配和回收都由一个叫做GC(Garbage Collector)的组件完成。虽然通过IL2CPP以后代码变成了静态的C++,但是内存管理这块还是遵循C#的方式,这也是为什么最后还要有一个 IL2CPP VM的原因:它负责提供诸如GC管理,线程创建这类的服务性工作。但是由于去除了IL加载和动态解析的工作,使得IL2CPP VM可以做的很小,并且使得游戏载入时间缩短。

3.由于C++是一门静态语言,这就意味着我们不能使用动态语言的那些酷炫特性。运行时生 成代码并执行肯定是不可能了。这就是Unity里面提到的所谓AOT(Ahead Of Time)编译而非JIT(Just In Time)编译。其实很多平台出于安全的考虑是不允许JIT的,大家最熟悉的有iOS平台,在Console游戏机上,不管是微软的Xbox360, XboxOne,还是Sony的PS3,PS4,PSV,没有一个是允许JIT的。使用了IL2CPP,就完全是AOT方式了,如果原来使用了动态特性的 代码肯定会编译失败。这些代码在编译iOS平台的时候天生也会失败,所以如果你是为iOS开发的游戏代码,就不用担心了。因此就这点而言,我们开发上几乎 不会感到什么问题。

最后给出Unite 2014上官方给出的性能测试截图(数字越小表示运行得越快):

img

img

有了IL2CPP,程序尺寸可以相对缩小,运行速度可以提高!看了兴奋吗?其实现有的Unity版本中已经引入了IL2CPP技术。本文下篇就通过一个实际的例子,看看IL2CPP都为我们做了哪些,以及我们需要注意些什么。

Update FixedUpdate

使用了哪些设计模式

工具设计

Unity Canvas

Batch的规则

https://zhuanlan.zhihu.com/p/103612944

https://blog.csdn.net/akak2010110/article/details/80953370

Dictionary

1. 哈希冲突情况下的优化方案 - > 当链表长度大于8的时候,转红黑树

List

进程间通信方式