三次握手
为什么不是两次握手?
如果两次握手,当client发送SYN后,SYN包因为某些原因,经过超时的时间之后才到达server,此时server返回ACK包,而client早已进入CLOSE状态,而server以为连接已经建立,等待client传送数据。
导致失败连接白白占用了资源。
第三次握手失败怎么办?
可以看出当失败时服务器并不会重传ack报文,而是直接发送RTS报文段,进入CLOSED状态。这样做的目的是为了防止SYN洪泛攻击。
另一种说法就是Linux下默认会进行5次重发SYN-ACK包,每次时间指数退避(1s,2s,4s…),第五个包没有响应之后TCP才会断开这个连接。
TCP/IP详解上面写的是第一种
建立连接时出现四次握手的情况
当Peer两端同时发起SYN来建立连接时,就出现了四次握手来建立连接(对于有些TCP/IP的实现,可能不支持这种同时打开的情况)
四次挥手
为什么需要TIME_WAIT?
TIMEWAIT状态也称为2MSL等待状态。
MSL是Maximum Segment Lifetime英文的缩写,中文可以译为“报文最大生存时间”,他是任何报文在网络上存在的最长时间,超过这个时间报文将被丢弃。
TTL与MSL是有关系的但不是简单的相等的关系,MSL要大于等于TTL。
1)为实现TCP这种全双工(full-duplex)连接的可靠释放
当TCP的一端发起主动关闭,在发出最后一个ACK包后,即第3次握手完成后发送了第四次握手的ACK包后就进入了TIME_WAIT状态,必须在此状态上停留两倍的MSL时间,等待2MSL时间主要目的是怕最后一个ACK包对方没收到,那么对方在超时后将重发第三次握手的FIN包,主动关闭端接到重发的FIN包后可以再发一个ACK应答包,保证可靠释放。
2)为使旧的数据包在网络因过期而消失
每个具体TCP实现必须选择一个报文段最大生存时间MSL(Maximum Segment Lifetime)。它是任何报文段被丢弃前在网络内的最长时间。在TIME_WAIT状态时两端的端口不能使用,要等到2MSL时间结束才可继续使用。当连接处于2MSL等待阶段时任何迟到的报文段都将被丢弃。
在实际应用中可以通过设置SO_REUSEADDR选项达到不必等待2MSL时间结束再使用此端口。还有IME_WAIT的快速回收和重用,这里不作深入了。
Client和Server同时发起断开连接的FIN包会怎么样呢,TCP状态是怎么转移的?
正常情况:TCP的Peer端在收到对端的FIN包前发出了FIN包,那么该Peer的状态就变成了FIN_WAIT1。
Peer在FIN_WAIT1状态下收到对端Peer对自己FIN包的ACK包的话,那么Peer状态就变成FIN_WAIT2。
Peer在FIN_WAIT2下收到对端Peer的FIN包,在确认已经收到了对端Peer全部的Data数据包后,就响应一个ACK给对端Peer,然后自己进入TIME_WAIT状态。
但是如果Peer在FIN_WAIT1状态下首先收到对端Peer的FIN包的话,那么该Peer在确认已经收到了对端Peer全部的Data数据包后,就响应一个ACK给对端Peer,然后自己进入CLOSEING状态,Peer在CLOSEING状态下收到自己FIN包的ACK包的话,那么就进入TIME WAIT状态。
经过2MSL时间后CLOSED。
关闭TCP一定要四次挥手吗
不一定,4次挥手关闭TCP连接是最安全的做法。但有时是不太希望出现TIME_WAIT 状态(比如当MSL数值设置过大导致服务器端有太多TIME_WAIT状态的TCP连接,减少这些条目数可以更快地关闭连接,为新连接释放更多资源),这时我们可以通过设置SOCKET变量的SO_LINGER标志来避免SOCKET在close()之后进入TIME_WAIT状态,这时将通过发送RST强制终止TCP连接(取代正常的TCP四次握手的终止方式)。但这种方法并不优先选择,使用TIME_WAIT的方法是优先考虑的。
数据传输
确认丢失和确认迟到
确认丢失:发送的包在超时时间内没有收到确认。
确认迟到:发送的包在超时时间外收到确认(因为已经超时,所以此时已经重传),发送端收到确认不做事,如果接收端收到重复的包则丢弃。
ARQ协议
定义:
可靠传输协议常称为自动重传请求ARQ (Automatic Repeat reQuest),通过接收方请求发送方重传出错的数据报文来恢复出错的报文。
-
停等式ARQ
在停等式ARQ中,数据报文发送完成之后,发送方等待接收方的状态报告,如果状态报告报文发送成功,发送后续的数据报文,否则重传该报文。
停等式ARQ,发送窗口和接收窗口大小均为1,发送方每发送一帧之后就必须停下来等待接收方的确认返回,仅当接收方确认正确接收后再继续发送下一帧。该方法所需要的缓冲存储空间最小,缺点是信道效率很低。 -
连续ARQ协议
由于停止等待ARQ协议信道利用率太低,所以需要使用连续ARQ协议来进行改善。这个协议会连续发送一组数据包,然后再等待这些数据包的ACK。一般采用积累确认方式,那就会使用GBN;否则使用选择性重传。-
回退n帧的ARQ(go-back-n/GBN)
发信侧不用等待收信侧的应答,持续的发送多个帧,假如发现已发送的帧中有错误发生,那么从那个发生错误的帧开始及其之后所有的帧全部再重新发送。
特点:复杂度低,但是不必要的帧会再重发,所以大幅度范围内使用的话效率是不高的
例:如果序列号有K bits,那么这个ARQ的协议大小为:2^k-1。(收到哪个就返回下个期望收到的帧序号) -
选择性重传ARQ协议
发信侧不用等待收信侧的应答,持续的发送多个帧,假如发现已发送的帧中有错误发生,那么发信侧将只重新发送那个发生错误的帧。
特点:相对于GBN 复杂度高,但是不需要发送没必要的帧,所以效率高。
例:如果序列号有K bits,那么这个ARQ的协议大小为:2^(k-1)
关于2^(k-1): (假设序号最大为7即0,1,2,3,4,5,6,7,发送窗口大小为5,当发送窗口发送0,1,2,3,4后,假设接收窗口全部收到,则接收窗口向前移动到5次,接受窗口期望接收5,6,7,0,1.若发送窗口并没接收到任何ACK,所以发送窗口重发0,1,2,3,4此时接收窗口会以为重发的0,1是新的分组。所以此时如果发送窗口大小是4,发送4个分组后,全部收到而ACK全部丢失,窗口移动至5-8,如果接收方收到了,认出5-8是新分组,返回8,不会与上一次丢失的分组混淆)
-
SACK选择性确认(SACK是选择性重传ARQ协议的接收方行为)
SACK是TCP选项,它使得接收方能告诉发送方哪些报文段丢失,哪些报文段重传了,哪些报文段已经提前收到等信息。根据这些信息TCP就可以只重传哪些真正丢失的报文段。(其扩展是D-SACK)
需要注意的是只有收到失序的分组时才会可能会发送SACK,TCP的ACK还是建立在累积确认的基础上的。也就是说如果收到的报文段与期望收到的报文段的序号相同就会发送累积的ACK,SACK只是针对失序到达的报文段的。
TCP快速重传
如果收到一个out-of-order的报文段时, TCP需要立刻产生一个ACK,这个ACK不应该被延时,目的在于让对方知道收到一个失序的报文,并告诉对方自己希望收到的报文seq,我们不知道这个重复的ACK的原因,因为还是会等待少量的重复ACK到来,如果连续收到3个或者3个以上的dup ACK,就被判断这个报文被丢失了,于是就需要立即重传丢失的数据段,这个地方不用等待定时器溢出。
两次duplicated ACK时很可能是乱序造成的!三次duplicated ACK 时很可能是丢包造成的!四次duplicated ACK更更更可能是丢包造成的!但是这样的响应策略太慢(折中)。丢包肯定会造成三次duplicated ACK!综上是选择收到三个重复确认时窗口减半效果最好,这是 实践经验 …。
Other
为什么建立连接是三次握手,而关闭连接却是四次挥手呢?
这是因为服务端在LISTEN状态下,收到建立连接请求的SYN报文后,把ACK和SYN放在一个报文里发送给客户端。而关闭连接时,当收到对方的FIN报文时,仅仅表示对方不再发送数据了但是还能接收数据,我们也未必全部数据都发送给对方了,所以我们不可以立即close,也可以发送一些数据给对方后(缓冲区未发送的),再发送FIN报文给对方来表示同意现在关闭连接,因此,我们的ACK和FIN一般都会分开发送。
滑动窗口协议
TCP 连接的每一端都必须设有两个窗口——一个发送窗口和一个接收窗口。TCP 的可靠传输机制用字节的序号进行控制,窗口是随着时间变化可以向前滑动的。TCP 所有的确认都是基于序号而不是基于报文段。TCP的标准窗口是一个16bit位字段,所以窗口最大为2^16-1=65535字节。
发送过的数据未收到确认之前必须保留,以便超时重传时使用。发送窗口没收到确认不动,收到新的确认后前移。
发送缓存用来暂时存放: 发送应用程序传送给发送方 TCP 准备发送的数据;TCP 已发送出但尚未收到确认的数据。
接收缓存用来暂时存放:按序到达的、但尚未被接收应用程序读取的数据; 不按序到达的数据
必须强调三点:
- A 的发送窗口并不总是和 B 的接收窗口一样大(因为有一定的时间滞后)。
- TCP 标准没有规定对不按序到达的数据应如何处理。通常是先临时存放在接收窗口中,等到字节流中所缺少的字节收到后,再按序交付上层的应用进程。
- TCP 要求接收方必须有累积确认的功能,这样可以减小传输开销(累积确认:一个确认包确认了累积到某一序号的所有包,而不是对每个序号都发确认包。)
初始化序列号ISN和确认号
第一次握手时客户端向服务器发送一个同步数据包请求建立连接,该数据包中,初始序列号(ISN)是客户端随机产生的一个值,确认号(ACK的值)是0;
客户端和服务端在建立tcp连接时,双方都会发送SYN报文并初始化序号(英文为:Initial Sequence Number,简称ISN)。序号的范围是0 ~ 231−1之间,而且序号的生成也是随机的,通常是一个很大的数值,因此可以认为每个tcp连接使用的序号也是不一样的。
随机可以避免被攻击者伪造包。接收到包的序号不在当前连接的范围内则丢弃。
如果已经建立了连接,但是客户端突然出现故障了怎么办?
TCP还设有一个保活计时器,显然,客户端如果出现故障,服务器不能一直等下去,白白浪费资源。服务器每收到一次客户端的请求后都会重新复位这个计时器,时间通常是设置为2小时,若两小时还没有收到客户端的任何数据,服务器就会发送一个探测报文段,以后每隔75s发送一次。若一连发送10个探测报文仍然没反应,服务器就认为客户端出了故障,接着就关闭连接。
TCP四种计时器,即重传计时器、坚持计时器、保活计时器和时间等待计时器
- 重传计时器(对于超时重传机制)
当TCP发送报文段时,就创建该特定报文段的重传计时器 。可能发生两种情况:
(1)、若在计时器截止时间到( 通常是60秒 )之前收到了对此特定报文段的确认,则撤销此计时器。
(2)、若在收到了对此特定报文段的确认之前计时器截止期到,则重传此报文段,并将计时器复位。 - 坚持计时器(对于流量控制)
为了对付零窗口大小通知,TCP需要另一个计时器。假定接收TCP宣布了窗口大小为零。发送TCP就停止传送报文段,直到接收TCP发送确认并宣布一个非零的窗口大小。但这个确认可能会丢失。我们知道在TCP中,对确认是不需要发送确认的。若确认丢失了,接收TCP并不知道,而是会认为它已经完成任务了,并等待着发送TCP接着会发送更多的报文段。但发送TCP由于没有收到确认,就等待对方发送确认来通知窗口的大小。双方的TCP都在永远地等待着对方。
要打开这种死锁,TCP为每一个连接使用一个坚持计时器。 当发送TCP收到一个窗口大小为零的确认时,就启动坚持计时器 。 当坚持计时器期限到时,发送TCP就发送一个特殊的报文段, 叫做 探测报文段 。这个报文段只有一个字节的数据。它有一个序号,但它的序号永远不需要确认;甚至在计算对其他部分的数据的确认时该序号也被忽略。探测报文段提醒对端:确认已丢失,必须重传。
坚持计时器的值设置为重传时间的数值。但是,若没有收到从接收端来的响应,则需发送另一个探测报文段,并将坚持计时器的值加倍和复位。发送端继续发送探测报文段,将坚持计时器设定的值加倍和复位,直到这个值增大到门限值( 通常是60秒 )为止。在这以后,发送端每隔60秒就发送一个探测报文段,直到窗口重新打开。 - 保活计时器 (心跳)
保活计时器使用在某些实现中,用来防止在两个TCP之间的连接出现长时期的空闲。假定客户打开了到服务器的连接,传送了一些数据,然后就保持静默了。也许这个客户出故障了。在这种情况下,这个连接将永远地处理打开状态。
要解决这种问题,在大多数的实现中都是使服务器设置保活计时器。每当服务器收到客户的信息,就将计时器复位。保活计时器 通常设置为2小时 。若服务器过了2小时还没有收到客户的信息,它就发送探测报文段。若发送了10个探测报文段(每一个相隔75秒)还没有响应,就假定客户出了故障,因而就终止该连接。 - 时间等待计时器 (TIME_WAIT状态)
时间等待计时器是在连接终止期间使用的 。当TCP关闭一个连接时,它并不认为这个连接马上就真正地关闭了。在时间等待期间中,连接还处于一种中间过渡状态。这就可以使重复的FIN报文段(如果有的话)可以到达目的站因而可将其丢弃。这个计时器的值 通常设置为一个报文段的寿命期待值的两倍(2MSL) 。