TCP协议的特点
TCP 报文段
TCP "三次握手"
TCP "四次挥手"
客户端和服务器端所经历的状态
TCP 可靠传输
TCP流量控制
TCP拥塞控制
面试相关问题
本篇博文主要是为了复习 TCP 协议而做的总结。其中很多内容都是来自于《计算机网络》,《Linux网络编程》,《TCP/IP详解》等书籍。首先可以从 TCP 协议思维导图看到本文的大致内容。虽然有很多内容,但"三次握手"、"四次挥手"和可靠传输机制才是本文的重点,其中会用 wireshark 抓取相应的包用于协议分析。最后也会总结一些面试常问的问题。TCP 协议思维导图如下:
TCP 是在不可靠的 IP 层之上实现的可靠的数据传输协议,它主要解决传输的可靠、有序、无丢失和不重复的问题。TCP 的主要特点有:
(1) TCP 是面向连接的传输层协议。
(2) 每一条 TCP 连接只能有两个端点,每一条 TCP 连接只能是点对点的(一对一)。
(3) TCP 提供可靠的交付服务,保证传送的数据无差错、不丢失、不重复且有序。
(4) TCP 提供全双工通信,TCP 允许通信双方的应用进程在任何时候都能发送数据,为此 TCP 连接的两端都设有发送缓存和接收缓存,用来临时存放双向通信的数据。
发送缓存用来暂存以下数据:①应用程序传送给发送方 TCP 准备发送的数据;② TCP 已发送出但尚未收到的确认的数据。
接收缓存用来暂存以下数据:①按序到达的但尚未被接收应用程序读取的数据;②不按序到达的数据。
(5) TCP 是面向字节流的,虽然应用程序和 TCP 的交互是一次一个数据块,但 TCP 把应用程序交下来的数据看成仅仅是一连串的无结构的字节流。
TCP 传输的数据单元称为报文段。一个 TCP 报文段分为 TCP 首部和 TCP 数据两部分,整个 TCP 段作为 IP 数据报的数据部分封装在 IP 数据报中。
各字段的含义如下:
(1) 源端口和目的端口:各占2个字节。端口是运输层与应用层的服务接口。运输层的复用和分用功能都要通过端口才能实现。
(2) 序号:占4个字节。TCP是面向字节流的,所以TCP连接中传送的数据流中的每一个字节都编上一个序号。序号字段的值则指的是本报文段所发送的数据的第一个字节的序号。例如,一个报文段的序号字段值是200,携带的数据总共有100字节,表明这个报文段的数据的最后一个字节的序号是299,所以下一个报文段的数据序号应从300开始。
(3) 确认号:占4个字节,是期望收到对方的下一个报文段的数据的第一个字节的序号。若确认号为N,表明前N-1的所有数据都已经正确接收。例如,B正确的收到了A发送过来的一个报文段,其序号字段是指501,而数据长度是200字节(序号501~700),表明B正确的收到了A发送的序号700之前的数据。因此B希望收到A的下一个数据序号是701,所以B在发送给A的确认报文段中应把确认号设置成701。
(4) 数据偏移量:占4位,这里不是IP数据报分片的那个数据偏移,而是表示首部长度,它指出TCP报文段的数据起始处距离TCP报文段的起始处有多远。该字段若为15,则表明TCP首部达到最大长度60字节(以4字节为计算单位乘以15)。
(5) 保留:占6位,目前不使用,所以置为0。
(6) 紧急位 URG:当其值为1时,表明紧急指针字段有效。它会告诉系统此报文段中有紧急数据,应该尽快传送。但是URG需要和紧急指针配套使用,即从第一个字节到紧急指针所指字节就是紧急数据。
(7) 确认位 ACK:只有当ACK=1时,确认号字段才有效。TCP规定,在连接建立后所有传送的报文段都必须把ACK置为1。
(8) 推送位 PSH:TCP收到PSH=1的报文段,就尽快的交付接收应用进程,而不再等到整个缓存都填满了后再向上交付。
(9) 复位位 RST:当RST=1时,表明TCP连接中出现了严重的错误,必须释放连接,然后再重新建立运输连接。
(10) 同步位 SYN:SYN=1表示这是一个连接请求或连接接收报文。当SYN=1,ACK=0时,表明这是一个连接请求报文,对方若同意建立连接,则在响应报文中使用SYN=1,ACK=1。
(11) 终止位 FIN:用来释放一个连接。FIN=1表明此报文段的发送方的数据已经发送完毕,并要求释放传输连接。
(12) 窗口:占2个字节。它指出现在允许对方发送的数据量,接收方的数据缓存空间是有限的,所以使用窗口值作为接收方让发送方设置其发送窗口的依据,单位是字节。例如,设确认号是101,窗口字段是1000.这就表明,从101号开始,发送此报文段的一方还有接收1000字节数据(字节序号是101~1100)的接收缓存空间。
(13) 校验和:占2个字节。它的检验范围包括首部和数据部分。计算时要在TCP报文段前面加上12字节的伪首部。
(14) 紧急指针:占16位,指出在本报文段中紧急数据共有多少个字节。
(15) 选项:长度可变。TCP最初只规定了一种选项,即最大报文段长度(MSS)。
(16) 填充:这是为了使整个首部长度是4字节的整数倍。
主要需要理解以下几个字段:
源端口号和目标端口号:谁发的和发给谁(类似于哲学问题:你是谁?从哪里来?到哪里去?);
序号:为了解决乱序问题;
确认序号:发出去的包应该有确认,没有收到就应该重新发送,直到送达;
状态位:常见的有SYN、ACK、FIN,分别表示发起一个连接、确认和结束连接;
窗口大小:用于TCP流量控制,通信双方各声明一个窗口,说明自己当前的处理能力。发送的太快,处理不了,发的太慢,影响发送的效率;
第一次握手:客户端首先向服务器发送一个连接请求报文段。这个特殊的报文段中不含应用层数据,其首部中的SYN标志位被置为1。另外,客户端会随机选择一个起始号seq=x(连接请求报文不携带数据,但要消耗掉一个序号)。客户端进程进入SYN_SENT(同步发送)状态。
第二次握手:服务器收到连接请求报文段后,如同意建立连接,就向客户端发回确认,并为该TCP连接分配TCP缓存和变量。在确认报文段中,SYN和ACK位都被置为1,确认号字段的值为x+1,并且服务器随机产生起始序号seq=y(确认报文不携带数据,但也要消耗掉一个序号)。确认报文段同样不包含应用层数据。服务端进入SYN_RCVD(同步接收)状态。
第三次握手:当客户端收到确认报文段后,还要向服务器给出确认,并且也要给该连接分配缓存和变量。这个报文段的ACK标志位被置为1,序号字段为x+1,确认号字段为ack=y+1。该报文可以携带数据,如果不携带数据则不消耗序号。客户端进入ESTABLISHED(建立连接)状态。
在成功的完成以上三步后,TCP连接就建立了,接下来就可以传送应用层数据了。TCP提供的是全双工通信,因此通信双方的应用进程在任何时候都能发送数据。注意:服务器端的资源是在完成第二次握手时分配的,而客户端的资源是在完成第三次握手时分配的。所以服务器易于受到SYN洪泛攻击。
TCP "三次握手"报文详细分析
TCP报文:[29 05 01 bb f4 0a 2c 77 1f a9 79 d2 50 10 01 00 b9 84 00 00]
为了更易理解:[29 05 01 bb]
[f4 0a 2c 77]
[1f a9 79 d2]
[50 10 01 00]
[b9 84 00 00]
源端口:2905(HEX) = 10501(DEC)
目的端口:01bb(HEX) = 443(DEC)
序号:1 为什么连接过程中的"序号"显示1,而相应字段的值不是1。详情请看面试问题的(6)
确认号:1
十六进制[50 10]转化成二进制[0101 0000 0001 0000]
数据偏移(占4位):单位是4个字节,此字段值为5,所以TCP首部的长度是20字节。
保留(占6位):保留为今后使用,目前置为0。
紧急位URG:0
确认位ACK:1
推送位PSH:0
复位位RST:0
同步位SYN:0
终止位FIN:0
窗口:0100(HEX) = 256(DEC)
检验和:0xb984
紧急指针:0
参与TCP连接的两个进程中的任何一个都能终止该连接。
第一步:若客户端打算关闭连接,就向其服务器发送一个连接释放报文段,并停止再发送数据,主动关闭TCP连接,该报文段的FIN标志位被置为1,seq=u,它等于前面已传送过的数据的最后一个字节的序号加1(FIN报文段即使不携带数据,也要消耗掉一个序号)。TCP是全双工的,即可以想象成是一条TCP连接上有两条数据通路。当发送FIN报文时,发送FIN的一端就不能再发送数据,也就是关闭了其中一条数据通路,但另一条没有关闭的数据通路仍可以发送数据。
第二步:服务器收到连接释放报文段后就会发出确认,确认号为ack=u+1,而这个报文段自己的序号是v,等于它前面已传送过的数据的最后一个字节的序号加1。此时,从客户端到服务器这个方向的连接就释放了,TCP连接处于半关闭状态。但服务器若发送数据,客户端仍能接收,即从服务器到客户端这个方向的连接并未关闭。
第三步:若服务器已经没有要向客户端发送的数据,就释放连接,此时发出FIN=1的连接释放报文段。
第四步:客户端收到连接释放报文段后,必须发出确认。在确认报文段中,ACK字段被置为1,确认号ack=w+1,序号seq=u+1。此时TCP连接还没有释放掉,必须经过2MSL后,客户端才进入连接关闭状态。
结合连接和释放过程图看状态转换图,以下是客户端的状态转换图:
服务器端的状态转换图:
完整的状态转换图(红线是客户端的过程,蓝线是服务器的过程,表示正常的连接和断开过程):
断开过程中的状态解析
(1)FIN_WAIT_1: FIN_WAIT_1和FIN_WAIT_2状态的真正含义都是表示等待对方的FIN报文。而这两种状态的区别是: FIN_WAIT_1状态实际上是当SOCKET在ESTABLISHED状态时,它想主动关闭连接,向对方发送了FIN报文,此时该SOCKET即进入到FIN_WAIT_1状态。而当对方回应ACK报文后,则进入到FIN_WAIT_2状态,当然在实际的正常情况下,无论对方何种情况下,都应该马上回应ACK报文,所以FIN_WAIT_1状态一般是比较难见到的,而FIN_WAIT_2状态还有时常常可以用netstat看到。(主动方)
(2)FIN_WAIT_2:上面已经详细解释了这种状态,实际上FIN_WAIT_2状态下的SOCKET,表示半连接,也即有一方要求close连接,但另外一方告诉对方,我暂时还有点数据需要传送给你(ACK信息),稍后再关闭连接。(主动方)
(3)TIME_WAIT: 表示收到了对方的FIN报文,并发送出了ACK报文,就等2MSL后即可回到CLOSED可用状态了。如果FIN_WAIT_1状态下,收到了对方同时带FIN标志和ACK标志的报文时,可以直接进入到TIME_WAIT状态,而无须经过FIN_WAIT_2状态。(主动方)
(4)CLOSING(比较少见): 这种状态比较特殊,实际情况中应该是很少见,属于一种比较罕见的例外状态。正常情况下,当你发送FIN报文后,按理来说是应该先收到(或同时收到)对方的 ACK报文,再收到对方的FIN报文。但是CLOSING状态表示你发送FIN报文后,并没有收到对方的ACK报文,反而却也收到了对方的FIN报文。什么情况下会出现此种情况呢?其实细想一下,也不难得出结论:那就是如果双方几乎在同时close一个SOCKET的话,那么就出现了双方同时发送FIN报文的情况,也即会出现CLOSING状态,表示双方都正在关闭SOCKET连接。
(5)CLOSE_WAIT: 你需要查看你是否还有数据发送给对方,如果没有的话,那么你也就可以close这个SOCKET,发送FIN报文给对方,即关闭连接。所以你在CLOSE_WAIT状态下,需要完成的事情是等待你去关闭连接。(被动方)
(6)LAST_ACK: 它是被动关闭一方在发送FIN报文后,最后等待对方的ACK报文。当收到ACK报文后,也即可以进入到CLOSED可用状态了。(被动方)
(7)CLOSED: 表示连接中断。
断开连接时的意外
当TCP连接发生一些物理上的意外情况时,例如网线断开,linux上的TCP实现会依然认为该连接有效,而windows则会在一定时间后返回错误信息。这似乎可以通过设置SO_KEEPALIVE选项来解决,不过不知道这个选项是否对于所有平台都有效。
TIME_WAIT状态所带来的影响
当某个连接的一端处于TIME_WAIT状态时,该连接将不能再被使用。事实上,对于我们比较有现实意义的是,这个端口将不能再被使用。某个端口处于TIME_WAIT状态(其实应该是这个连接)时,这意味着这个TCP连接并没有断开(完全断开),那么,如果你bind这个端口,就会失败。对于服务器而言,如果服务器突然crash掉了,那么它将无法再2MSL内重新启动,因为bind会失败。解决这个问题的一个方法就是设置socket的SO_REUSEADDR选项。这个选项意味着你可以重用一个地址。
注意:当建立一个TCP连接时,服务器端会继续用原有端口监听,同时用这个端口与客户端通信。而客户端默认情况下会使用一个随机端口与服务器端的监听端口通信。有时候,为了服务器端的安全性,我们需要对客户端进行验证,即限定某个IP某个特定端口的客户端。客户端可以使用bind来使用特定的端口。对于服务器端,当设置了SO_REUSEADDR选项时,它可以在2MSL内启动并listen成功。 但是对于客户端,当使用bind并设置SO_REUSEADDR时,如果在2MSL内启动,虽然bind会成功,但是在windows平台上connect会失败。而在linux上则不存在这个问题。
TCP的任务是在IP层的不可靠、尽力而为服务的基础上建立一种可靠数据传输服务。TCP提供的可靠数据传输服务就是要保证接收方进程从缓冲区读出的字节流与发送方发出的字节流是完全一样的。TCP使用了校验、序号、确认和重传机制来达到这个目的。
校验
在计算校验和时,要在TCP数据报之前增加12个字节的伪首部,伪首部并不是TCP报文段真正的首部。只是在计算校验和时,临时添加在TCP数据报文段的前面,得到一个临时的TCP报文段。伪首部既不向下传送也不向上递交,而仅仅是为了计算校验和。注意:IP数据报的校验和只检验IP数据报的首部,但TCP的校验和会把首部和数据部分一起检验。
序号
TCP首部的序号字段用来保证数据能有序提交给应用层,TCP把数据看成一个无结构但是有序的字节流,而序号是建立在传送的字节流之上,而不是建立在报文段之上。TCP连接中传送的数据流中的每一个字节都编上一个序号。序号字段的值则指的是本报文段所发送的数据的第一个字节的序号。
确认
TCP首部的确认号是期望收到对方的下一个报文段的数据的第一个字节的序号。TCP默认使用累计确认,即TCP只确认数据流中至第一个丢失字节为止的字节。例如,接收方B收到了发送方A发送的包含字节0~2和6~7的报文段。由于某些原因,B还没有收到字节3~5的报文段,此时B仍在等待字节3(和其后面的字节),因此,B到A的下一个报文段将确认号字段设置为3。
重传
有两种事件会导致TCP对报文段进行重传:超时和冗余ACK。
(1) 超时
TCP每发送一个报文段,就对这个报文段设置一次计时器。只要计时器设置的重传时间到期但还没有收到确认,就要重传这一报文段。当检测到接收数据有错误时,会采取直接丢弃出错的数据,发送端等待接收端的确认超时后,会自动重发该报文段。
由于IP数据报在传输的时候选择的路由变化很大,因此传输层的往返时延的方差很大。为了计算超时计时器的重传时间,TCP采用一种自适应算法,它记录一个报文段发出的时间,以及收到相应确认的时间,这两个时间之差叫做报文段的往返时间(RTT)。TCP保留了RTT的一个加权平均往返时间RTTs,当第一次测量RTT样本时,RTTs值就为所测量到的RTT样本的值,但之后每测量一个新的RTT样本,就按下式重新计算一次RTTs:
新的RTTs = ( 1- a ) * (旧的RTTs) + a(新的RTT样本)
在上式中0 <= a < 1。若a很接近于零,表示新的RTTs值和旧的RTTs值相比变化不大,而受新的RTT样本影响不大(RTT值更新较慢)。若a接近于1,则表示新的RTTs值受新的RTT样本的影响较大(RTT值更新较快)。[RFC 2988]推荐的a值为0.125。
所以超时计时器设置的超时重传时间(RTO)应略大于上面得出的加权平均往返时间RTTs。即RTO = RTTs + 4RTTd。其中RTTd是RTT的偏差的加权平均值,它与RTTs和新的RTT样本之差有关。当第一次测量时,RTTd取为测量到的RTT样本值的一半,以后测量中,使用下式计算:新的RTTd = (1-β) *(旧的RTTd) + β*|RTTs - 新的RTT样本|,其中β是个小于1的系数,它的推荐值是0.25。
(2) 冗余ACK
超时触发重传存在的一个问题就是超时周期往往太长。幸运的是,发送方通常可在超时事件发生之前通过注意冗余ACK来较好地检测丢包情况。冗余ACK就是再次确认某个报文段的ACK,而发送方先前已经收到过该报文段的确认。例如,发送方A发送了序号为1、2、3、4、5的TCP报文段,其中2号报文段丢失了,它将无法到达接收方B。因此,3、4、5号报文段对于B来说就成了失序报文段。TCP规定每当比期望序号大的失序报文段到达时,发送冗余ACK,指明下一个期望字节的序号[RFC 1122, RFC 2581]。在这个例子中,3、4、5号报文到达B,但它们不是B所期待的下一个报文,于是B就发送3个对1号报文段的冗余ACK,表示自己期望接收2号报文段。TCP规定当发送方收到对同一个报文段的3个冗余ACK时,就可以认为跟在这个被确认报文段之后的报文段已经丢失。就前面的例子而言,当A收到对于1号报文段的3个冗余ACK时,则它可以认为2号报文段已经丢失。这时发送方A可以立即对2号报文执行重传,这种技术成为快速重传。
TCP提供了流量控制服务以消除发送方使接收方缓存区溢出的可能性,因此TCP流量控制是为了匹配发送方的发送速率与接收方的读取速率。TCP提供一种基于滑动窗口协议的流量控制机制。其原理是:在通信过程中,接收方根据自己接收缓存的大小,动态地调整发送方的发送窗口大小,这就是接收窗口rwnd,即调整TCP报文段首部中的"窗口"字段的值,来限制发送方向网络注入报文的速率。同时,发送方根据其对当前网络拥塞程度的估计而确定窗口值,称为拥塞窗口cwnd,其大小与网络的带宽和时延密切相关。
例如,在通信中,有效数据只从A发往B,而B仅向A发送确认报文,这时,B就可以通过设置确认报文段首部的窗口字段来将rwnd值来限制自己发送窗口的大小,这样可以将未确认的数据量控制再rwnd大小之内,保证了A不会使B得接收缓存溢出。A的发送窗口的实际大小取的是rwnd和cwnd中的最小值。
设主机A向主机B发送数据,在连接建立时,B告诉A:"我的接收窗口rwnd = 400(字节)"。
传输层和数据链路层的流量控制的区别在于:传输层定义了端到端用户之间的流量控制,数据链路层定义了两个中间的相邻结点的流量控制。另外,数据链路层滑动窗口协议的窗口大小不能动态变化,传输层则可以动态变化。
所谓的拥塞控制就是为了防止过多的数据注入网络中,这样可以使网络中的路由器或链路不会过载。当出现拥塞时,端点并不能了解到拥塞发生的细节,对通信连接的端点来说,拥塞往往表现为通信时延的增加。拥塞控制和流量控制相似的地方是通过控制发送方发送数据的速率来达到效果。
拥塞控制与流量控制的区别:拥塞控制是让网络能够承受现有的网络负荷,它是一个全局性的过程,涉及所有的主机、所有的路由器,以及与降低网络传输性能有关的所有因素。而流量控制往往使指点对点通信量的控制,即接收端控制发送端,它所做的就是抑制发送端发送数据的速率,以便使接收端来得及接收。
为了更好地对传输层进行拥塞控制,有以下四种算法:慢开始、拥塞避免、快重传、快恢复。发送方在确定发送报文段的速率时,既要根据接收方的接收能力,又要从全局考虑不要使网络发生拥塞。因此,TCP协议要求发送方维护以下两个窗口:
(1)接收窗口rwnd,接收方根据目前接收缓存大小所许诺的最新的窗口值,反映了接收方的容量。有接收方根据其放在TCP报文的首部的"窗口"字段通知发送方。
(2)拥塞窗口cwnd,发送方根据自己估算的网络拥塞程度而设置的窗口值,反映了网络当前容量。只要网络没有出现拥塞,拥塞窗口就再增大一些,以便把更多的分组发送出去。但只要网络出现拥塞,拥塞窗口就减少一些,以减少注入网络中的分组数。发送窗口的上限值应当取接收窗口rwnd和拥塞窗口cwnd中较小的一个,即:Min[rwnd, cwnd]。
慢开始和拥塞避免
(1)慢开始算法
在TCP刚刚连接好,开始发送TCP报文段时,先让拥塞窗口cwnd=1,即一个最大报文段长度MSS。而在每收到一个对新的报文段的确认后,将cwnd加倍,即刚开始会增大一个MSS。用这样的方法逐步增大发送方的拥塞窗口cwnd,可以使分组注入到网络的速率更加合理。例如,A向B发送数据,当发送时A的拥塞窗口为2,那么A一次可以发送两个TCP报文段,当经过一个RTT后,A收到B对刚才两个报文的确认,于是就把拥塞窗口调整为4,即下一次发送时就可以发送4个报文段。
使用慢开始算法后,每经过一个传输轮次,拥塞窗口cwnd就会加倍,即cwnd的大小呈指数形式增长。这样慢开始一直把拥塞窗口cwnd增大到一个规定的慢开始门限ssthresh(阈值),然后改用拥塞避免算法。
(2)拥塞避免算法
拥塞避免算法的做法是:发送端的拥塞窗口cwnd每经过一个往返时延RTT就增加一个MSS的大小,而不是加倍,使cwnd按线性规律缓慢增长(即加法增大),而当出现一次超时(网络拥塞)时,会令慢开始门限ssthresh等于当前cwnd的一半(即乘法减小)。
根据cwnd的大小执行不同的算法,可归纳为:
当cwnd < ssthresh时,使用慢开始算法。
当cwnd > ssthresh时,改用拥塞避免算法。
当cwnd = ssthresh时,既可以使用慢开始,也可以使用拥塞避免算法。
(3)网络拥塞的处理
当网络出现拥塞时,无论是在慢开始阶段还是在拥塞避免阶段,只要发送方检测到超时事件的发生(没有按时收到确认,重传计时器超时),就会把慢开始门限ssthresh设置为出现拥塞时的发送窗口cwnd值的一半(但不能小于2)。然后把拥塞窗口cwnd重新设置为1,执行慢开始算法。这样做的目的就是要迅速减少主机发送到网络中的分组数,使得发生拥塞的路由器有足够的时间把队列中积压的分组处理完。拥塞避免是指在拥塞避免阶段把拥塞窗口控制为线性规律增长,使网络比较不容易出现拥塞,利用以上措施想要完全避免网络拥塞是不可能的。
1.初始时,拥塞窗口置为1,即cwnd=1,慢开始门限置为16,即ssthresh=16。慢开始阶段,cwnd初值为1,以后发送方每收到一个确认ACK,cwnd值加倍,即经过每个传输轮次(RTT),cwnd呈指数规律增长。
2.当拥塞窗口cwnd增长到慢开始门限ssthresh时(即当cwnd=16时),就改用拥塞避免算法,cwnd按线性规律增长。
3.若此时cwnd=24时,网络发生拥塞,更新ssthresh值为12(即变为超时时cwnd值的一半),cwnd重置为1,并执行慢开始算法,当cwnd=12时,改为拥塞避免算法。
注意,在慢开始阶段,若2*cwnd > ssthresh,则下一个RTT的cwnd应等于ssthresh,而不是2*cwnd,即cwnd不能越过ssthresh值。如上图,在16个轮次时,cwnd=8、ssthresh=12,第17轮次时,cwnd=12,而不是16。
快重传和快恢复
(1)快重传
TCP可靠传输机制中,快速重传技术使用了冗余ACK来检测丢包的发生。同样,冗余ACK也用于网络拥塞的检测。快重传并非取消重传计时器,而是在某些情况下可更早的重传丢失的报文段。当发送方连续收到三个重复的ACK报文时,直接重传对方尚未收到的报文段,而不必等待那个报文段设置的重传计时器超时。
如果收到3个相同的ACK。TCP在收到乱序到达包时就会立即发送ACK,TCP利用3个相同的ACK来判定数据包的丢失,此时进行快速重传,快速重传做的事情有:
1.把ssthresh设置为cwnd的一半。
2.把cwnd再设置为ssthresh的值(具体实现有些为ssthresh+3)。
3.重新进入拥塞避免阶段。
(2)快恢复
后来的"快速恢复"算法是在上述的"快速重传"算法后添加的,当收到3个重复ACK时,TCP最后进入的不是拥塞避免阶段,而是快速恢复阶段。快速重传和快速恢复算法一般同时使用。快速恢复的思想是"数据包守恒"原则,即同一个时刻在网络中的数据包数量是恒定的,只有当"老"数据包离开了网络后,才能向网络中发送一个"新"的数据包,如果发送方收到一个重复的ACK,那么根据TCP的ACK机制就表明有一个数据包离开了网络,于是cwnd加1。如果能够严格按照该原则那么网络中很少会发生拥塞,事实上拥塞控制的目的也就在修正违反该原则的地方。
具体来说快速恢复的主要步骤是:
1.当收到3个重复ACK时,把ssthresh设置为cwnd的一半,把cwnd设置为ssthresh的值加3(有的实现版本不加3),然后重传丢失的报文段,加3的原因是因为收到3个重复的ACK,表明有3个"老"的数据包离开了网络。
2.再收到重复的ACK时,拥塞窗口增加1。
3.当收到新的数据包的ACK时,把cwnd设置为第一步中的ssthresh的值。原因是因为该ACK确认了新的数据,说明从重复ACK时的数据都已收到,该恢复过程已经结束,可以回到恢复之前的状态了,也即再次进入拥塞避免状态。
快速重传算法首次出现在4.3BSD的Tahoe版本,快速恢复首次出现在4.3BSD的Reno版本,也称之为Reno版的TCP拥塞控制算法。
在流量控制中,发送方发送数据的量由接收方决定,而在拥塞控制中,有发送方自己通过检测网络状况而决定。实际上,慢开始、拥塞避免算法、快重传和快恢复集中算法应该是同时应用在拥塞控制机制之中的,当发送方检测到超时的时候就采用慢开始和拥塞避免,当发送方接收到冗余ACK的时候就采用快重传和快恢复。
(1)为什么四次挥手发送最后一次报文后要等待2MSL (报文最大生存时间)的时间?
答:(1)为了保证A发送的最后一个确认报文段能够到达B。如果A不等待2MSL,若A返回的最后确认报文段丢失,则B不能进入正常关闭状态,而A此时已经关闭,也不可能再重传。
(2)防止出现”已失效的连接请求报文段”。A在发送完最后一个确认报文段后,再经过2MSL可保证本连接持续的时间内所产生的所有报文段从网络中消失。
(2)TCP使用的是GBN(后退N帧协议)还是SR(选择重传协议)?
答:这是为了让人踩坑而出的问题。因为TCP使用累计确认,看起来像是GBN。但是,正确收到但失序的报文并不会被丢弃,而是缓存起来,并且发送冗余ACK指明希望收到的下一个报文段,这是TCP方式和GBN的显著区别。例如,A发送了N个报文段,其中第k(k < N)个报文段丢失,其余N-1个报文段正确地按序到达接收方B。当使用GBN时,A需要重传分组k,以及所有后继分组k+1,k+2,...,N。相反,TCP却最多重传一个报文段,即报文段k。另外,TCP中提供一个SACK(Selective ACK)选项,也就是选择确认选项。当使用选择确认选项的时候,TCP看起来就和SR非常相似。因此,TCP的差错恢复机制可以看成是GBN和SR协议的混合体。
(3)为什么超时时间发生时cwnd被置为1,而收到3个冗余ACK时cwnd只是减半?
答:首先应分析那种情况的网络拥塞程度更严重。其实不难发现,在收到3个冗余ACK的情况下,网络虽然拥塞,但至少ACK报文段能够被正确交付。而当超时发生时,说明网络可能已经拥塞的连ACK报文段都传输不了了,发送方只能等待超时后重传数据。因此,超时时间发生时,网络拥塞更严重,所以发送方应该最大限度地抑制数据发送量,所以cwnd置为1;收到3个冗余ACK时,网络拥塞相对而言不是很严重,所以cwnd减半即可。
(4)为什么不采用"两次握手"建立连接?
答:这主要是为了防止两次握手情况下已失效的连接请求报文段突然有传送到服务端,而产生了错误。考虑以下情况:客户A想服务器B发送TCP连接请求,第一个连接请求报文在网络的某个结点长时间滞留,A超时后认为报文丢失,于是再重传一次连接请求,B收到后建立连接。数据传输完毕后双方断开连接。此时,前一个滞留在网络中的连接请求到达了服务端B,而B认为A有发来连接请求,此时若是使用"三次握手",则B向A返回确认报文段,由于是一个失效的请求,因此A不予理睬,建立连接失败。若采用的是"两次握手",则这种情况下B认为传输连接已经建立,并一直等待A传输数据,而A此时并无连接请求,因此不予理睬,这样就造成了B的资源白白浪费了。
(5)是否TCP和UDP都需要计算往返时间RTT?
答:往返时间RTT只是针对传输层TCP协议才很重要,因为TCP要根据RTT的值来设置超时计时器的超时时间。UDP没有确认和重传机制,因此RTT对UDP没有什么意义。
(6)为什么TCP在建立连接的时候不能每次选择相同的、固定的初始序号?
答:(1)假如A和B频繁地建立连接,传送一些TCP报文段后再释放连接,然后又不断的建立新的连接、传送报文段和释放连接。
(2)假如每一次建立连接时,主机A都选择相同的、固定的初始序号,如1。
(3)若主机A发送出的某些TCP报文段在网络中会滞留较长的时间,以致造成主机A超时重传这些TCP报文段。
(4)若有一些在网络中滞留时间较长的TCP报文段最后终于到达了主机B,但这时传送该报文段的那个连接早已释放了,而在到达主机B时的TCP连接是一条新的TCP连接。
以上这些情况可能会导致在新的TCP连接中的主机B有可能会接收在旧的连接传送的、已经没有意义的、过时的TCP报文段(因为这个TCP报文段的序号有可能正好处于新的连接所使用的序号范围内)。因为必须使得迟到的TCP报文段的序号不在新的连接中使用的序号范围内。所以,TCP在建立新的连接时所选择的初始序号一定要和前面的一些连接所使用过的序号不一样。因此,不同的TCP连接不能使用相同的初始序号。
(7)在使用TCP传输数据时,如果有一个确认报文段丢失了,也不一定会引起与该确认报文段对应的数据的重传。试说明理由。
答:这是因为发送方可能还未重传时,就收到了更高序号的确认。例如主机A连续发送两个报文段,均正确到达主机B。B连续发送两个确认ACK1和ACK2(ACK2的序号比ACK1的序号高)。但前一个确认帧在传输时丢失了。若在超时前,ACK2被A接收,更高的序号代表该序号之前的所有字节都被接收了,所以A知道前一个报文也被正确的接收了,这种情况下A不会重传第一个报文段。