转载于https://github.com/poetries/FE-Interview-Questions,by poetriesnode
1.1 面向报文git
UDP
是一个面向报文(报文能够理解为一段段的数据)的协议。意思就是UDP
只是报文的搬运工,不会对报文进行任何拆分和拼接操做github
具体来讲面试
UDP
协议,UDP
只会给数据增长一个 UDP
头标识下是 UDP
协议,而后就传递给网络层了UDP
只去除 IP
报文头就传递给应用层,不会任何拼接操做1.2 不可靠性算法
UDP
是无链接的,也就是说通讯不须要创建和断开链接。UDP
也是不可靠的。协议收到什么数据就传递什么数据,而且也不会备份数据,对方能不能收到是不关心的UDP
没有拥塞控制,一直会以恒定的速度发送数据。即便网络条件很差,也不会对发送速率进行调整。这样实现的弊端就是在网络条件很差的状况下可能会致使丢包,可是优势也很明显,在某些实时性要求高的场景(好比电话会议)就须要使用 UDP 而不是 TCP
1.3 高效数组
UDP
没有 TCP
那么复杂,须要保证数据不丢失且有序到达。因此 UDP
的头部开销小,只有八字节,相比 TCP
的至少二十字节要少得多,在传输数据报文时是很高效的头部包含了如下几个数据浏览器
IPv4
可选 字段),该字段用于发现头部信息和数据中的错误1.4 传输方式缓存
UDP
不止支持一对一的传输方式,一样支持一对多,多对多,多对一的方式,也就是说 UDP 提供了单播,多播,广播的功能安全
2.1 头部服务器
TCP
头部比UDP
头部复杂的多
对于
TCP
头部来讲,如下几个字段是很重要的
Sequence number
,这个序号保证了 TCP
传输的报文都是有序的,对端能够经过序号顺序的拼接报文Acknowledgement Number
,这个序号表示数据接收端指望接收的下一个字节的编号是多少,同时也表示上一个序号的数据已经收到Window Size
,窗口大小,表示还能接收多少字节的数据,用于流量控制标识符
URG=1
:该字段为一表示本数据报的数据部分包含紧急信息,是一个高优先级数据报文,此时紧急指针有效。紧急数据必定位于当前数据包数据部分的最前面,紧急指针标明了紧急数据的尾部。ACK=1
:该字段为一表示确认号字段有效。此外,TCP
还规定在链接创建后传送的全部报文段都必须把 ACK
置为一 PSH=1
:该字段为一表示接收端应该当即将数据 push 给应用层,而不是等到缓冲区满后再提交。RST=1
:该字段为一表示当前 TCP
链接出现严重问题,可能须要从新创建 TCP
链接,也能够用于拒绝非法的报文段和拒绝链接请求。SYN=1
:当SYN=1
,ACK=0
时,表示当前报文段是一个链接请求报文。当SYN=1
,ACK=1
时,表示当前报文段是一个赞成创建链接的应答报文。FIN=1
:该字段为一表示此报文段是一个释放链接的请求报文2.2 状态机
HTTP
是无链接的,因此做为下层的TCP
协议也是无链接的,虽然看似TCP
将两端链接了起来,可是其实只是两端共同维护了一个状态
TCP
的状态机是很复杂的,而且与创建断开链接时的握手息息相关,接下来就来详细描述下两种握手。创建链接三次握手
TCP
协议中,主动发起请求的一端为客户端,被动链接的一端称为服务端。无论是客户端仍是服务端,TCP
链接创建完后都能发送和接收数据,因此 TCP
也是一个全双工的协议。CLOSED
状态。在通讯开始前,双方都会建立 TCB
。 服务器建立完 TCB
后遍进入 LISTEN
状态,此时开始等待客户端发送数据第一次握手
客户端向服务端发送链接请求报文段。该报文段中包含自身的数据通信初始序号。请求发送后,客户端便进入 SYN-SENT 状态,x 表示客户端的数据通讯初始序号。
第二次握手
服务端收到链接请求报文段后,若是赞成链接,则会发送一个应答,该应答中也会包含自身的数据通信初始序号,发送完成后便进入
SYN-RECEIVED
状态。
第三次握手
当客户端收到链接赞成的应答后,还要向服务端发送一个确认报文。客户端发完这个报文段后便进入
ESTABLISHED
状态,服务端收到这个应答后也进入ESTABLISHED
状态,此时链接创建成功。
TCP
快速打开(TFO
)技术。其实只要涉及到握手的协议,均可以使用相似 TFO
的方式,客户端和服务端存储相同 cookie
,下次握手时发出 cookie
达到减小 RTT
的目的你是否有疑惑明明两次握手就能够创建起链接,为何还须要第三次应答?
能够想象以下场景。客户端发送了一个链接请求 A,可是由于网络缘由形成了超时,这时 TCP 会启动超时重传的机制再次发送一个链接请求 B。此时请求顺利到达服务端,服务端应答完就创建了请求。若是链接请求 A 在两端关闭后终于抵达了服务端,那么这时服务端会认为客户端又须要创建 TCP 链接,从而应答了该请求并进入
ESTABLISHED
状态。此时客户端实际上是 CLOSED 状态,那么就会致使服务端一直等待,形成资源的浪费
PS:在创建链接中,任意一端掉线,TCP 都会重发 SYN 包,通常会重试五次,在创建链接中可能会遇到 SYN FLOOD 攻击。遇到这种状况你能够选择调低重试次数或者干脆在不能处理的状况下拒绝请求
断开连接四次握手
TCP
是全双工的,在断开链接时两端都须要发送FIN
和ACK
。
第一次握手
若客户端 A 认为数据发送完成,则它须要向服务端 B 发送链接释放请求。
第二次握手
B 收到链接释放请求后,会告诉应用层要释放 TCP 连接。而后会发送 ACK 包,并进入 CLOSE_WAIT 状态,表示 A 到 B 的链接已经释放,不接收 A 发的数据了。可是由于 TCP 链接时双向的,因此 B 仍旧能够发送数据给 A。
第三次握手
B 若是此时还有没发完的数据会继续发送,完毕后会向 A 发送链接释放请求,而后 B 便进入 LAST-ACK 状态。
PS:经过延迟确认的技术(一般有时间限制,不然对方会误认为须要重传),能够将第二次和第三次握手合并,延迟 ACK 包的发送。
第四次握手
为何 A 要进入 TIME-WAIT 状态,等待 2MSL 时间后才进入 CLOSED 状态?
HTTP
协议是个无状态协议,不会保存状态
3.1 Post 和 Get 的区别
Get
请求能缓存,Post
不能Post
相对 Get
安全一点点,由于Get
请求都包含在 URL
里,且会被浏览器保存历史纪录,Post
不会,可是在抓包的状况下都是同样的。Post
能够经过 request body
来传输比 Get
更多的数据,Get
没有这个技术URL
有长度限制,会影响 Get
请求,可是这个长度限制是浏览器规定的,不是 RFC
规定的Post
支持更多的编码类型且不对数据类型限制3.2 常见状态码
2XX 成功
200 OK
,表示从客户端发来的请求在服务器端被正确处理204 No content
,表示请求成功,但响应报文不含实体的主体部分205 Reset Content
,表示请求成功,但响应报文不含实体的主体部分,可是与 204
响应不一样在于要求请求方重置内容206 Partial Content
,进行范围请求3XX 重定向
301 moved permanently
,永久性重定向,表示资源已被分配了新的 URL302 found
,临时性重定向,表示资源临时被分配了新的 URL303 see other
,表示资源存在着另外一个 URL,应使用 GET 方法丁香获取资源304 not modified
,表示服务器容许访问资源,但因发生请求未知足条件的状况307 temporary redirect
,临时重定向,和302含义相似,可是指望客户端保持请求方法不变向新的地址发出请求4XX 客户端错误
400 bad request
,请求报文存在语法错误401 unauthorized
,表示发送的请求须要有经过 HTTP
认证的认证信息403 forbidden
,表示对请求资源的访问被服务器拒绝404 not found
,表示在服务器上没有找到请求的资源5XX 服务器错误
500 internal sever error
,表示服务器端在执行请求时发生了错误501 Not Implemented
,表示服务器不支持当前请求所须要的某个功能503 service unavailable
,代表服务器暂时处于超负载或正在停机维护,没法处理请求3.3 HTTP 首部
通用字段 | 做用 |
---|---|
Cache-Control |
控制缓存的行为 |
Connection |
浏览器想要优先使用的链接类型,好比 keep-alive |
Date |
建立报文时间 |
Pragma |
报文指令 |
Via |
代理服务器相关信息 |
Transfer-Encoding |
传输编码方式 |
Upgrade |
要求客户端升级协议 |
Warning |
在内容中可能存在错误 |
请求字段 | 做用 |
---|---|
Accept |
能正确接收的媒体类型 |
Accept-Charset |
能正确接收的字符集 |
Accept-Encoding |
能正确接收的编码格式列表 |
Accept-Language |
能正确接收的语言列表 |
Expect |
期待服务端的指定行为 |
From |
请求方邮箱地址 |
Host |
服务器的域名 |
If-Match |
两端资源标记比较 |
If-Modified-Since |
本地资源未修改返回 304(比较时间) |
If-None-Match |
本地资源未修改返回 304(比较标记) |
User-Agent |
客户端信息 |
Max-Forwards |
限制可被代理及网关转发的次数 |
Proxy-Authorization |
向代理服务器发送验证信息 |
Range |
请求某个内容的一部分 |
Referer |
表示浏览器所访问的前一个页面 |
TE |
传输编码方式 |
响应字段 | 做用 |
---|---|
Accept-Ranges |
是否支持某些种类的范围 |
Age |
资源在代理缓存中存在的时间 |
ETag |
资源标识 |
Location |
客户端重定向到某个 URL |
Proxy-Authenticate |
向代理服务器发送验证信息 |
Server |
服务器名字 |
WWW-Authenticate |
获取资源须要的验证信息 |
实体字段 | 做用 |
---|---|
Allow |
资源的正确请求方式 |
Content-Encoding |
内容的编码格式 |
Content-Language |
内容使用的语言 |
Content-Length |
request body 长度 |
Content-Location |
返回数据的备用地址 |
Content-MD5 |
Base64 加密格式的内容MD5 检验值 |
Content-Range |
内容的位置范围 |
Content-Type |
内容的媒体类型 |
Expires |
内容的过时时间 |
Last_modified |
内容的最后修改时间 |
DNS 的做用就是经过域名查询到具体的 IP。
在
TCP
握手以前就已经进行了DNS
查询,这个查询是操做系统本身作的。当你在浏览器中想访问www.google.com
时,会进行一下操做
以上介绍的是 DNS 迭代查询,还有种是递归查询,区别就是前者是由客户端去作请求,后者是由系统配置的 DNS 服务器作请求,获得结果后将数据返回给客户端。
概念
实现
每种数据结构均可以用不少种方式来实现,其实能够把栈当作是数组的一个子集,因此这里使用数组来实现
class Stack { constructor() { this.stack = [] } push(item) { this.stack.push(item) } pop() { this.stack.pop() } peek() { return this.stack[this.getCount() - 1] } getCount() { return this.stack.length } isEmpty() { return this.getCount() === 0 } }
应用
匹配括号,能够经过栈的特性来完成
var isValid = function (s) { let map = { '(': -1, ')': 1, '[': -2, ']': 2, '{': -3, '}': 3 } let stack = [] for (let i = 0; i < s.length; i++) { if (map[s[i]] < 0) { stack.push(s[i]) } else { let last = stack.pop() if (map[last] + map[s[i]] != 0) return false } } if (stack.length > 0) return false return true };
概念
队列一个线性结构,特色是在某一端添加数据,在另外一端删除数据,遵循先进先出的原则
实现
这里会讲解两种实现队列的方式,分别是单链队列和循环队列
class Queue { constructor() { this.queue = [] } enQueue(item) { this.queue.push(item) } deQueue() { return this.queue.shift() } getHeader() { return this.queue[0] } getLength() { return this.queue.length } isEmpty() { return this.getLength() === 0 } }
由于单链队列在出队操做的时候须要
O(n)
的时间复杂度,因此引入了循环队列。循环队列的出队操做平均是O(1)
的时间复杂度
class SqQueue { constructor(length) { this.queue = new Array(length + 1) // 队头 this.first = 0 // 队尾 this.last = 0 // 当前队列大小 this.size = 0 } enQueue(item) { // 判断队尾 + 1 是否为队头 // 若是是就表明须要扩容数组 // % this.queue.length 是为了防止数组越界 if (this.first === (this.last + 1) % this.queue.length) { this.resize(this.getLength() * 2 + 1) } this.queue[this.last] = item this.size++ this.last = (this.last + 1) % this.queue.length } deQueue() { if (this.isEmpty()) { throw Error('Queue is empty') } let r = this.queue[this.first] this.queue[this.first] = null this.first = (this.first + 1) % this.queue.length this.size-- // 判断当前队列大小是否太小 // 为了保证不浪费空间,在队列空间等于总长度四分之一时 // 且不为 2 时缩小总长度为当前的一半 if (this.size === this.getLength() / 4 && this.getLength() / 2 !== 0) { this.resize(this.getLength() / 2) } return r } getHeader() { if (this.isEmpty()) { throw Error('Queue is empty') } return this.queue[this.first] } getLength() { return this.queue.length - 1 } isEmpty() { return this.first === this.last } resize(length) { let q = new Array(length) for