1.面向连接的运输:TCP
1.1 TCP连接
TCP是面向连接的,因为在一个应用程序开始向另一个应用程序发送数据前,两个进程必须相互握手,即它们必须相互发送某些预备报文段,以建立确保数据传输的参数。由于TCP协议只在端系统中运行,中间的网络元素(路由器和链路交换机)不会维持TCP连接状态,即路由器对TCP连接视而不见,看到的只是数据报,不是连接。
TCP连接提供的是全双工服务,并且该连接也总是点对点的,即在单个发送方与单个接收方之间的连接。
如下图所示:
客户进程提供套接字(进程之门)传递数据,数据一旦提供该门,就由客户中运行的TCP控制了。TCP将这些数据引导到该连接的发送缓存里,之后TCP就不时的从发送缓存中取出数据。TCP从缓存中取出并放入报文段的数据量受制于最大报文段长度(MSS),注意MSS是指报文段中应用层数据的最大长度,不包括TCP首部。而MSS则根据由本地发送主机发送的**最大链路层帧长度(最大传输单元MTU)**来设置。以太网和PPP链路层协议具有1500字节的MTU,而MSS的典型值为1460字节。
1.2 TCP报文段结构
TCP报文段由首部字段和一个数据字段组成。TCP发送一个大文件时,通常将该文件划分为长度为MSS的若干块(最后一块通常小于MSS)。其中,首部中最重要的是序号字段和确认号字段。结构如下图所示:
一个报文段的序号是该报文段首字节的字节流编号,假设数据流由包含500000字节的文件组成,MSS为1000字节,则TCP会将该数据流构建500个报文段,给第一个报文段分配序号0,第二个分配1000,以此类推,如图所示:
由于TCP是全双工的,所以主机A向主机B发送数据时,也许会接收B的数据。主机A填充进报文段的确认号是主机A希望从B收到的下一个字节的序号。
假设一个场景:主机A和服务器B进行会话,A输入的字符被发送到B,B将回送字符的副本给客户,假设A和B的起始序号是42和79,建立连接但没有发送数据前,A期待字节79,B期待字节42。过程如下图所示:
1.3 可靠数据传输
TCP的可靠数据传输服务确保一个进程从其接收缓存中读出的数据流是无损坏,无间隔,非冗余和按序的数据流。
1.3.1 超时间隔加倍
在拥塞的时候,如果源持续重传分组,会使拥堵更加严重。TCP可以采用加倍间隔的方式,即每个发送的重传都是经过越来越长的时间间隔后进行。
1.3.2 快速重传
超时触发重传存在的问题之一是超时周期可能相对较长。不过,发送方通常可在超时事件发生之前通过注意所谓冗余ACK来较好地检测到丢包的情况。当有报文段丢失时,由于TCP不发送否定确认,所以接收方只是对已接受到的最后一个按序字节数据进行重复确认,即产生冗余ACK。
如果一个报文段丢失,就会产生许多的冗余ACK,一旦接收到3个冗余ACK,TCP就执行快速重传,即在该报文段的定时器过期之前重传丢失的报文段。
1.4 流量控制
当TCP连接收到正确,按序的字节后,它就将数据放入接收缓存。当接收方在忙于别的任务时,在缓存中的数据可能会很久后才被读取,如果发送方发送的太多,太快,数据就很容易使接收缓存溢出。
TCP提供了流量控制服务以消除缓存溢出的可能性。流量控制和拥塞控制都是对发送方的遏制,但是它们针对不同的原因而采取的措施。
TCP通过让发送方维护一个接收窗口的变量来提供流量控制,即接收窗口给发送方提示接收方还有多少缓存空间。因为TCP是全双工通信,连接的两端均有一个接收窗口,大小设为rwnd。接收缓存大小设为RcvBuffer。如图所示:
如果A向B发送报文,此时B的缓存已满,且B没有数据要发送给A,A接收到的rwnd的值就为0。但是B将缓存清空后,TCP并不会向A发送带有新的rwnd的值,A也就不知道B有缓存空间了,即A被拥塞。为了解决这个问题,TCP要求B的接收窗口为0时,A继续发送只有一个字节数据的报文段,这个报文段会被B接收。当B缓存开始清空时,会在确认报文中包含一个非0的rwnd值。
1.5 *TCP连接管理
1.5.1 三次握手
下图是TCP建立连接的过程,即三次握手:
- 客户端的TCP首先向服务器的TCP发起一个特殊的TCP报文段,该段不包含应用层数据,但是在该段的首部中的标志位SYN被置为1(这个报文段也被称为SYN报文段)。此外,客户会随机选择一个初始序号client_isn放置在该SYN报文段的序号字段中。然后该报文段会封装在IP数据报中并发送给服务器。
- 当IP数据报达到服务器后,服务器从中提取出SYN报文段,为该TCP连接分配TCP缓存和变量(后面会提到在三次握手的第三步前分配这些缓存和变量,会使TCP易受到SYN洪泛攻击),并向客户TCP发送发送允许连接的报文段(同样不包含应用层数据)。但是在该报文首部有3个重要信息:首先,SYN被置为1。其次,首部的确认号字段被置为client_isn+1。最后,服务器将自己的初始序号server_isn放置在首部的序号字段中。该报文段的含义就是:“我收到了你发送的SYN分组,该分组有client_isn。我同意连接,且我自己的初始序号是server_isn”。该报文段被称为SYNACK报文段。
- 收到该SYNACK报文段后,客户也要为该连接分配缓存和变量,且向服务器发送另一个报文段,该报文段将值server_isn+1放置在TCP报文段首部的确认字段中(即对服务器的允许连接报文段进行确认)。因为连接已经建立,该SYN被置为0。在第三个阶段中是可以在报文段中携带客户到服务器的数据的。
完成着三个步骤,客户和服务器就可以互相发送数据了。且在以后发送的每个报文段中,SYN都会被置为0。
为什么是三次握手不是两次?
**第三次握手时为了防止已失效的连接请求报文段有传送到B,因而产生错误。**现假定出现一种异常情况,即A发出的第一个请求连接报文段并没有丢失,而是在某些网络结点长时间滞留了,以至到连接释放以后的某个时间才到达B。本来这是一个已失效的报文段。但B收到此失效的连接请求报文段后,就误认为是A又发出一次新的连接请求。于是就向A发出确认报文段,同意建立连接。假定不采用三次握手,那么只要B发出确认,新的连接就建立了。由于现在A并没有发出建立请求的连接,因此不会理睬B的确认,也不会向B发送数据,但B却以为新的运输连接已经建立了,并一直等待A发来的数据。B的许多资源就这样白白浪费了。
摘自https://zhuanlan.zhihu.com/p/51448333
1.5.2 四次挥手
下图是TCP断开连接的过程,即四次挥手:
假设客户端发起中断连接请求,也就是发送FIN报文。服务器端接到FIN报文后,意思是说"我客户端没有数据要发给你了",但是如果你还有数据没有发送完成,则不必急着关闭Socket,可以继续发送数据。所以服务器端先发送ACK,告诉客户端:“你的请求我收到了,但是我还没准备好,请继续你等我的消息"。这个时候客户端就进入FIN_WAIT状态,继续等待服务器端的FIN报文。当服务器端确定数据已发送完成,则向客户端发送FIN报文,告诉客户端:我这边数据发完了,准备好关闭连接了"。客户端收到FIN报文后,就知道可以关闭连接了,但是它还是不相信网络,怕服务器端不知道要关闭,所以发送ACK后进入TIME_WAIT状态,如果服务器端没有收到ACK则可以重传最后的确认报文。服务器端收到ACK后,就知道可以断开连接了。客户端等待了2MSL后依然没有收到回复,则证明服务器端已正常关闭,客户端也可以关闭连接了。
为什么连接的时候是三次握手,关闭的时候却是四次握手?
因为当服务器端收到客户端的SYN连接请求报文后,可以直接发送SYN+ACK报文。其中ACK报文是用来应答的,SYN报文是用来同步的。但是关闭连接时,当服务器端收到FIN报文时,很可能并不会立即关闭SOCKET,所以只能先回复一个ACK报文,告诉客户端,“你发的FIN报文我收到了”。只有等到服务器端所有的报文都发送完了,才能发送FIN报文,因此不能一起发送。故需要四步握手。
摘自https://blog.csdn.net/whuslei/article/details/6667471
2.TCP拥塞控制
TCP拥塞控制的方法是让每一个发送方根据感知到的网络拥塞程度来限制其能向连接发送流量的速率。有3个问题需要解决:
- TCP如何限制它向连接发送流量的速率?
- TCP发送方如何感知路上有拥塞?
- 发送方感到拥塞时,采用什么算法改变发送速率?
首先分析第一个问题。前面知道TCP两端都有一个接收缓存,一个发送缓存和几个变量(rwnd等)。其中还有一个变量:拥塞窗口(cwnd)。一个发送方中未确认的数据量不会超过cwnd和rwnd中的最小值,这样就间接对发送流量的速率进行了限制。下面都是假设rwnd无限大,只考虑cwnd。
接下来考虑如何感知到拥塞。当出现过度拥塞时,路径上的路由器缓存会溢出,引起数据报被丢弃。丢弃的数据包接着引起发送方的丢包(要么超时,要么收到3个冗余ACK),发送方就认为路径上有拥塞。
最后就是考虑TCP拥塞控制算法。算法主要包括3个部分:慢启动,拥塞避免和快速回复。慢启动和拥塞避免是TCP的强制部分,二者的差异在于接收到ACK时增加cwnd长度的方式。快速恢复对于TCP发送方不是必须的。
- 慢启动:当一条TCP连接开始时,cwnd的值通常为MSS的较小值。但可用带宽比MSS/RTT大得多,于是希望找到一个合适的带宽数量。如图所示:
在图中,每当有一个报文段被确认时,发送方对每个确认报文段将拥塞窗口增加一个MSS(注意是针对每一个报文段,不是这一批报文段)。这样每过一个RTT,发送速率就翻倍。因此,TCP发送速率起始慢,在慢启动阶段以指数增长。但是要什么时候结束?
第一种方式:如果存在由于超时而丢包时,TCP发送方将cwnd设置为1并重新开始慢启动。还将ssthresh(慢启动阈值的速记)设置为cwnd/2,即检测到拥塞就将值设为窗口值的一半。
第二种方式:当cwnd等于ssthresh的值时,结束慢启动并TCP转移到拥塞避免模式。
第三种方式:如果检测到3个冗余ACK,则TCP执行快速重传并进入快速恢复状态。
-
拥塞避免:一旦进入到拥塞避免状态,cwnd的值大约是上次遇到拥塞时的一半。因此,TCP无法每过一个RTT就将cwnd翻倍,而是采取每个RTT只将cwnd的值增加一个MSS。
-
快速恢复:对于引起TCP进入快速恢复状态的缺失报文段,对收到的每个冗余的ACK, cwnd的值增加一个MSS。最终,当对丢失报文段的一个ACK到达时,TCP在降低cwnd后进入拥塞避免状态。简而言之就是先对cwnd进行增加,增加的数值为冗余ACK的数量,然后进入拥塞避免状态。
下图是Reno版TCP和Tahoe版TCP的拥塞控制窗口演化情况:
前面两者都是慢启动,第4轮的时候到达阈值8,然后窗口值线性爬升,在第8轮出现3个冗余ACK。此时,Tahoe版TCP的拥塞窗口值回到1,然后指数增长,且阈值被设为12/2=6。而Reno版TCP拥塞窗口设为6+3=9,然后线性增长。