了解最新公司动态及行业资讯
当浏览器第一次发送请求给服务器时,服务器响应了;
如果同个浏览器发起第二次请求给服务器时,它还是会响应。但是呢,服务器不知道你就是刚才的那个浏览器。
简而言之,服务器不会去记住你是谁,所以是无状态协议。
HTTP/1.0
默认使用短连接,每次请求都需要建立一个TCP连接。它可以设置Connection: keep-alive 这个字段,强制开启长连接。
HTTP/1.1
默认使用长连接,即TCP连接默认不关闭,可以被多个请求复用。分块传输编码,即服务端每产生一块数据,就发送一块,用“流模式”取代“缓存模式”。管道机制,即在同一个TCP连接里面,客户端可以同时发送多个请求。HTTP/2.0
二进制协议,1.1版本的头信息是文本(ASCII编码),数据体可以是文本或二进制;2.0中,头信息和数据体都是二进制。完全多路复用,在一个连接里,客户端和服务器都可以同时发送多个请求或响应,而且不用按照顺序一一对应。报头压缩,HTTP协议不带有状态,每次请求都必须带上所有信息。HTTP/2.0引入了头信息压缩机制,使用gzip或compress压缩后再发送。服务端推送,允许服务器未经请求,主动向客户端发送资源。什么是HTTP的长连接?
HTTP分为长连接和短连接,本质上说的是TCP的长短连接。TCP连接是一个双向的通道,它是可以保持一段时间不关闭的,因此TCP连接才具有真正的长连接和短连接这一说法。TCP长连接可以复用一个TCP连接,来发起多次HTTP请求,这样就可以减少资源消耗,比如一次HTML请求,如果是短连接的话,可能还需要请求后续的JS/CSS。如何设置长连接?
通过在请求头和响应头设置Connection字段指定为keep-alive,HTTP/1.0协议支持,但默认是关闭的,从HTTP/1.1以后,连接默认都是长连接。
在什么时候会超时?
HTTP一般会有httpd守护进程,里面可以设置keep-alive timeout,当tcp连接闲置超过这个时间就会关闭,也可以在HTTP的header里面设置超时时间。TCP 的keep-alive包含三个参数,支持在系统内核的net.ipv4里面设置;当 TCP 连接之后,闲置了tcp_keepalive_time,则会发生侦测包,如果没有收到对方的ACK,那么会每隔 tcp_keepalive_intvl 再发一次,直到发送了tcp_keepalive_probes,就会丢弃该连接。tcp_keepalive_intvl = 15 tcp_keepalive_probes = 5 tcp_keepalive_time = 1800 123HTTP 即超文本传输协议,是一个基于TCP/IP通信协议来传递明文数据的协议。HTTP会存在这几个问题:
请求信息是明文传输,容易被窃听截取。没有验证对方身份,存在被冒充的风险。数据的完整性未校验,容易被中间人篡改。为了解决HTTP存在的问题,HTTPS出现啦。
HTTPS是什么?
HTTPS= HTTP+SSL/TLS,可以理解为 HTTPS 是身披 SSL(Secure Socket Layer,安全套接层)的HTTP。
它们的主要区别如下:
数字证书构成:
公钥和个人等信息,经过Hash算法加密,形成消息摘要;将消息摘要拿到拥有公信力的认证中心(CA),用它的私钥对消息摘要加密,形成数字签名。公钥和个人信息、数字签名共同构成数字证书。对称加密:指加密和解密使用同一密钥,优点是运算速度较快,缺点是不能安全地将密钥传输给另一方。常见的对称加密算法有:DES、AES等。
非对称加密:指的是加密和解密使用不同的密钥(即公钥和私钥)。公钥与私钥是成对存在的,如果用公钥对数据进行加密,只有对应的私钥才能解密。常见的非对称加密算法有 RSA。
DNS的解析过程如下图:
假设你要查询www.baidu.com的IP地址:浏览器 -> 本地DNS服务器 -> 根域名服务器 -> 顶级域名服务器 -> 权威域名服务器
首先会查找浏览器的缓存,看看是否能找到www.baidu.com对应的IP地址,找到就直接返回;否则进行下一步。将请求发往本地DNS服务器,如果查找到也直接返回,否则继续进行下一步;本地DNS服务器向根域名服务器发送请求,根域名服务器返回负责.com的顶级域名服务器的列表。本地DNS服务器再向其中一个顶级域名服务器发送一个请求,返回负责.baidu的权威域名服务器的列表。本地DNS服务器再向其中一个权威域名服务器发送一个请求,返回www.baidu.com所对应的IP地址。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。
接下来要怎么传呢?
拼接到 URL:直接把 SessionID 作为 URL 的请求参数。放到请求头:把 SessionID 放到请求头里面,比较常用。TCP 三次握手过程:
最开始,客户端和服务端都处于 CLOSE(关闭)状态,服务端监听客户端的请求,进入 LISTEN(监听) 状态。客户端发送连接请求,进行第一次握手(同步位 SYN=1,序号字段 seq=x),发送完毕后,客户端就进入 SYN_SENT(同步已发送) 状态。服务端确认连接,进行第二次握手(同步位 SYN=1,确认位 ACK=1,序号字段 seq=y,确认号字段 ack=x+1), 发送完毕后,服务端就进入 SYN_RCV(同步已接收) 状态。客户端收到服务端的确认后,再次向服务端确认,进行第三次握手(确认位 ACK=1,确认号字段 ack=y+1),发送完毕后,客户端就进入 ESTABLISHED(连接已建立) 状态,当服务端接收到这个包时,也进入 ESTABLISHED(连接已建立) 状态。为什么不能是两次?
防止已失效的连接请求报文段突然又传到服务端,因而产生错误
所以我们需要 “第三次握手” 来确认这个过程:
通过第三次握手的数据来告诉服务端,客户端有没有收到服务端 “第二次握手” 时传过去的数据,以及这个连接的序号是不是有效的。若发送的这个数据是 “收到且没有问题” 的信息,服务端接收后就可以正常建立 TCP 连接,否则建立 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 后才进入关闭状态,这是否有必要呢 ?
服务端发送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 状态就是为了保证服务端在关闭连接之前将待发送的数据处理完。
TCP主要提供了 连接管理、校验和、序列号/确认应答、流量控制、最大消息长度、超时重传、拥塞控制等方式实现了可靠传输。
连接管理:TCP 使用三次握手和四次挥手来保证可靠地建立连接和释放连接。校验和:用来检查整个 TCP 报文段在传输过程中是否出现了误码。序列号/确认应答:TCP 会给发送的每一个包进行编号,接收方会对收到的包进行应答,发送方就会知道接收方是否收到对应的包,如果发现没有收到,就会重发,这样就能保证数据的完整性了。流量控制:TCP 连接的每一方都有固定大小的缓冲空间,TCP 的接收端只允许发送端发送接收端缓冲区能接纳的数据大小。当接收方来不及处理发送方的数据时,能提示发送方降低发送的速率,防止包丢失。TCP 使用的流量控制协议是可变大小的滑动窗口协议。(TCP 利用滑动窗口实现流量控制)最大消息长度:在建立 TCP 连接的时候,双方约定一个最大的长度(MSS)作为发送的单位,重传的时候也是以这个单位来进行重传的。理想情况下是该长度的数据刚好不被网络层分块。超时重传:超时重传是指发送出去的数据包到接收到确认包之间的时间,如果超过了这个时间,就会被认为是丢包了,需要重传。拥塞控制:如果网络非常拥堵,此时再发送数据就会加重网络负担,那么发送的数据段很可能超过了最大生存时间也没有到达接收方,就会产生丢包问题。为此 TCP 引入了慢启动机制,先发出少量数据,就像探路一样,先摸清当前的网络拥堵状态后,再决定按照多大的速度传送数据。TCP 提供了一种机制,可以让发送方根据接收方的实际接收能力控制发送的数据量,这就是流量控制。
TCP 通过「滑动窗口」来实现流量控制
首先 TCP 双方进行三次握手,初始化各自的窗口大小,均为 400 字节。TCP 发送一个数据,如果需要收到确认应答才会发送下一个数据。这样的话就会有个缺点:效率会比较低。
为了解决这个问题,TCP 引入了滑动窗口,它是操作系统开辟的一个缓存空间。窗口大小表示无需等待确认应答而可以继续发送数据的最大值。
TCP 头部有个 16 位的窗口大小,它告诉对方本端的 TCP 接收缓冲区还能容纳多少字节的数据,这样对方就可以控制发送数据的速度,从而达到流量控制的目的。
通俗点讲,就是接收方每次收到数据包,在发送确认报文的时候,同时告诉发送方,自己的接收缓冲区还有多少空闲空间,缓冲区的空闲空间,我们就称之为接收窗口大小。
TCP 滑动窗口分为两种: 发送窗口和接收窗口。
发送方的滑动窗口包含四个部分:
已发送且已收到 ACK 确认已发送但未收到 ACK 确认未发送但可以发送未发送且不可发送接收方的滑动窗口包含三个部分:
已成功接收并确认未收到数据但可以接收未收到数据且不可以接收的数据什么是拥塞控制?不是有了流量控制吗?
前⾯的流量控制是避免发送⽅的数据填满接收⽅的缓存,但是并不知道整个⽹络中发⽣了什么。
⼀般来说,计算机⽹络都处在⼀个共享的环境。因此也有可能会因为其他主机之间的通信使得⽹络出现拥堵。
在⽹络出现拥堵时,如果继续发送⼤量数据包,可能会导致数据包延迟、丢失等,这时 TCP 就会重传数据,但是⼀重传就会导致⽹络的负担更重,于是会导致更⼤的延迟以及更多的丢包,这个情况就会进⼊恶性循环并且被不断地放⼤…
所以,TCP 不能忽略整个网络中发⽣的事,它被设计成⼀个⽆私的协议,当⽹络发送拥塞时,TCP 会⾃我牺牲,降低发送的数据流。
于是,就有了拥塞控制,拥塞控制的⽬的就是为了避免发送⽅的数据填满整个⽹络。
就像是一个水管,不能让太多的水(数据流)流入水管,如果超过水管的承受能力,水管就会被撑爆(丢包)。
发送方维护一个拥塞窗口 cwnd(congestion window) 的变量,调节所要发送数据的量。
什么是拥塞窗⼝?和发送窗⼝有什么关系呢?
拥塞窗⼝ **cwnd **是发送⽅维护的⼀个状态变量,它会根据⽹络的拥塞程度动态变化。
发送窗⼝ swnd 和接收窗⼝ rwnd 是约等于的关系,那么由于加⼊了拥塞窗⼝的概念后,此时发送窗⼝的值 swnd = min(cwnd, rwnd),也就是取拥塞窗⼝和接收窗⼝中的最⼩值。
拥塞窗⼝ cwnd 变化的规则:
只要⽹络中没有出现拥塞, cwnd 就会增⼤但如果⽹络中出现了拥塞, cwnd 就会减小拥塞控制有哪些常用算法?
慢启动慢启动算法,慢慢启动。
它表示 TCP 建立连接完成后,一开始不要发送大量的数据,而是先探测一下网络的拥塞程度。由小到大逐渐增加拥塞窗口的大小,如果没有出现丢包,每收到一个 ACK,就将拥塞窗口 cwnd 的大小加 1(单位是 MSS)。每轮次发送窗口增加一倍,呈指数增长,如果出现丢包,拥塞窗口就减半,进入拥塞避免阶段。
举个例子:
连接建⽴完成后,⼀开始初始化 cwnd = 1 ,表示可以传⼀个 MSS ⼤⼩的数据。当收到⼀个 ACK 确认应答后,cwnd 增加 1,于是⼀次性能够发送 2 个。当收到 2 个 ACK 确认应答后, cwnd 增加 2,于是就能⽐之前多发送 2 个,所以这⼀次能够发送 4 个。当这 4 个 ACK 确认到来的时候,每个确认 cwnd 增加 1, 4 个确认 cwnd 增加 4,于是就能⽐之前多发送 4 个,所以这⼀次能够发送 8 个。发送包的个数是呈指数性增⻓的。
为了防止 cwnd 增长过大而引起网络拥塞,还需设置一个慢启动阀值 ssthresh(slow start threshold)的状态变量。当 cwnd 到达该阀值后,就好像水管被关小了水龙头一样,减少了拥塞状态。即当 cwnd > ssthresh 时,进入拥塞避免算法。
拥塞避免一般来说,慢启动阀值 ssthresh 的大小是 65535 字节,cwnd 到达慢启动阀值后
每收到一个 ACK 时,cwnd = cwnd + 1/cwnd当每过一个 RTT 时,cwnd = cwnd + 1显然这是一个线性上升的算法,可以避免发送过快导致网络出现拥塞问题。
接着上面慢启动的例子,假定 ssthresh 为 8:
当 8 个 ACK 确认应答到来时,每个确认增加 1/8,8 个 ACK 确认后 cwnd ⼀共增加 1,于是这⼀次能够发送 9 个 MSS ⼤⼩的数据,变成了线性增⻓。当网络拥塞发生丢包时,会有两种情况:
RTO 超时重传快速重传如果是发生了RTO 超时重传,就会使用「拥塞发生」算法
慢启动阀值 sshthresh = cwnd/2cwnd 重置为 1进入新的慢启动过程这真的是辛辛苦苦几十年,一朝回到解放前。其实还有更好的处理方式,就是「快速重传」。当发送方收到 3 个连续的重复 ACK 时,就会快速地重传,不必等待 RTO超时 再重传。
发⽣「快速重传」的拥塞发⽣算法:
拥塞窗口大小 cwnd = cwnd/2慢启动阀值 ssthresh = cwnd进入快速恢复算法快速恢复快速重传和快速恢复算法一般是同时使用的。快速恢复算法认为,还能收到 3 个重复的 ACK,说明网络也没有那么糟糕,所以没必要像 RTO超时重传 那样强烈。
正如前面所说的,进入快速恢复之前,cwnd 和 sshthresh 已被更新:
cwnd = cwnd/2sshthresh = cwnd然后,真正进入「快速恢复」算法:
cwnd = sshthresh + 3重传重复的那几个 ACK(即丢失的那几个数据包)如果再收到重复的 ACK,那么 cwnd = cwnd +1如果收到新数据的 ACK 后,cwnd = sshthresh。因为收到新数据的 ACK,表明恢复过程已经结束,可以再次进入「拥塞避免」算法了。重传包括:超时重传、快速重传、带选择确认的重传(SACK)、重复 SACK 四种。
超时重传超时重传,是 TCP 协议保证数据可靠性的另一个重要机制,其原理是在发送某一个数据以后就开启一个重传计时器,在一定时间内如果没有收到发送的数据报的 ACK 报文,那么就重新发送数据,直到收到 ACK 报文为止。
超时时间应该设置为多少合适呢?
RTT 就是数据完全发送完,到收到确认信号的时间,即数据包的一次往返时间。
超时重传时间,就是 RTO(Retransmission Timeout)。那么,RTO 应该设置多大呢?
如果 RTO 设置很大,等了很久都没重发,这样肯定不行。如果 RTO 设置很小,那很可能数据都没有丢失,就开始重发了,这将会导致网络阻塞,从而发生恶性循环,导致更多的超时出现。一般来说,RTO 略微大于 RTT,效果是最佳的。
超时重传并不是十分完美的重传方案,它有这些缺点:
当一个报文丢失时,会等待一定的超时周期,才重传分组,增加了端到端的时延。当一个报文丢失时,在其等待超时的过程中,可能会出现这种情况:其后面的报文段已经被接收方接收了但却迟迟得不到确认,发送方会认为其后面的报文段也丢失了,从而引起不必要的重传,既浪费资源也浪费时间。快速重传快速重传可以用来解决超时重发的时间等待问题。
它不以时间驱动,而是以数据驱动。它是基于接收方的反馈信息来引发重传的。
快速重传的流程如下:
发送方发送了 1,2,3,4,5,6 份数据:
第一份 Seq=1 先送到了,于是 ACK 回2;第二份 Seq=2 也送到了,于是 ACK 回3;第三份 Seq=3 由于网络等某些原因,没送到;第四份 Seq=4 送到了,但是由于 Seq=3 没收到。因此 ACK 还是回3;后面的 Seq=5,6 也送到了,ACK 还是回复3,因为 Seq=3 没有收到。发送方连续收到三个重复冗余的 ACK=3 的确认(其实是4个哈,但是因为前面的一个是正常的ACK,后面三个才是重复冗余的),于是知道哪个报文段在传输过程中丢失了;发送方就在重传定时器过期之前,重传该报文段。最后,接收方收到了 Seq=3,此时因为 Seq=4,5,6 都收到了,于是它回 ACK=7。快速重传机制也有缺点:发送方并不知道到底是哪个报文丢失了,到底该重传多少个数据包?
是只重传 Seq=3 ?还是重传 Seq=3、Seq=4、Seq=5、Seq=6 呢?因为发送方并不清楚这三个连续的 ACK=3 是谁传回来的。
带选择确认的重传(SACK)为了解决应该重传多少个包的问题? TCP 提供了带选择确认的重传(即 SACK,Selective Acknowledgment)。
SACK 机制就是,在快速重传的基础上,接收方返回最近收到报文段的序列号范围,这样发送方就知道接收方哪些数据包是没收到的。这样就很清楚应该重传哪些数据包。
如上图中,发送⽅收到了三次同样的 ACK 确认报⽂,于是就会触发「快速重传」机制,通过 SACK 信息发现只有 200~299 这段数据丢失,则重发时,就只选择了这个 TCP 段进⾏重发。
重复 SACK(D-SACK)D-SACK,英文是 Duplicate SACK,是在 SACK 的基础上做了一些扩展,主要用来告诉发送方,有哪些数据包,自己重复接受了。
D-SACK 的目的是帮助发送方判断,是否发生了包失序、ACK 丢失、包重复或伪重传。让 TCP 可以更好的做网络流控。
例如 ACK 丢包导致的数据包重复:
什么是 TCP 粘包和拆包?
TCP 是面向字节流,没有界限的一串数据。TCP 底层并不了解上层业务数据的具体含义,它会根据 TCP 缓冲区的实际情况进行包的划分,所以在业务上认为,一个完整的包可能会被 TCP 拆分成多个包进行发送,也有可能把多个小的包封装成一个大的数据包进行发送,这就是所谓的 TCP 粘包和拆包问题。
为什么会产生粘包和拆包呢?
要发送的数据小于 TCP 发送缓冲区的大小,TCP 将多次写入缓冲区的数据一次性发送出去,将会发生粘包;接收方的应用层没有及时读取接收缓冲区的数据,将会发生粘包;要发送的数据大于 TCP 发送缓冲区剩余空间的大小,将会发生拆包;待发送的数据大于 MSS(最大报文长度),TCP 在传输前将会进行拆包。即 TCP报文长度 - TCP头部长度 > MSS。解决方案:
发送方给每个数据包添加包首部,首部中应该至少包含数据包的长度,这样接收方在收到数据后,通过读取包首部的长度字段,便知道每一个数据包的实际长度了。发送方将每个数据包封装为固定长度(不够的可以通过补0填充),这样接收方每次从接收缓冲区中读取固定长度的数据,就自然而然的把每个数据包拆分开来。可以在数据包之间设置边界,如添加特殊符号,这样接收方通过这个边界就可以将不同的数据包拆分开来。