当浏览器第一次发送请求给服务器时,服务器响应了;
如果同个浏览器发起第二次请求给服务器时,它还是会响应。但是呢,服务器不知道你就是刚才的那个浏览器。
简而言之,服务器不会去记住你是谁,所以是无状态协议。
HTTP/1.0
默认使用短连接,每次请求都需要建立一个TCP连接。它可以设置Connection: keep-alive
这个字段,强制开启长连接。
HTTP/1.1
HTTP/2.0
二进制协议,1.1版本的头信息是文本(ASCII编码),数据体可以是文本或二进制;2.0中,头信息和数据体都是二进制。
完全多路复用,在一个连接里,客户端和服务器都可以同时发送多个请求或响应,而且不用按照顺序一一对应。
报头压缩,HTTP协议不带有状态,每次请求都必须带上所有信息。HTTP/2.0引入了头信息压缩机制,使用gzip或compress压缩后再发送。
服务端推送,允许服务器未经请求,主动向客户端发送资源。
什么是HTTP的长连接?
如何设置长连接?
通过在请求头和响应头设置Connection字段指定为keep-alive
,HTTP/1.0协议支持,但默认是关闭的,从HTTP/1.1以后,连接默认都是长连接。
在什么时候会超时?
tcp_keepalive_intvl = 15
tcp_keepalive_probes = 5
tcp_keepalive_time = 1800
HTTP 即超文本传输协议,是一个基于TCP/IP通信协议来传递明文数据的协议。HTTP会存在这几个问题:
为了解决HTTP存在的问题,HTTPS出现啦。
HTTPS是什么?
HTTPS= HTTP+SSL/TLS,可以理解为 HTTPS 是身披 SSL(Secure Socket Layer,安全套接层)的HTTP。
它们的主要区别如下:
数字证书构成:
对称加密:指加密和解密使用同一密钥,优点是运算速度较快,缺点是不能安全地将密钥传输给另一方。常见的对称加密算法有:DES、AES等。
非对称加密:指的是加密和解密使用不同的密钥(即公钥和私钥)。公钥与私钥是成对存在的,如果用公钥对数据进行加密,只有对应的私钥才能解密。常见的非对称加密算法有 RSA。
DNS的解析过程如下图:
假设你要查询www.baidu.com的IP地址:浏览器 -> 本地DNS服务器 -> 根域名服务器 -> 顶级域名服务器 -> 权威域名服务器
.com
的顶级域名服务器的列表。.baidu
的权威域名服务器的列表。Socket是一套标准,它完成了对TCP/IP的高度封装,屏蔽网络细节,以便开发者更好地进行网络编程。
Socket等于IP地址 + 端口 + 协议。
WebSocket是一个持久化的协议,它是伴随H5而出的协议,用来解决HTTP不支持持久化连接的问题。
Socket是一个网络编程的标准接口,而WebSocket则是应用层通信协议。
HTTP是一个基于TCP/IP协议来传递数据的超文本传输协议,传输的数据类型有HTML、图片等。
是servlet中的两种主要跳转方式。forward:转发,redirect:重定向
forward是服务器内部的重定向,服务器直接访问目标地址,把里面的东西取出来,但是客户端并不知道,因此用forward的话,客户端浏览器的网址是不会发生变化的。
redirect是服务器根据逻辑,发送一个状态码,告诉浏览器重新去请求那个地址,所以地址栏显示的是新地址。
由于在整个转发的过程中使用的是同一个request,因此forward会将request信息带到被重定向的jsp或servlet中使用,即可以共享数据。
redirect不能共享数据。
forward一般用于用户登录时,根据角色转发到相应的模块
redirect一般用于用户注销登录时返回主页面
forward转发是服务器上的行为,redirect重定向是客户端的行为。
forword效率高,而redirect效率低。
forword只有一次请求,而redirect有两次请求。
Cookie 是保存在客户端的一小块文本串的数据。客户端向服务器发起请求时,服务器会向客户端发送一个 Cookie,客户端就把 Cookie 保存起来。下次向同一服务器再发起请求时,Cookie 就被携带发送到服务器。服务器可以根据这个 Cookie 判断用户的身份和状态。
Session 指的是服务器和客户端一次会话的过程。它是另一种记录客户端状态的机制。不同的是 Cookie 是保存在客户端浏览器中的,而 Session 是保存在服务器上的。客户端浏览器在访问服务器时,服务器会把客户端信息以某种形式记录在服务器上,这就是 Session。客户端浏览器再次访问时只需要从该 Session 中查找用户的状态。
Session 和 Cookie 到底有什么不同呢?
Session 和 Cookie 有什么关联呢?
可以使用 Cookie 记录 Session 的唯一标识
分布式环境下 Session 该怎么处理呢?
分布式环境下,客户端请求经过负载均衡,可能会分配到不同的服务器上,假如一个用户的请求两次没有落到同一台服务器上,那么在新的服务器上就没有记录该用户状态对应的 Session。
可以使用 Redis 等分布式缓存来存储 Session,保证在多台服务器间共享。
客户端如果无法使用 Cookie 怎么办呢?
有可能客户端无法使用 Cookie,比如浏览器禁用 Cookie,或者客户端是 安卓、IOS 设备等。
这时候怎么办呢?SessionID 怎么存呢?怎么传给服务器呢?
首先是 SessionID 的存储,可以使用客户端的本地存储,比如浏览器的 sessionStorage
。
接下来要怎么传呢?
TCP 三次握手过程:
为什么不能是两次?
防止已失效的连接请求报文段突然又传到服务端,因而产生错误
所以我们需要 “第三次握手” 来确认这个过程:
为什么不是四次?
简单来说,就是三次握手已经足够创建可靠的连接,没有必要再多一次握手导致花费更多的时间在建立连接上。
第一次握手服务端未收到
SYN
报文
服务端不会进行任何的动作,而客户端由于一段时间内没有收到服务端发来的确认报文,等待一段时间后会重新发送 SYN 报文,
如果仍然没有回应,会重复这个过程,直到发送次数超过最大重传次数,就会返回连接建立失败。
第二次握手客户端未收到服务端响应的
ACK
报文
因为第二次握手是包含对客户端第一次握手的 ACK 确认报文,所以如果客户端迟迟没有收到第二次握手,那么客户端就会觉得可能是自己的 SYN 报文(第一次握手)丢失了,于是客户端就会触发超时重传机制,重传 SYN 报文。
然后,因为第二次握手是包含服务端的 SYN 报文,所以当客户端收到后,需要给服务端发送 ACK 确认报文(第三次握手),服务端才会认为该 SYN 报文被客户端收到了。
那么,如果第二次握手丢失了,服务端就收不到第三次握手,于是服务端这边会触发超时重传机制,重传 SYN-ACK
报文。
第三次握手服务端未收到客户端发送过来的
ACK
报文
客户端收到服务端的 SYN-ACK
报文后,就会给服务端发送一个 ACK 报文,也就是第三次握手,此时客户端进入到 ESTABLISH(连接已建立) 状态。
因为这个第三次握手的 ACK 是对第二次握手的 SYN 的确认报文,所以当第三次握手丢失了,如果服务端那一方迟迟收不到这个确认报文,就会触发超时重传机制,重传 SYN-ACK
报文,直到收到第三次握手,或者达到最大重传次数。
ACK
是为了告诉客户端传来的数据已经接收无误。
而传回 SYN
是为了告诉客户端,服务端响应的确实是客户端发送的报文。
第三次握手是可以携带数据的。
此时客户端已经处于连接已建立
状态。对于客户端来说,它已经建立连接成功了,并且确认服务端的接收和发送能力是正常的。
第一次握手不能携带数据是出于安全的考虑,因为如果允许携带数据,攻击者每次在 SYN
报文中携带大量数据,就会导致服务端消耗更多的时间和空间去处理这些报文,会造成CPU和内存的消耗。
2MSL
后,也进入 CLOSED(关闭) 状态。客户端在发送完最后一个确认报文后,为什么不直接进入关闭状态 ? 而是要进入时间等待状态,2MSL 后才进入关闭状态,这是否有必要呢 ?
服务端发送TCP连接释放报文段后进入最后确认状态。
客户端收到该报文段后,发送普通的TCP确认报文段,并进入关闭状态而不是时间等待状态。然而,该TCP确认报文段丢失了。
这必然会造成服务端对之前所发送的TCP连接释放报文段的超时重传,并仍处于最后确认状态。重传的TCP连接释放报文段到达客户端,由于客户端处于关闭状态,因此不理睬该报文段,这必然会造成服务端反复重传TCP连接释放报文段,并一直处于最后确认状态而无法进入关闭状态。
因此时间等待状态以及处于该状态2MSL
时长,可以确保服务端可以收到最后一个TCP确认报文段而进入关闭状态。
另外,客户端在发送完最后一个TCP确认报文段后,再经过2MSL
时长,就可以使本次连接持续时间内所产生的所有报文段都从网络中消失,这样就可以使下一个新的TCP连接中,不会出现旧连接中的报文段。
为什么等待时间是
2MSL
?
MSL 是报⽂最⼤⽣存时间,它是任何报⽂在⽹络上存在的最⻓时间,超过这个时间报⽂将被丢弃。
TIME_WAIT 等待 2 倍的 MSL,是因为⽹络中可能存在来⾃发送⽅的数据包,当这些发送⽅的数据包被接收⽅处理后⼜会向对⽅发送响应,所以⼀来⼀回需要等待 2 倍的时间。
⽐如服务端如果没有收到客户端发送的TCP确认报文段,就会触发超时重传,重新发送TCP连接释放报文段,客户端收到后,会重发TCP确认报文段给服务端, ⼀来⼀去正好 2 个 MSL。
再来回顾下四次挥手双方发
FIN
包的过程,就能理解为什么需要四次了。
关闭连接时,客户端向服务端发送 FIN
报文,仅仅表示客户端不再发送数据了但是还能接收数据。
服务端收到客户端的 FIN
报文后,先返回一个 ACK
确认报文;而服务端可能还有数据需要处理和发送,等服务端不再发送数据了,再发送 FIN
报文给客户端来表示同意现在关闭连接。
从上面的过程可知,服务端通常需要等待完成数据的发送和处理,所以服务端的 ACK
和 FIN
一般都会分开发送,从而导致比三次握手多了一次。
除了时间等待计时器外,TCP 还有一个保活计时器(keepalive timer)。
设想这样的场景:
TCP 双方已经建立了连接,后来,客户端的主机突然出现了故障。显然,服务端以后就不能再收到客户端发来的数据。因此,应当有措施使服务端不要再白白等待下去。这就需要使用保活计时器了。
服务端每收到一次客户端的数据,就重新设置并启动保活计时器(2小时定时)。若定时周期内都没有收到客户端发来的数据,服务端就发送一个探测报文段,以后每隔 75 秒钟发送一次。若连续发送 10 个探测报文段后仍然无客户端的响应,服务端就认为客户端出了故障,接着就关闭这个连接。
服务端收到客户端关闭连接的请求并确认之后,就会进入 CLOSE-WAIT
状态。
此时服务端可能还有一些数据没有传输完成,因此不能立即关闭连接,而 CLOSE-WAIT
状态就是为了保证服务端在关闭连接之前将待发送的数据处理完。
16位端口号:源端口号,标识发送该 TCP 报文段的应用进程;目的端口号,标识接收该 TCP 报文段的应用进程。
32位序号:指出本 TCP 报文段数据载荷的第一个字节的序号。
32位确认号:指出期望收到对方下一个 TCP 报文段的数据载荷的第一个字节的序号,同时也是对之前收到的所有数据的确认。若确认号 = n,则表明到序号 n-1 为止的所有数据都已正确接收,期望收到序号为 n 的数据。
4位头部长度:指出 TCP 报文段的首部长度。
6位标志位:
16位窗口大小:
16位校验和:用来检查整个 TCP 报文段在传输过程中是否出现了误码。
16位紧急指针:当发送方有紧急数据时,可将紧急数据插队到发送缓存的最前面,并立刻封装到一个 TCP 报文段中进行发送。紧急指针会指出本报文段的数据载荷部分包含了多长的紧急数据,紧急数据之后是普通数据。
TCP主要提供了 连接管理、校验和、序列号/确认应答、流量控制、最大消息长度、超时重传、拥塞控制等方式实现了可靠传输。
连接管理:TCP 使用三次握手和四次挥手来保证可靠地建立连接和释放连接。
校验和:用来检查整个 TCP 报文段在传输过程中是否出现了误码。
序列号/确认应答:TCP 会给发送的每一个包进行编号,接收方会对收到的包进行应答,发送方就会知道接收方是否收到对应的包,如果发现没有收到,就会重发,这样就能保证数据的完整性了。
流量控制:TCP 连接的每一方都有固定大小的缓冲空间,TCP 的接收端只允许发送端发送接收端缓冲区能接纳的数据大小。当接收方来不及处理发送方的数据时,能提示发送方降低发送的速率,防止包丢失。TCP 使用的流量控制协议是可变大小的滑动窗口协议。(TCP 利用滑动窗口实现流量控制)
最大消息长度:在建立 TCP 连接的时候,双方约定一个最大的长度(MSS)作为发送的单位,重传的时候也是以这个单位来进行重传的。理想情况下是该长度的数据刚好不被网络层分块。
超时重传:超时重传是指发送出去的数据包到接收到确认包之间的时间,如果超过了这个时间,就会被认为是丢包了,需要重传。
拥塞控制:如果网络非常拥堵,此时再发送数据就会加重网络负担,那么发送的数据段很可能超过了最大生存时间也没有到达接收方,就会产生丢包问题。为此 TCP 引入了慢启动机制,先发出少量数据,就像探路一样,先摸清当前的网络拥堵状态后,再决定按照多大的速度传送数据。
TCP 提供了一种机制,可以让发送方根据接收方的实际接收能力控制发送的数据量,这就是流量控制。
TCP 通过「滑动窗口」来实现流量控制
SND.NXT
会右移 200 字节,也就是说当前的可用窗口减少了 200 字节。REV.WND
= 400-200=200 字节,所以 win=200 字节返回给发送方。接收方会在 ACK 的报文首部带上缩小后的滑动窗口 200 字节REV.WND
= 400-200-100=100 字节,即 win=100 字节返回给发送方。TCP 发送一个数据,如果需要收到确认应答才会发送下一个数据。这样的话就会有个缺点:效率会比较低。
为了解决这个问题,TCP 引入了滑动窗口,它是操作系统开辟的一个缓存空间。窗口大小表示无需等待确认应答而可以继续发送数据的最大值。
TCP 头部有个 16 位的窗口大小,它告诉对方本端的 TCP 接收缓冲区还能容纳多少字节的数据,这样对方就可以控制发送数据的速度,从而达到流量控制的目的。
通俗点讲,就是接收方每次收到数据包,在发送确认报文的时候,同时告诉发送方,自己的接收缓冲区还有多少空闲空间,缓冲区的空闲空间,我们就称之为接收窗口大小。
TCP 滑动窗口分为两种: 发送窗口和接收窗口。
发送方的滑动窗口包含四个部分:
SND.WND
:表示发送窗口的大小,上图虚线框的格子数是 14 个,即发送窗口大小是 14。SND.NXT
:下一个发送的位置,它指向未发送但可以发送的第一个字节的序列号。SND.UNA
:一个绝对指针,它指向的是已发送但未收到确认的第一个字节的序列号。接收方的滑动窗口包含三个部分:
REV.WND
:表示接收窗口的大小,上图虚线框的格子数就是 9 个,即接收窗口的大小是 9。REV.NXT
:下一个接收的位置,它指向未收到但可以接收的第一个字节的序列号。什么是拥塞控制?不是有了流量控制吗?
前⾯的流量控制是避免发送⽅的数据填满接收⽅的缓存,但是并不知道整个⽹络中发⽣了什么。
⼀般来说,计算机⽹络都处在⼀个共享的环境。因此也有可能会因为其他主机之间的通信使得⽹络出现拥堵。
在⽹络出现拥堵时,如果继续发送⼤量数据包,可能会导致数据包延迟、丢失等,这时 TCP 就会重传数据,但是⼀重传就会导致⽹络的负担更重,于是会导致更⼤的延迟以及更多的丢包,这个情况就会进⼊恶性循环并且被不断地放⼤…
所以,TCP 不能忽略整个网络中发⽣的事,它被设计成⼀个⽆私的协议,当⽹络发送拥塞时,TCP 会⾃我牺牲,降低发送的数据流。
于是,就有了拥塞控制,拥塞控制的⽬的就是为了避免发送⽅的数据填满整个⽹络。
就像是一个水管,不能让太多的水(数据流)流入水管,如果超过水管的承受能力,水管就会被撑爆(丢包)。
发送方维护一个拥塞窗口 cwnd(congestion window) 的变量,调节所要发送数据的量。
什么是拥塞窗⼝?和发送窗⼝有什么关系呢?
拥塞窗⼝ **cwnd **是发送⽅维护的⼀个状态变量,它会根据⽹络的拥塞程度动态变化。
发送窗⼝ swnd 和接收窗⼝ rwnd 是约等于的关系,那么由于加⼊了拥塞窗⼝的概念后,此时发送窗⼝的值 swnd = min(cwnd, rwnd)
,也就是取拥塞窗⼝和接收窗⼝中的最⼩值。
拥塞窗⼝ cwnd 变化的规则:
拥塞控制有哪些常用算法?
慢启动算法,慢慢启动。
它表示 TCP 建立连接完成后,一开始不要发送大量的数据,而是先探测一下网络的拥塞程度。由小到大逐渐增加拥塞窗口的大小,如果没有出现丢包,每收到一个 ACK,就将拥塞窗口 cwnd 的大小加 1(单位是 MSS)。每轮次发送窗口增加一倍,呈指数增长,如果出现丢包,拥塞窗口就减半,进入拥塞避免阶段。
举个例子:
发送包的个数是呈指数性增⻓的。
为了防止 cwnd 增长过大而引起网络拥塞,还需设置一个慢启动阀值 ssthresh(slow start threshold)的状态变量。当 cwnd
到达该阀值后,就好像水管被关小了水龙头一样,减少了拥塞状态。即当 cwnd > ssthresh
时,进入拥塞避免算法。
一般来说,慢启动阀值 ssthresh
的大小是 65535 字节,cwnd
到达慢启动阀值后
cwnd = cwnd + 1/cwnd
cwnd = cwnd + 1
显然这是一个线性上升的算法,可以避免发送过快导致网络出现拥塞问题。
接着上面慢启动的例子,假定 ssthresh
为 8:
1/8
,8 个 ACK 确认后 cwnd ⼀共增加 1,于是这⼀次能够发送 9 个 MSS ⼤⼩的数据,变成了线性增⻓。当网络拥塞发生丢包时,会有两种情况:
如果是发生了RTO 超时重传,就会使用「拥塞发生」算法
sshthresh = cwnd/2
这真的是辛辛苦苦几十年,一朝回到解放前。其实还有更好的处理方式,就是「快速重传」。当发送方收到 3 个连续的重复 ACK 时,就会快速地重传,不必等待 RTO超时 再重传。
发⽣「快速重传」的拥塞发⽣算法:
cwnd = cwnd/2
ssthresh = cwnd
快速重传和快速恢复算法一般是同时使用的。快速恢复算法认为,还能收到 3 个重复的 ACK,说明网络也没有那么糟糕,所以没必要像 RTO超时重传 那样强烈。
正如前面所说的,进入快速恢复之前,cwnd 和 sshthresh 已被更新:
cwnd = cwnd/2
sshthresh = cwnd
然后,真正进入「快速恢复」算法:
cwnd = sshthresh + 3
cwnd = cwnd +1
cwnd = sshthresh
。因为收到新数据的 ACK,表明恢复过程已经结束,可以再次进入「拥塞避免」算法了。重传包括:超时重传、快速重传、带选择确认的重传(SACK)、重复 SACK 四种。
超时重传,是 TCP 协议保证数据可靠性的另一个重要机制,其原理是在发送某一个数据以后就开启一个重传计时器,在一定时间内如果没有收到发送的数据报的 ACK 报文,那么就重新发送数据,直到收到 ACK 报文为止。
超时时间应该设置为多少合适呢?
RTT 就是数据完全发送完,到收到确认信号的时间,即数据包的一次往返时间。
超时重传时间,就是 RTO(Retransmission Timeout)。那么,RTO 应该设置多大呢?
一般来说,RTO 略微大于 RTT,效果是最佳的。
超时重传并不是十分完美的重传方案,它有这些缺点:
快速重传可以用来解决超时重发的时间等待问题。
它不以时间驱动,而是以数据驱动。它是基于接收方的反馈信息来引发重传的。
快速重传的流程如下:
发送方发送了 1,2,3,4,5,6
份数据:
快速重传机制也有缺点:发送方并不知道到底是哪个报文丢失了,到底该重传多少个数据包?
是只重传 Seq=3 ?还是重传 Seq=3、Seq=4、Seq=5、Seq=6 呢?因为发送方并不清楚这三个连续的 ACK=3 是谁传回来的。
为了解决应该重传多少个包的问题? TCP 提供了带选择确认的重传(即 SACK,Selective Acknowledgment)。
SACK 机制就是,在快速重传的基础上,接收方返回最近收到报文段的序列号范围,这样发送方就知道接收方哪些数据包是没收到的。这样就很清楚应该重传哪些数据包。
如上图中,发送⽅收到了三次同样的 ACK 确认报⽂,于是就会触发「快速重传」机制,通过 SACK 信息发现只有 200~299
这段数据丢失,则重发时,就只选择了这个 TCP 段进⾏重发。
D-SACK,英文是 Duplicate SACK,是在 SACK 的基础上做了一些扩展,主要用来告诉发送方,有哪些数据包,自己重复接受了。
D-SACK 的目的是帮助发送方判断,是否发生了包失序、ACK 丢失、包重复或伪重传。让 TCP 可以更好的做网络流控。
例如 ACK 丢包导致的数据包重复:
接收⽅发给发送⽅的两个 ACK 确认应答都丢失了,所以发送⽅超时后,重传第⼀个数据包(3000 ~ 3499)
接收⽅发现数据是重复收到的,于是回了⼀个 SACK = 3000~3500,告诉「发送⽅」 3000~3500的数据早已被接收了,因为 ACK 都到 4000 了,意味着 4000 之前的所有数据都已经收到了,所以这个 SACK 就代表着 D-SACK
。这样发送⽅就知道了,数据并没有丢,而是接收⽅的 ACK 确认报⽂丢了。
什么是 TCP 粘包和拆包?
TCP 是面向字节流,没有界限的一串数据。TCP 底层并不了解上层业务数据的具体含义,它会根据 TCP 缓冲区的实际情况进行包的划分,所以在业务上认为,一个完整的包可能会被 TCP 拆分成多个包进行发送,也有可能把多个小的包封装成一个大的数据包进行发送,这就是所谓的 TCP 粘包和拆包问题。
为什么会产生粘包和拆包呢?
TCP报文长度 - TCP头部长度 > MSS
。解决方案: