tcp 知识点
- TCP头部结构和字段介绍
- TCP三次握手
- TCP四次挥手
- TCP超时重传
- TCP流量控制
- 滑动窗口
- TCP拥塞控制
- 慢启动、拥塞避免、快重传、快恢复
- TCP的四种定时器
- TCP粘包/拆包问题
TCP头部结构和字段介绍
其中比较重要的字段有:
序号(sequence number):Seq序号,占32位,用来标识从TCP源端向目的端发送的字节流,发起方发送数据时对此进行标记。
确认号(acknowledgement number):Ack序号,占32位,只有ACK标志位为1时,确认序号字段才有效,Ack=Seq+1。
标志位(Flags):共6个,即URG、ACK、PSH、RST、SYN、FIN等。具体含义如下:
- URG:紧急指针(urgent pointer)有效。
- ACK:确认序号有效。
- PSH:接收方应该尽快将这个报文交给应用层。
- RST:重置连接。
- SYN:发起一个新连接。
- FIN:释放一个连接。
需要注意的是:
- 不要将确认序号Ack与标志位中的ACK搞混了。确认方Ack=发起方Seq+1,两端配对。
TCP三次握手
详解
所谓的三次握手即TCP连接的建立。这个连接必须是一方主动打开,另一方被动打开的。以下为客户端主动发起连接的图解:
握手之前主动打开连接的客户端结束CLOSED阶段,被动打开的服务器端也结束CLOSED阶段,并进入LISTEN阶段。随后开始“三次握手”:
(1)首先客户端向服务器端发送一段TCP报文,其中:
- 标记位为SYN,表示“请求建立新连接”;
- 序号为Seq=X(X一般为1);
- 随后客户端进入SYN-SENT阶段。
(2)服务器端接收到来自客户端的TCP报文之后,结束LISTEN阶段。并返回一段TCP报文,其中:
- 标志位为SYN和ACK,表示“确认客户端的报文Seq序号有效,服务器能正常接收客户端发送的数据,并同意创建新连接”(即告诉客户端,服务器收到了你的数据);
- 序号为Seq=y;
- 确认号为Ack=x+1,表示收到客户端的序号Seq并将其值加1作为自己确认号Ack的值;随后服务器端进入SYN-RCVD阶段。
(3)客户端接收到来自服务器端的确认收到数据的TCP报文之后,明确了从客户端到服务器的数据传输是正常的,结束SYN-SENT阶段。并返回最后一段TCP报文。其中:
- 标志位为ACK,表示“确认收到服务器端同意连接的信号”(即告诉服务器,我知道你收到我发的数据了);
- 序号为Seq=x+1,表示收到服务器端的确认号Ack,并将其值作为自己的序号值;
- 确认号为Ack=y+1,表示收到服务器端序号Seq,并将其值加1作为自己的确认号Ack的值;
- 随后客户端进入ESTABLISHED阶段。
服务器收到来自客户端的“确认收到服务器数据”的TCP报文之后,明确了从服务器到客户端的数据传输是正常的。结束SYN-SENT阶段,进入ESTABLISHED阶段。
在客户端与服务器端传输的TCP报文中,双方的确认号Ack和序号Seq的值,都是在彼此Ack和Seq值的基础上进行计算的,这样做保证了TCP报文传输的连贯性。一旦出现某一方发出的TCP报文丢失,便无法继续”握手”,以此确保了”三次握手”的顺利完成。
此后客户端和服务器端进行正常的数据传输。这就是“三次握手”的过程。
为什么需要三次握手?
首先我们要知道信道是不可靠的,但是我们要建立可靠的连接发送可靠的数据,也就是数据传输是需要可靠的。在这个时候三次握手是一个理论上的最小值,并不是说是tcp协议要求的,而是为了满足在不可靠的信道上传输可靠的数据所要求的。
在《计算机网络》一书中其中有提到,三次握手的目的是“为了防止已经失效的连接请求报文段突然又传到服务端,因而产生错误”。
这种情况是:一端(client)A发出去的第一个连接请求报文并没有丢失,而是因为某些未知的原因在某个网络节点上发生滞留,导致延迟到连接释放以后的某个时间才到达另一端(server)B。本来这是一个早已失效的报文段,但是B收到此失效的报文之后,会误认为是A再次发出的一个新的连接请求,于是B端就向A又发出确认报文,表示同意建立连接。如果不采用“三次握手”,那么只要B端发出确认报文就会认为新的连接已经建立了,但是A端并没有发出建立连接的请求,因此不会去向B端发送数据,B端没有收到数据就会一直等待,这样B端就会白白浪费掉很多资源。如果采用“三次握手”的话就不会出现这种情况,B端收到一个过时失效的报文段之后,向A端发出确认,此时A并没有要求建立连接,所以就不会向B端发送确认,这个时候B端也能够知道连接没有建立。
TCP四次挥手
详解
- TCP连接的释放(解除)。连接的释放必须是一方主动释放,另一方被动释放。以下为客户端主动发起释放连接的图解:
挥手之前主动释放连接的客户端结束ESTABLISHED阶段。随后开始“四次挥手”:
(1)首先客户端想要释放连接,向服务器端发送一段TCP报文,其中:
- 标记位为FIN,表示“请求释放连接“;
- 序号为Seq=U;
- 随后客户端进入FIN-WAIT-1阶段,即半关闭阶段。并且停止在客户端到服务器端方向上发送数据,但是客户端仍然能接收从服务器端传输过来的数据。
- 注意:这里不发送的是正常连接时传输的数据(非确认报文),而不是一切数据,所以客户端仍然能发送ACK确认报文。
(2)服务器端接收到从客户端发出的TCP报文之后,确认了客户端想要释放连接,随后服务器端结束ESTABLISHED阶段,进入CLOSE-WAIT阶段(半关闭状态)并返回一段TCP报文,其中:
- 标记位为ACK,表示“接收到客户端发送的释放连接的请求”;
- 序号为Seq=V;
- 确认号为Ack=U+1,表示是在收到客户端报文的基础上,将其序号Seq值加1作为本段报文确认号Ack的值;
- 随后服务器端开始准备释放服务器端到客户端方向上的连接。
- 客户端收到从服务器端发出的TCP报文之后,确认了服务器收到了客户端发出的释放连接请求,随后客户端结束FIN-WAIT-1阶段,进入FIN-WAIT-2阶段
- 前”两次挥手”既让服务器端知道了客户端想要释放连接,也让客户端知道了服务器端了解了自己想要释放连接的请求。于是,可以确认关闭客户端到服务器端方向上的连接了
(3)服务器端自从发出ACK确认报文之后,经过CLOSED-WAIT阶段,做好了释放服务器端到客户端方向上的连接准备,再次向客户端发出一段TCP报文,其中:
- 标记位为FIN,ACK,表示“已经准备好释放连接了”。注意:这里的ACK并不是确认收到服务器端报文的确认报文。
- 序号为Seq=W;
- 确认号为Ack=U+1;表示是在收到客户端报文的基础上,将其序号Seq值加1作为本段报文确认号Ack的值。
- 随后服务器端结束CLOSE-WAIT阶段,进入LAST-ACK阶段。并且停止在服务器端到客户端的方向上发送数据,但是服务器端仍然能够接收从客户端传输过来的数据。
(4)客户端收到从服务器端发出的TCP报文,确认了服务器端已做好释放连接的准备,结束FIN-WAIT-2阶段,进入TIME-WAIT阶段,并向服务器端发送一段报文,其中:
- 标记位为ACK,表示“接收到服务器准备好释放连接的信号”。
- 序号为Seq=U+1;表示是在收到了服务器端报文的基础上,将其确认号Ack值作为本段报文序号的值。
- 确认号为Ack=W+1;表示是在收到了服务器端报文的基础上,将其序号Seq值作为本段报文确认号的值。
- 随后客户端开始在TIME-WAIT阶段等待2MSL
为什么要客户端要等待2MSL呢?见后文。
服务器端收到从客户端发出的TCP报文之后结束LAST-ACK阶段,进入CLOSED阶段。由此正式确认关闭服务器端到客户端方向上的连接。
客户端等待完2MSL之后,结束TIME-WAIT阶段,进入CLOSED阶段,由此完成“四次挥手”。
后“两次挥手”既让客户端知道了服务器端准备好释放连接了,也让服务器端知道了客户端了解了自己准备好释放连接了。于是,可以确认关闭服务器端到客户端方向上的连接了,由此完成“四次挥手”。
与“三次挥手”一样,在客户端与服务器端传输的TCP报文中,双方的确认号Ack和序号Seq的值,都是在彼此Ack和Seq值的基础上进行计算的,这样做保证了TCP报文传输的连贯性,一旦出现某一方发出的TCP报文丢失,便无法继续”挥手”,以此确保了”四次挥手”的顺利完成。
4.1 为什么需要四次握手?
- 为了确保数据能够完成传输。
- 关闭连接时,当收到对方的FIN报文通知时,它仅仅表示对方没有数据发送给你了;但未必你所有的数据都全部发送给对方了,所以你可以未必会马上会关闭SOCKET,也即你可能还需要发送一些数据给对方之后,再发送FIN报文给对方来表示你同意现在可以关闭连接了,所以它这里的ACK报文和FIN报文多数情况下都是分开发送的。
释放连接时为什么TIME-WAIT状态必须等待2MSL时间?
- 首先说下什么是MSL:
- MSL是Maximum Segment Lifetime英文的缩写,中文可以译为“报文最大生存时间”,他是任何报文在网络上存在的最长时间,超过这个时间报文将被丢弃。
- 第一,为了保证A发送的最后一个ACK报文能够到达B。这个ACK报文段有可能丢失,因而使处在LAST-ACK状态的B收不到对已发送的FIN+ACK报文段的确认。B会超时重传这个FIN+ACK报文段,而A就能在2MSL时间内收到这个重传的FIN+ACK报文段,重置时间等待计时器(2MSL)。如果A在TIME-WAIT状态不等待一段时间,而是在发送完ACK报文段后就立即释放连接,就无法收到B重传的FIN+ACK报文段,因而也不会再发送一次确认报文段。这样,B就无法按照正常的步骤进入CLOSED状态。
- 第二,A在发送完ACK报文段后,再经过2MSL时间,就可以使本连接持续的时间所产生的所有报文段都从网络中消失。这样就可以使下一个新的连接中不会出现这种旧的连接请求的报文段。
客户端突然挂掉了怎么办?
- 正常连接时,客户端突然挂掉了,如果没有措施处理这种情况,那么就会出现客户端和服务器端出现长时期的空闲。解决办法是在服务器端设置保活计时器,每当服务器收到客户端的消息,就将计时器复位。超时时间通常设置为2小时。若服务器超过2小时没收到客户的信息,他就发送探测报文段。若发送了10个探测报文段,每一个相隔75秒,还没有响应就认为客户端出了故障,因而终止该连接。
TCP超时重传
原理是在发送某一个数据以后就开启一个计时器,在一定时间内如果没有得到发送的数据报的ACK报文,那么就重新发送数据,直到发送成功为止。
影响超时重传机制协议效率的一个关键参数是
重传超时时间(RTO,Retransmission TimeOut)
。RTO的值被设置过大过小都会对协议造成不利影响。- RTO设长了,重发就慢,没有效率,性能差。
- RTO设短了,重发的就快,会增加网络拥塞,导致更多的超时,更多的超时导致更多的重发。
连接往返时间(RTT,Round Trip Time),指发送端从发送TCP包开始到接收它的立即响应所消耗的时间。
RTO理论上最好是网络 RTT 时间,但又受制于网络距离与瞬态时延变化,所以实际上使用自适应的动态算法(例如 Jacobson 算法和 Karn 算法等)来确定超时时间。
TCP流量控制
一条TCP连接每一侧主机都为该连接设置了接收缓存。当该TCP连接收到了正确的、按序的字节后,他就将数据放入接收缓存。相关联的应用进程会从该缓存中读取数据。但不必是数据一到达就立即读取。事实上,接收方也许正忙于其他任务,甚至要过很长时间后才读取该数据。
如果某个应用进程读取比较缓慢,但是发送方发送的太多、太快,发送的数据就会很容易地使该连接的接收缓存溢出。
TCP为它的应用程序提供了流量控制服务(flow-control service)以消除发送方使接收方缓存溢出的可能性。流量控制因此是一个速度匹配服务,即发送方的发送速率与接收方应用程序的读取速率相匹配。
流量控制的实现
- TCP通过让发送方维护一个称为接收窗口(receive window)的变量(TCP报文段首部的接收窗口字段)来提供流量控制。通俗的讲,接收窗口用于给发送方一个指示--该接收方还有多少可用的缓存空间。因为TCP是全双工通信,在连接两端的发送方都各自维护了一个接收窗口。
如上图所示
RcvBuffer
是接收缓存的总大小,buffered data
是当前已经缓存了的数据,而free buffer space
是当前剩余的缓存空间大小,rwnd
的值就是free buffer space
。主机B通过把当前的rwnd
值放入到它发送给主机A的报文段首部的接收窗口字段中,通知主机A它在该连接的缓存中还有多少可用空间。而主机A则将自己发往主机B的序号空间中未确认的数据量控制在
rwnd
值的范围内,这样就可以避免主机A使主机B的接收缓存溢出。
拥塞控制
为什么需要拥塞控制?
- 我们在前面讲到过,在TCP协议中,分组丢失一般是当网络变得拥塞时由
路由器缓存溢出
引起的。因此分组重传是作为网络拥塞的征兆
来对待,但是却无法处理导致网络拥塞的原因,因为有太多的源想以过高的速率发送数据。一旦网络发生拥塞,分组所经历的时延会变大,分组丢失的可能性会变大,发送端需要重传的分组会变多,这只会导致网络越来越拥塞,形成恶性循环。因此,为了处理网络拥塞,需要一些机制以在面临网络拥塞时遏制发送方。
拥塞控制方法
- 在实践中采用了两种主要的拥塞控制方法,这两种方法是根据网络层是否为传输层拥塞控制提供了显示帮助来区分。它们是端到端拥塞控制和网路辅助的拥塞控制。
- 端到端拥塞控制
- 在端到端的拥塞控制中,网路层没有为运输层提供显示支持。即使网络中存在拥塞,端系统也必须通过对网络行为的观察(如分组的丢失与时延)来推理判断之。
- 网络辅助的拥塞控制
- 在网络辅助的拥塞控制中,网络层构件(即路由器)向发送方提供关于网络中拥塞状态的显示反馈信息。拥塞信息从网络反馈到发送发通常有两种方式:直接反馈信息可以由网络路由器发给发送方,这种方式的通知通常采用一种阻塞分组的形式;第二种方法是路由器标记或更新从发送方流向接收方的分组中的某个字段来指示拥塞的产生,一旦收到一个标记的分组后,接收方就会向发送方发送该网络拥塞指示。
TCP拥塞控制
TCP是通过
端到端的方法
来解决拥塞控制的,因为IP层不会向端系统提供有关网络拥塞的反馈信息。TCP报文段的丢失被认为是网络拥塞的一个迹象
,TCP会相应的减小其窗口长度。这里我们需要明确的几个问题是:- TCP发送方如何限制它向其连接发送流量的
速率
? - TCP发送方如
何感知从它到目的地之间的路径上存在拥塞呢
? - 当感知到了拥塞时,采用
何种算法
来改变其发送速率呢?
- TCP发送方如何限制它向其连接发送流量的
首先我们来考虑TCP发送方怎样
限制它向网络发送流量的速率
。与流量控制一样,在发送方的TCP拥塞控制机制中跟踪了一个额外的变量,即拥塞窗口(congestion window)
。拥塞窗口表示为cwnd,通过这个拥塞窗口,我们就能够对发送方向其连接发送数据的速率进行限制。具体的措施是:让一个发送方中未确认的数据量不会超过cwnd和rwnd的最小值
,即:
1 | LastByteSend - LastByteAcked <= min{cwnd,rwnd} |
LastByteSend, LastByteAcked 分别是最后一个发送的字节的序号和最后一个被确认的字节的序号。
如前所述TCP发送方通过
捕获到丢包时间的产生
感知从它到目的地之间的路径上存在拥塞。在拥塞情况发生时,我们可以通过
减小cwnd的值
来减小发送方发送数据的速率。那么如果没有拥塞发生呢?如果没有拥塞,我们应该增加cwnd的值来增大发送方发送数据的速率。发送方发送速率过大会导致网络拥塞,甚至拥塞崩溃;而如果发送方过于谨慎,发送太慢则不能充分利用带宽。因此根据网络情况合理设置cwnd的值非常重要。在概述了TCP拥塞控制之后,现在我们来考虑一下广受赞誉的TCP拥塞控制算法。该算法包括三部分:
慢启动,拥塞避免,快速重传,快速恢复
。
慢启动
- 参考链接
- 当一条TCP连接开始,cwnd值通常初始置为一个MSS较小值。这使得初始发送速率为MSS/RTT。在慢启动(slow-start)状态,cwnd的值以1个MSS开始并且每当传输的报文段首次被确认就增加一个MSS。
- 何时结束慢启动阶段的指数增长呢?
- 如果存在一个由超时指示的丢包事件,TCP发送方将
cwnd设置为1
并重新开始慢启动过程。它还将第二个状态变量的值ssthresh(慢启动阈值)设置为cwnd/2
,即当检测到拥塞时将ssthresh置为拥塞窗口值的一半。 - 当检测到拥塞时ssthresh设为cwnd的一半,当到达或超过ssthresh的值时,结束慢启动并且TCP转移到
拥塞避免模式
。 - 如果检测到3个冗余ACK,这时TCP执行一种快速重传并进入快速恢复状态。
- 如果存在一个由超时指示的丢包事件,TCP发送方将
拥塞避免
每个RTT只将cwnd的值增加一个MSS
:对于TCP发送方无论何时到达一个新的确认,就将cwnd增加一个MSS(MSS/cwnd)字节。例如,如果MSS是1460字节,并且cwnd是14600字节,则在一个RTT内发送10个报文段。每个到达ACK增加1/10MSS的拥塞窗口长度,因此在收到对所有10个报文段的确认后,拥塞窗口的值将增加了一个MSS。当出现超时时,TCP的拥塞避免与慢启动阶段一样。当出现丢包时,网络继续从发送方向接收方交付报文段,当接收到3个冗余ACK时,将ssthresh的值置为cwnd的一半,同时将cwnd的值减半加上3个MSS。
快速恢复
- 对收到的每个用冗余ACK,cwnd值增加一个MSS。
- 当对丢失报文段的一个ACK到达时,TCP在降低cwnd进入拥塞避免状态。
- 如果出现超时事件,执行如同慢启动和拥塞避免中相同的动作后,迁移到慢启动状态.