1、tcp协议简析

概述

传输控制协议(TCP,Transmission Control Protocol)是一种面向连接的、可靠的、基于字节流的传输层通信协议,由IETF的RFC 793 定义。

  • TCP 提供一种面向连接的、可靠的字节流服务
  • 在一个 TCP 连接中,仅有两方进行彼此通信。广播和多播不能用于 TCP
  • TCP 使用校验和,确认和重传机制来保证可靠传输
  • TCP 给数据分节进行排序,并使用累积确认保证数据的顺序不变和非重复
  • TCP 使用滑动窗口机制来实现流量控制,通过动态改变窗口的大小进行拥塞控制

注意:TCP 并不能保证数据一定会被对方接收到,因为这是不可能的。TCP 能够做到的是,如果有可能,就把数据递送到接收方,否则就(通过放弃重传并且中断连接这一手段)通知用户。因此准确说 TCP 也不是 100% 可靠的协议,它所能提供的是数据的可靠递送或故障的可靠通知

报文结构分析

image.png

源端口

第一个字段是 源端口source port ),它的长度为 16位,表示报文 发送方 的端口号。

目的端口

第二个字段是 目的端口destination port ),它的长度为 16 位,表示报文 接收方 的端口号。

序号

序号sequence number )字段,长度为 32 位,表示数据首字节的序号(当前发送方发送的是第多少个字节)。在三次握手阶段,SYN 指令也是通过该字段,将本端选定的 起始序号 告诉接收方。

确认号

确认号acknowledgement number )字段,长度为 32 位。它表示已确认收到的数据序号,它的值为:已收到数据最后一个字节的序号加一,即接收方期望进一步接收的数据序号。

假如客户端上次发送的seq=1000,则服务端返回返回一个报文ack=1001,代表告诉客户端,我已经接收到你1000个字节了,下次你从第1001个字节开始发送!

头部长度(数据偏移)

头部长度header length )字段,长度为 4 位,表示 TCP 报文头部的长度,也可称为 数据偏移data offset )。跟 IP 协议一样,TCP 头部长度字段也不是以字节为单位,而是以 15位bit(4字节)为单位。

之所以有数据偏移是因为tcp协议的头部长度是不固定的(因为可选字段【后面解释】的长度不固定),因此需要发送方表示一下,这样就可以知道,从多少字节位置开始解析 传输的数据。 4*15=60 因此tcp 的头部长度范围位 20~60 字节。

保留位

保留字段,6个bit位,至今没啥用。

紧急位URG

当URG=1时,表明紧急指针字段有效。因为TCP发送报文段,每一个报文段都包含TCP首部信息,因此也都包含URG标识。接收方TCP看到报文段中有URG=1的数据就会把这段报文往前排进行插队。

举一个例子:我们都用过xshell连接远程服务器查看日志,当日志在不停的输出的时候,我们使用Ctrl+C进行停止,这个时候发送的Ctrl+C的URG就等于1,告诉远程优先执行这个命令。

标志位

标志位flags ),长度为 6 位,用于保存一些标志位。

  • URG ,紧急数据指令,表示紧急指针有效,报文段包含高优先级数据(这个是告知发送方:我这里面是紧急数据,我要先被发送);
  • ACK ,确认指令,表示确认号有效,对已接收数据进行确认;
  • PSH ,立即推送指令,指示接收方立即将数据提交给应用层,不用等缓冲区装满(这个是告知接受方:我这里面是紧急数据,我要先被处理);
  • RST ,重置指令,表示出现严重错误,常用于拒绝非法报文段以及拒绝连接请求
  • SYN ,序号同步指令,在 TCP 三次握手建立连接时,将本端选定序号告诉对端(这个只在三次握手的前两个握手中有,代表连接请求)
  • FIN ,连接关闭指令,用于告诉对端,本端数据已发送完毕,连接关闭;

    窗口

    占2字节,用来控制对方发送的数据量。窗口字段反映了接收方接收缓存的可用空间大小

    校验和

    占2字节,由发送端填充。接收端对TCP报文段执行CRC算法以检验TCP报文段在传输过程中是否损坏
    注意:这个校验不仅包括TCP头部,也包括数据部分。这也是TCP可靠传输的一个重要保障。

    选项

    长度可变,一般在三次握手中有选项。每个选项的开始都是1个字节的kind说明,其他选项在kind字段后面还要len字节,说明总长度包括kind和len字节。

    三次握手

    三次握手的原文是 three-way handshake,整个名词的可以翻译为:需要三个步骤才能建立握手/连接的机制。当然,三次握手也可以叫 three-message handshake,通过三条消息来建立的握手/连接。
    image.png
    回顾一下图中字符的含义:

  • SYN:连接请求/接收 报文段

  • seq:发送的第一个字节的序号
  • ACK:确认报文段
  • ack:确认号。希望收到的下一个数据的第一个字节的序号

    第一次握手

    客户端向服务端发送一个 SYN 报文(SYN = 1),并指明客户端的初始化序列号 ISN(x),即图中的 seq = x,表示本报文段所发送的数据的第一个字节的序号。此时客户端处于 SYN_Send 状态。

    SYN-SENT :在发送连接请求后等待匹配的连接请求

第二次握手

服务器收到客户端的 SYN 报文之后,会发送 SYN 报文作为应答(SYN = 1),并且指定自己的初始化序列号 ISN(y),即图中的 seq = y。同时会把客户端的 seq + 1 作为确认号 ack 的值,表示已经收到了客户端发来的的 SYN 报文,希望收到的下一个数据的第一个字节的seq是 x + 1,此时服务器处于 SYN_REVD 的状态。

SYN-RECEIVED:在收到和发送一个连接请求后等待对连接请求的确认

第三次握手

客户端收到服务器端响应的 SYN 报文之后,会发送一个 ACK 报文(注意这时候已经没有SYN标志位了),也是一样把服务器的 seq+ 1 作为 ack 的值,表示已经收到了服务端发来的的 SYN 报文,希望收到的下一个数据的第一个字节的序号是 y + 1,并指明此时客户端的序列号 seq = x + 1(初始为 seq = x,所以第二个报文段要 +1),此时客户端处于 Establised 状态。
服务器收到 ACK 报文之后,也处于 Establised 状态,至此,双方建立起了 TCP 连接。

ESTABLISHED:代表一个打开的连接,数据可以传送给用户

可以看出:连接过程中有两个数据最值得引起注意:seq和ack。 seq:代表发送方接收方标识我当前给你的是我第多少个字节。 ack:是接受方发送方标识 你的收据我已收到,下次给我从多少字节开始发送关系: seq= 上次接受的ack ack=上次接受的seq+1


为啥是三次握手而不是两次?

先说结论:是为了防止已经失效的报文再次发起连接请求。

分析:
如果是两次握手就可以建立连接则是下面这种模型。

image.png

这样的服务端就太被动了,只要客户端发个连接请求我就连接,那我服务器岂不是很没有面子。
其实面子是小事,大事是可能造成服务器资源的浪费。
看下面例子:
image.png
如果客户端开始发送了连接请求1,但是在网络传输过程中由于某些原因,没有及时到达服务端,这个客户端迟迟没有接收到服务端的确定请求,则又发送了一个连接请求2,连接请求2很快到达了服务端,并且服务端做出了响应建立了连接,但是突然之前的连接请求1 历经千辛万苦也到达了服务端,服务端跟个傻小子一样,看到连接请求就答应,于是又给客户端发送了连接确定(服务端进入连接状态),但客户端接收到确认请求一看,这谁呀我不认识(因为客户端以为连接请求1已经死了),于是没进入连接,那么导致的结果就是服务器跟个冤大头一样搁哪里空等。

如果三次握手那会怎么样呢,当过期的连接请求1到达服务端后,服务端学聪明了,没有立刻进入连接状态,而是去找客户端确认了一下,此时客户端一看这个请求已经不认识了,那么也不会回复服务端,服务端等了会发现客户端没有回复,就知道这个请求是来招摇撞骗的(已经过期了),自然不会进入连接状态,那之前的问题就解决了。

说白了就是服务端学聪明了,不是啥阿猫阿狗的连接请求过来我都连接,我要去找客户端来给我确认一下请求合法我才连接。

四次挥手

image.png

第一次挥手

客户端发送一个 FIN 报文(请求连接终止:FIN = 1),报文中会指定一个序列号 seq = u。并停止再发送数据,主动关闭 TCP 连接。此时客户端处于 FIN_WAIT1 状态,等待服务端的确认。

FIN-WAIT-1 - 等待远程TCP的连接中断请求,或先前的连接中断请求的确认;

第二次挥手

服务端收到 FIN 之后,会发送 ACK 报文,且把客户端的seq +1 作为 ACK 报文的序列号值,表明已经收到客户端的报文了,此时服务端处于 CLOSE_WAIT 状态。

CLOSE-WAIT - 等待从本地用户发来的连接中断请求;

此时的 TCP 处于半关闭状态,客户端到服务端的连接释放。客户端收到服务端的确认后,进入FIN_WAIT2(终止等待 2)状态,等待服务端发出的连接释放报文段。

FIN-WAIT-2 - 从远程TCP等待连接中断请求;

注:这个报文只是服务端告诉客户端,我知道你要断不开连接了。 此时如果服务端还有数据要发送,还可以接着发送。

第三次挥手

如果服务端也想断开连接了(没有要向客户端发出的数据),和客户端的第一次挥手一样,发送 FIN 报文,且指定一个序列号。此时服务端处于 LAST_ACK 的状态,等待客户端的确认。

LAST-ACK - 等待原来发向远程TCP的连接中断请求的确认;

这个是在服务端传输完数据后,告诉客户端,我好了,我要准备断开了呦!

第四次挥手

客户端收到 FIN 之后,一样发送一个 ACK 报文作为应答(ack = w+1),且把服务端的序列值 +1 作为自己 ACK 报文的序号值(seq=u+1),此时客户端处于 TIME_WAIT (时间等待)状态

TIME-WAIT - 等待足够的时间以确保远程TCP接收到连接中断请求的确认;

🚨 注意 !!!这个时候由服务端到客户端的 TCP 连接并未释放掉需要经过时间等待计时器设置的时间 2MSL(一个报文的来回时间) 后才会进入 CLOSED 状态(这样做的目的是确保服务端收到自己的 ACK 报文。如果服务端在规定时间内没有收到客户端发来的 ACK 报文的话,服务端会重新发送 FIN 报文给客户端,客户端再次收到 FIN 报文之后,就知道之前的 ACK 报文丢失了,然后再次发送 ACK 报文给服务端)。服务端收到 ACK 报文之后,就关闭连接了,处于 CLOSED 状态。

挥手为啥要挥四次而不是三次?

由于 TCP 的半关闭(half-close)特性,TCP 提供了连接的一端在结束它的发送后还能接收来自另一端数据的能力。
任何一方都可以在数据传送结束后发出连接释放的通知,待对方确认后进入半关闭状态。当另一方也没有数据再发送的时候,则发出连接释放通知,对方确认后就完全关闭了TCP连接。
通俗的来说,两次握手就可以释放一端到另一端的 TCP 连接,完全释放连接一共需要四次握手

简单说就是挥四次是因为tcp的半连接特性,那再追问一下,tcp为什么要设计成半连接呢?
我的理解:还是出于可靠性考虑,避免浪费资源——避免对方空等!
假如我想断开就断开,那么这个断开的报文丢了怎么办?那就回造成我已经断开了,但是对方有没啥接收到断开报文,而一直在那空等,浪费了资源。

题外话:如果三次握手我们归结为服务器的睿智(不当傻小子),那么四次挥手我们就可以归结为负责,当然这个负责是为对方负责任。

抓包分析

抓包流程:

  • 终端使用命令 ping www.baidu.com拿到百度的ip
  • 在wireshark 的filter 栏输入ip.addr == 百度ip监听和百度的数据交互
  • 终端输入 curl www.baidu.com 命令请求百度首页数据
  • 在wireshark中进行观察

    三次握手

    image.png

    四次挥手

    image.png

    2、 HTTP 1.0 、 HTTP 1.1 、HTTP 2.0之间有哪些区别?

    HTTP 1.0和 HTTP 1.1 有以下区别:

  • 连接方面,http1.0 默认使用非持久连接,而 http1.1 默认使用持久连接。http1.1 通过使用持久连接来使多个 http 请求复用同一个 TCP 连接,以此来避免使用非持久连接时每次需要建立连接的时延。

  • 资源请求方面,在 http1.0 中,存在一些浪费带宽的现象,例如客户端只是需要某个对象的一部分,而服务器却将整个对象送过来了,并且不支持断点续传功能,http1.1 则在请求头引入了 range 头域,它允许只请求资源的某个部分,即返回码是 206(Partial Content),这样就方便了开发者自由的选择以便于充分利用带宽和连接。
  • 缓存方面,在 http1.0 中主要使用 header 里的 If-Modified-Since、Expires 来做为缓存判断的标准,http1.1 则引入了更多的缓存控制策略,例如 Etag、If-Unmodified-Since、If-Match、If-None-Match 等更多可供选择的缓存头来控制缓存策略。
  • http1.1 中新增了 host 字段,用来指定服务器的域名。http1.0 中认为每台服务器都绑定一个唯一的 IP 地址,因此,请求消息中的 URL 并没有传递主机名(hostname)。但随着虚拟主机技术的发展,在一台物理服务器上可以存在多个虚拟主机,并且它们共享一个IP地址。因此有了 host 字段,这样就可以将请求发往到同一台服务器上的不同网站。
  • http1.1 相对于 http1.0 还新增了很多请求方法,如 PUT、HEAD、OPTIONS 等。

    HTTP 1.1和 HTTP 2.0 的区别:

  • 二进制协议:HTTP/2 是一个二进制协议。在 HTTP/1.1 版中,报文的头信息必须是文本(ASCII 编码),数据体可以是文本,也可以是二进制。HTTP/2 则是一个彻底的二进制协议,头信息和数据体都是二进制,并且统称为”帧”,可以分为头信息帧和数据帧。 帧的概念是它实现多路复用的基础。

  • 多路复用:HTTP/2 实现了多路复用,HTTP/2 仍然复用 TCP 连接,但是在一个连接里,客户端和服务器都可以同时发送多个请求或回应,而且不用按照顺序一一发送,这样就避免了”队头堵塞”【1】的问题。
  • 数据流:HTTP/2 使用了数据流的概念,因为 HTTP/2 的数据包是不按顺序发送的,同一个连接里面连续的数据包,可能属于不同的请求。因此,必须要对数据包做标记,指出它属于哪个请求。HTTP/2 将每个请求或回应的所有数据包,称为一个数据流。每个数据流都有一个独一无二的编号。数据包发送时,都必须标记数据流 ID ,用来区分它属于哪个数据流。
  • 头信息压缩:HTTP/2 实现了头信息压缩,由于 HTTP 1.1 协议不带状态,每次请求都必须附上所有信息。所以,请求的很多字段都是重复的,比如 Cookie 和 User Agent ,一模一样的内容,每次请求都必须附带,这会浪费很多带宽,也影响速度。HTTP/2 对这一点做了优化,引入了头信息压缩机制。一方面,头信息使用 gzip 或 compress 压缩后再发送;另一方面,客户端和服务器同时维护一张头信息表,所有字段都会存入这个表,生成一个索引号,以后就不发送同样字段了,只发送索引号,这样就能提高速度了。
  • 服务器推送:HTTP/2 允许服务器未经请求,主动向客户端发送资源,这叫做服务器推送。使用服务器推送提前给客户端推送必要的资源,这样就可以相对减少一些延迟时间。这里需要注意的是 http2 下服务器主动推送的是静态资源,和 WebSocket 以及使用 SSE 等方式向客户端发送即时数据的推送是不同的。

【1】队头堵塞:

队头阻塞是由 HTTP 基本的“请求 - 应答”模型所导致的。HTTP 规定报文必须是“一发一收”,这就形成了一个先进先出的“串行”队列。队列里的请求是没有优先级的,只有入队的先后顺序,排在最前面的请求会被最优先处理。如果队首的请求因为处理的太慢耽误了时间,那么队列里后面的所有请求也不得不跟着一起等待,结果就是其他的请求承担了不应有的时间成本,造成了队头堵塞的现象。

3、对HTTP keep-alive的理解

HTTP1.0 中默认是在每次请求/应答,客户端和服务器都要新建一个连接,完成之后立即断开连接,这就是短连接。当使用Keep-Alive模式时,Keep-Alive功能使客户端到服务器端的连接持续有效,当出现对服务器的后继请求时,Keep-Alive功能避免了建立或者重新建立连接,这就是长连接。其使用方法如下:

  • HTTP1.0版本是默认没有Keep-alive的(也就是默认会发送keep-alive),所以要想连接得到保持,必须手动配置发送Connection: keep-alive字段。若想断开keep-alive连接,需发送Connection:close字段;
  • HTTP1.1规定了默认保持长连接,数据传输完成了保持TCP连接不断开,等待在同域名下继续用这个通道传输数据。如果需要关闭,需要客户端发送Connection:close首部字段。

Keep-Alive的建立过程

  • 客户端向服务器在发送请求报文同时在首部添加发送Connection字段
  • 服务器收到请求并处理 Connection字段
  • 服务器回送Connection:Keep-Alive字段给客户端
  • 客户端接收到Connection字段
  • Keep-Alive连接建立成功

服务端自动断开过程(也就是没有keep-alive)

  • 客户端向服务器只是发送内容报文(不包含Connection字段)
  • 服务器收到请求并处理
  • 服务器返回客户端请求的资源并关闭连接
  • 客户端接收资源,发现没有Connection字段,断开连接

客户端请求断开连接过程

  • 客户端向服务器发送Connection:close字段
  • 服务器收到请求并处理connection字段
  • 服务器回送响应资源并断开连接
  • 客户端接收资源并断开连接

开启Keep-Alive的优点:

  • 较少的CPU和内存的使⽤(由于同时打开的连接的减少了);
  • 允许请求和应答的HTTP管线化;
  • 降低拥塞控制 (TCP连接减少了);
  • 减少了后续请求的延迟(⽆需再进⾏握⼿);
  • 报告错误⽆需关闭TCP连;

开启Keep-Alive的缺点

  • 长时间的Tcp连接容易导致系统资源无效占用,浪费系统资源

4、什么是WebSocket


WebSocket是HTML5提供的一种浏览器与服务器进行全双工通讯的网络技术,属于应用层协议。它基于TCP传输协议,并复用HTTP的握手通道。浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接, 并进行双向数据传输。

WebSocket 的出现就解决了半双工通信的弊端。它最大的特点是:服务器可以向客户端主动推动消息,客户端也可以主动向服务器推送消息。

WebSocket原理:客户端向 WebSocket 服务器通知(notify)一个带有所有接收者ID(recipients IDs)的事件(event),服务器接收后立即通知所有活跃的(active)客户端,只有ID在接收者ID序列中的客户端才会处理这个事件。

WebSocket 特点的如下:
●支持双向通信,实时性更强
●可以发送文本,也可以发送二进制数据‘’
●建立在TCP协议之上,服务端的实现比较容易
●数据格式比较轻量,性能开销小,通信高效
●没有同源限制,客户端可以与任意服务器通信
●协议标识符是ws(如果加密,则为wss),服务器网址就是 URL
●与 HTTP 协议有着良好的兼容性。默认端口也是80和443,并且握手阶段采用 HTTP 协议,因此握手时不容易屏蔽,能通过各种 HTTP 代理服务器。

Websocket的使用方法如下:

在客户端中:

  1. // 在index.html中直接写WebSocket,设置服务端的端口号为 9999
  2. let ws = new WebSocket('ws://localhost:9999');
  3. // 在客户端与服务端建立连接后触发
  4. ws.onopen = function() {
  5. console.log("Connection open.");
  6. ws.send('hello');
  7. };
  8. // 在服务端给客户端发来消息的时候触发
  9. ws.onmessage = function(res) {
  10. console.log(res); // 打印的是MessageEvent对象
  11. console.log(res.data); // 打印的是收到的消息
  12. };
  13. // 在客户端与服务端建立关闭后触发
  14. ws.onclose = function(evt) {
  15. console.log("Connection closed.");
  16. };

5、简单请求和复杂请求

我们知道CORS即Cross Origin Resource Sharing(跨来源资源共享)将请求分为简单请求复杂请求

简单请求

HTTP方法必须是如下之一:

● HEAD
● GET
● POST

HTTP的头部信息不能超出以下几种字段:

● Accept
● Accept-Language
● Content-Language
● Content-Type
● DPR
● Downlink
● Save-Data
● Viewport-Width
● Width

Content-Type的值只有以下三种

● text/plain
● multipart/form-data
● application/x-www-form-urlencoded

简单请求的发送从代码上来看和普通的XHR没太大区别,但是HTTP头当中要求总是包含一个域(Origin)的信息。该域包含协议名、地址以及一个可选的端口,不过这一项实际上由浏览器代为发送 。

复杂请求

非简单请求就是被称为复杂请求,复杂请求我们也可以称之为在实际进行请求之前,需要发起预检请求的请求。
复杂请求表面上看起来和简单请求使用上差不多,但实际上浏览器发送了不止一个请求。其中最先发送的是一种”预请求”,此时作为服务端,也需要返回”预回应”作为响应。预请求实际上是对服务端的一种权限请求,只有当预请求成功返回,实际请求才开始执行。
预见请求通过OPTIONS方法发送,响应中会返回CORS的两项特有信息

● Access-Control-Allow-Methods:接受的方法,包括 GET,HEAD,PUT,POST,DELETE,PATCH等;
● Access-Control-Allow-Origin:接受的域。

一旦预回应如期而至,所请求的权限也都已满足,则实际请求开始发送。

6、TCP粘包是怎么回事,如何处理?

默认情况下, TCP 连接会启⽤延迟传送算法 (Nagle 算法), 在数据发送之前缓存他们. 如果短时间有多个数据发送, 会缓冲到⼀起作⼀次发送 (缓冲⼤⼩⻅ socket.bufferSize ), 这样可以减少 IO 消耗提⾼性能.

如果是传输⽂件的话, 那么根本不⽤处理粘包的问题, 来⼀个包拼⼀个包就好了。但是如果是多条消息, 或者是别的⽤途的数据那么就需要处理粘包.

下面看⼀个例⼦, 连续调⽤两次 send 分别发送两段数据 data1 和 data2, 在接收端有以下⼏种常⻅的情况:
A. 先接收到 data1, 然后接收到 data2 .
B. 先接收到 data1 的部分数据, 然后接收到 data1 余下的部分以及 data2 的全部.
C. 先接收到了 data1 的全部数据和 data2 的部分数据, 然后接收到了 data2 的余下的数据.
D. ⼀次性接收到了 data1 和 data2 的全部数据.

其中的 BCD 就是我们常⻅的粘包的情况. ⽽对于处理粘包的问题, 常⻅的解决⽅案有:

  • 多次发送之前间隔⼀个等待时间:只需要等上⼀段时间再进⾏下⼀次 send 就好, 适⽤于交互频率特别低的场景. 缺点也很明显, 对于⽐较频繁的场景⽽⾔传输效率实在太低,不过⼏乎不⽤做什么处理.
  • 关闭 Nagle 算法:关闭 Nagle 算法, 在 Node.js 中你可以通过 socket.setNoDelay() ⽅法来关闭 Nagle 算法, 让每⼀次 send 都不缓冲直接发送。该⽅法⽐较适⽤于每次发送的数据都⽐较⼤ (但不是⽂件那么⼤), 并且频率不是特别⾼的场景。如果是每次发送的数据量⽐较⼩, 并且频率特别⾼的, 关闭 Nagle 纯属⾃废武功。另外, 该⽅法不适⽤于⽹络较差的情况, 因为 Nagle 算法是在服务端进⾏的包合并情况, 但是如果短时间内客户端的⽹络情况不好, 或者应⽤层由于某些原因不能及时将 TCP 的数据 recv, 就会造成多个包在客户端缓冲从⽽粘包的情况。 (如果是在稳定的机房内部通信那么这个概率是⽐较⼩可以选择忽略的)
  • 进⾏封包/拆包:封包/拆包是⽬前业内常⻅的解决⽅案了。即给每个数据包在发送之前, 于其前/后放⼀些有特征的数据, 然后收到数据的时 候根据特征数据分割出来各个数据包。

    7. 如何实现浏览器内多个标签页之间的通信?

    实现多个标签页之间的通信,本质上都是通过中介者模式来实现的。因为标签页之间没有办法直接通信,因此我们可以找一个中介者,让标签页和中介者进行通信,然后让这个中介者来进行消息的转发。通信方法如下:

  • 使用 websocket 协议,因为 websocket 协议可以实现服务器推送,所以服务器就可以用来当做这个中介者。标签页通过向服务器发送数据,然后由服务器向其他标签页推送转发。

  • 使用 ShareWorker 的方式,shareWorker 会在页面存在的生命周期内创建一个唯一的线程,并且开启多个页面也只会使用同一个线程。这个时候共享线程就可以充当中介者的角色。标签页间通过共享一个线程,然后通过这个共享的线程来实现数据的交换。
  • 使用 localStorage 的方式,我们可以在一个标签页对 localStorage 的变化事件进行监听,然后当另一个标签页修改数据的时候,我们就可以通过这个监听事件来获取到数据。这个时候 localStorage 对象就是充当的中介者的角色。
  • 使用 postMessage 方法,如果我们能够获得对应标签页的引用,就可以使用postMessage 方法,进行通信。

    8. 正向代理和反向代理

    正向代理

    概念

    正向代理是一个位于客户端和目标服务器之间的代理服务器(中间服务器)。为了从目标服务器取得内容,客户端向代理服务器发送一个请求,并且指定目标服务器,之后代理向目标服务器转发请求,将获得的内容返回给客户端。正向代理的情况下,客户端必须要进行一些特殊的设置才能使用。

    用途

  • 突破访问显示:通过代理服务器,可以突破自身ip访问限制,访问国外网站等

  • 提高访问速度:通常代理服务器都设置一个较大的硬盘缓冲区,会将部分请求的响应保存到缓冲区中,当其他用户再访问相同的信息时,则直接由缓冲区中取出信息,传给用户,以提高访问速度
  • 隐藏客户端真实ip:上网者可以通过正向代理的方法隐藏自己的ip,免受攻击

    反向代理

    概念

    反向代理是指以代理服务器来接收客户端的请求,然后将请求转发给内部网络上的服务器,将从服务器上得到的结果返回给客户端,此时代理服务器对外表现为一个反向代理服务器。
    对于客户端来说,反向代理就相当于目标服务器,只需要将反向代理当作目标服务器一样发送请求就可以了,并且客户端不需要进行任何设置。

    用途

  • 隐藏用途服务器真实ip:使用反向代理,可以对客户端隐藏服务器的ip地址

  • 负载均衡:反向代理服务器可以做负载均衡,根据所有真实服务器的负载情况,将客户端请求分发到不同的真实服务器上
  • 提高访问速度:反向代理服务器可以对静态内容及短时间内有大量访问请求的动态内容提供缓存服务,提高访问速度
  • 提供安全保障:反向代理服务器可以作为应用层防火墙,为网站提供对基于web的攻击行为(例如DoS/DDoS)的防护,更容易排查恶意软件等。还可以为后端服务器统一提供加密和SSL加速(如SSL终端代理),提供HTTP访问认证等。

    9.HTTP和HTTPS的区别

  • HTTP 是以明文的方式进行传输,HTTPS 则是具有安全性的 SSL 加密传输协议。

  • HTTP 和 HTTPS 用的是两种不同的方式进行连接,端口号也不一样。前者是 80,后者是 443。
  • 想要用 HTTPS 就得购买证书(CA),而免费的整数一般都很少,所以需要支付一定的费用。
  • HTTPS 对搜索引擎更友好,有利于 SEO ,优先索引 HTTPS 的网页。
  • HTTP 的连接简单,并且是无状态的。HTTPS 是由 SSL + HTTP 协议构建的可进行加密传输、身份认证的网络协议,比 HTTP 要安全。

    10. OPTIONS请求方法及使用场景

    OPTIONS是除了GET和POST之外的其中一种 HTTP请求方法。

OPTIONS方法是用于请求获得由Request-URI标识的资源在请求/响应的通信过程中可以使用的功能选项。通过这个方法,客户端可以在采取具体资源请求之前,决定对该资源采取何种必要措施,或者了解服务器的性能。该请求方法的响应不能缓存。

OPTIONS请求方法的主要用途有两个:

  • 获取服务器支持的所有HTTP请求方法;
  • 用来检查访问权限。例如:在进行 CORS 跨域资源共享时,对于复杂请求,就是使用 OPTIONS 方法发送嗅探请求,以判断是否有对指定资源的访问权限。

    11. 301、302、304 的区别

  • 301 表示永久重定向(301 moved permanently),表示请求的资源分配了新url,以后应使用新url。

  • 302 表示临时性重定向(302 found),请求的资源临时分配了新url,本次请求暂且使用新url。302与301的区别是,302表示临时性重定向,重定向的url还有可能还会改变。
  • 304 not modified 客户端发送附带条件的请求时(if-matched,if-modified-since,if-none-match,if-range,if-unmodified-since任一个)服务器端允许请求访问资源,但因发生请求未满足条件的情况后,直接返回304Modified(服务器端资源未改变,可直接使用客户端未过期的缓存)。304状态码返回时,不包含任何响应的主体部分。304虽然被划分在3xx类别中,但是和重定向没有关系。

    12. 为什么部分请求中,参数需要使用 encodeURIComponent 进行转码?

    一般来说,URL 只能使用英文字母、阿拉伯数字和某些标点符号,不能使用其他文字和符号。
    这是因为网络标准 RFC1738 做了硬性规定:这意味着,如果 URL 中有汉字,就必须编码后使用。但是麻烦的是 RFC1738 没有规定具体的编码方法,而是交给应用程序(浏览器)自己决定,这导致URL 编码成为了一个混乱的领域!
    不同的操作系统、不同的浏览器、不同的网页字符集,将导致完全不同的编码结果。如果程序员要把每一种结果都考虑进去,是不是太恐怖了?有没有办法,能保证客户端只用一种编码方法向服务器发出请求?
    就是使用 JS 先对 URL 编码,然后再向服务器提交,不要给浏览器插手的机会。因为 JS 的输出总是一致的,所以保证服务器得到的数据是格式统一的。
    JS 语言用于编码的函数,一共有 3 个,最古老的一个就是 escape()。虽然这个函数现在已经不提倡使用了,但是由于历史原因,很多地方还在使用它,所以有必要先从它讲起。它的具体规则是,楚克 ASCII 字母、数字、标点符号”@*_±./“以外,以其他所有字符进行编码。
    encodeURI()是 JS 中真正用来对 URL 编码的函数。它着眼于对整个 URL 进行编码,因此除了常见的符号以外,对其他一些在网址有特殊含义的符号”;,/?:@&=+$#”,也不进行编码。编码后,它输出符号的 utf-8 形式,并且在每个字节前加上%。
    最后一个 JS 编码函数是 encodeURIComponent()。与 encodeURI()的区别是,它用于对 URL 的组成部分进行个别编码,而不用对整个 URL 进行编码。
    因此,“;/,?$@&+=#”,这些在 encodeURI()中不被编码的符号,在 encodeURIComponent()中统统会被编码。至于具体的编码方法,两者是一样。它对应的解码函数是 decodeURIComponent()。

    13. WebSocket 与 HTTP 有什么关系?

    相同点:
    1、都是应用层协议
    2、都是建立在TCP之上,通过TCP协议来传输数据
    3、都是可靠性传输协议
    不同点:
    1、HTTP是单向协议,只能由客户端发起
    2、WebSocket是HTML5中的协议,支持持久连接,HTTP不支持持久连接
    关系:
    WebSocket 握手使用 HTTP Upgrade 头,从 HTTP 协议更改为 WebSocket 协议

    14. 使用 cookie、session 维持登录状态的原理是什么?

    cookie

    由于 HTTP 是一种无状态的协议,服务器单从网络连接上无从知道客户身份。怎么办呢?就给客户端们颁发一个通行证,每人一个,无论谁访问都必须携带自己通行证。这样服务器就能从通行证上确认客户身份了,而这个通行证就是 cookie。
    cookie 实际上是一小段的文本信息,存储在客户端,web 服务器通过传送 HTTP 包头中的 set-Cookie 把一个 cookie 发送到用户的浏览器中,内任凭主要包括:名字,值,过期时间,路径和域,路径和域一起构成 cookie 的作用范围。
    如果不设置过期时间,则表示这个 cookie 的生命期为浏览器会话期间,只要关闭浏览器窗口,cookie 就消失了,这种生命期为浏览器会话期的 cookie 被称为会话 cookie。会话 cookie 一般不存储在硬盘上而是保存在内存里,当然这种行为并不是规范规定的;如果设置了过期时间,浏览器就会把 cookie 保存在硬盘上,关闭后再次打开浏览器,这些 cookie 仍然有效直到超过设定的过期时间

    session

    session 机制是一种服务器端的机制,服务器使用一种类似于散列表的机构来保存信息,一般存储在文件、数据库或内存中。
    当客户端第一次请求服务端的时候,服务端会检查客户端的请求头携带的 cookie 中,是否包含 sessionID,如果有的话则会检索这个 sessionID 对应的 session 是否存在。如果不存在则会创建相应的会话信息,生成对应的 session,并将 session 返回给客户端,客户端接收到这个 seesionID,把它存储起来,下一次发送请求的时候,附带着这个 session 一起发送给服务端,服务端只要根据这个 sessionID,就知道是谁了,而这个 sessionID 就是这次会话生命周期的凭证,服务端可以给这个 sessionID 设置过期时间,一旦客户端丢失这个 sessionID 或者服务端认为这个 sessionID 失效了,那么这次会话结束了

    登录状态的维持过程

    首先用户在客户端浏览器发起登录请求。
    登录成功后,服务端会把用户信息保存在服务端,并返回一个唯一的 session 标识给客户端浏览器。
    客户端浏览器会把这个唯一的 session 标识保存起来,存在 cookie 中。
    以后再次访问 web 应用时,客户端浏览器会把这个唯一的 session 标识带上,这样服务端就能根据这个唯一标识找到用户信息。

    15. 你所理解的点击劫持是什么?

    解析
    ●Clickjacking:点击劫持,是指利用透明的按钮或链接做成陷阱,覆盖在 Web 页面之上。然后诱使用户在不知情的情况下,点击那个链接访问内容的一种攻击手段。这种行为又称之为界面伪装(UI Redressing)
    ●大概有两种方式:攻击者使用一个透明 iframe,覆盖在一个网页上,然后诱使用户在该页面上进行操作,此时用户将在不知情的情况下点击透明的 iframe 页面;攻击者使用一张图片覆盖在网页,遮挡网页原有的位置
    一般步骤
    ●黑客创建一个网页利用 iframe 包含目标网站
    ●隐藏目标网站,使用户无法察觉到目标网站存在
    ●构造网页,诱使用户点击特别按钮
    ●用户在不知情的情况下点击按钮,触发执行恶意网页的命令
    防御
    X-FRAME-OPTIONS:响应头是用来给浏览器指示允许一个页面是否存在或是否展现的标记。
    网站可以使用这个功能,来确保自己的网站内容没有被嵌到别人的网站中,也从而避免点击劫持的攻击。

16. 对于定长和不定长的数据,HTTP 是怎么传输的?

  • 定长包体

对于定长包体而言,发送端在传输的时候一般会带上Content-Length,来指明包体的长度。

  • 不定长包体

介绍另外一个http头部字段:Transfer-Encoding: chunked。
表示分块传输数据,设置这个字段后会自动产生两个效果:
Content-Length 字段会被忽略
基于长连接持续推送动态内容

17 什么是 DNS 劫持?.

域名劫持

域名劫持又称DNS劫持,是指在劫持的网络范围内拦截域名解析的请求,分析请求的域名,把审查范围外的请求放行,否则返回假的ip地址或者什么都不做使请求失去响应,其效果就是对特定的网络不能访问或者访问的是假地址。

域名劫持原理

域名解析(DNS)的基本原理是把网络地址(域名,一个字符串的形式)对应到真实的计算机能够识别的网络地址(IP地址,比如216.239.xx.xx这样的形式),以便计算机能够进一步通信,传递网络和内容等。由于域名劫持往往只能在特定的被劫持的网络范围内进行,所以在此范围外的域名服务器(DNS)能够返回正常的IP地址,高级用户可以在网络设置把DNS指向这些正常的域名服务器以实现对网址的正常访问。所以域名劫持通常相伴的措施-封锁正常DNS的IP。如果知道该域名的真实IP地址,则可以用此IP代替域名后进行访问。比如访问谷歌,可以把访问改为https://216.239.xx.xx/,从而绕开域名劫持。

常见域名劫持现象

广告劫持:用户正常页面指向到广告页面
恶意劫持:域名指向IP被改变,将用户访问流量引到挂马,盗号等对用户有害页面的劫持。
local DNS缓存:为了降低跨网流量及用户访问速度进行的一种劫持,导致域名解析结果不能按时更新。