TCP协议
传输层的协议主要有两个:TCP和UDP协议。TCP相对UDP协议的特点是:面向连接、字节流和可靠传输。使用TCP协议通信的双方必须先建立连接,然后才能开始数据的读写。TCP连接是全双工的,即双方的数据读写可以通过一个连接进行。完成数据交换后,通信双方都必须断开连接以释放系统资源。
TCP传输是可靠的。首先,TCP协议采用发送应答机制,即发送端发送的每个TCP报文段都必须得到接收方的应答,才认为这个TCP报文段传输成功,其次,TCP协议采用超时重传机制,发送端在发送一个TCP报文段之后启动定时器,如果在定时时间内未收到应答,它将重发该报文段。最后,因为TCP报文段最终是以IP数据报发送的,而IP数据报到达接收端可能乱序、重复,所以TCP协议还会对接收到的TCP报文段重排、整理,再交付给应用层。
1.网络模型
1.七层网络模型
这个七层网络模型在数据的传输过程中还会对数据进行封装,封装的过程如下:
当需要传送数据时,数据首先通过应用层的接口进入应用层。然后每经过一层,需要加上每一层数据的报头,然后被传递到下一层。当一帧数据通过物理层传送到目标主机的物理层时,该主机的物理层依次对数据进行解析,最终原始数据被递交到目标主机的具体应用程序中。
2.五层网络模型
五层网络模型主要有应用层、运输层、网络层、数据链路层和物理层。
3. 四层网络模型
四层网络模型使用最广泛。
网络接口层
网络接口层包括用于协作IP数据在已有网络介质上传输的协议。定义了像ARP(地址解析协议),提供TCP/IP协议的数据结构和实际物理硬件的接口
网络层
本层包含IP协议、RIP(路由信息协议)协议,负责数据的包装、寻址和路由,同时还包括ICMP协议(网间控制报文协议)用来提供网络诊断信息。
传输层
TCP协议提供可靠的数据流运输服务,UDP协议提供不可靠的用户数据报服务。
应用层
应用层包括FTP、Gopher、HTTP、Telent、SMTP、NNTP等。
2.TCP头部
TCP的头部格式如下图:
-
16位端口号: 告诉主机该报文段是来自哪里(源端口)以及传给哪个上层协议或应用程序(目的端口)的。进行TCP通信时,客户端通常使用系统自动选择的临时端口号,而服务器则使用知名服务端口号。所有知名服务使用的端口号都定义在/etc/services文件中。
-
32位序号:一次TCP通信(从TCP建立到断开)过程中某一个传输方向上的字节流的每个字节的编号。
-
32位确认号: 用作对另一方发来的TCP报文段的响应。其值是收到的TCP报文段的序号值加1。
-
4位头部长度: 标识该TCP头部有多少个32bit字(4byte),因为4位最大能表示15,所以TCP头部最长是60字节。
-
6位标志:
URG:表示紧急指针是否有效
ACK:表示确认号是否有效
PSH:提示接收端应用程序应该立即从TCP接收缓冲区中读走数据,为接收后续数据腾出空间
RST:表示要求对方重新建立连接
SYN:表示请求建立一个连接
FIN:表示通知对方本段要关闭连接了,我们称携带FIN标志的TCP报文段为结束报文段。 -
16位窗口大小: 是TCP流量控制的一个手段。它告诉对方本端的TCP接收缓冲区还能容纳多少字节的数据,这样对方就可以控制发送数据的速度。
-
16位校验和:由发送端填充,接收端对TCP报文段执行CRC算法以检验TCP报文段在传输过程中是否损坏。
-
16位紧急指针:是一个正的偏移量,它和序号字段的值相加表示最后一个紧急数据的下一个字节的序号。
3.TCP状态流转
著名的3次握手和4次挥手
TCP建立连接可以简单地称为3次握手,而连接的中止则可以称为4次握手:
建立连接
- 第一次握手:建立连接时,客户端发送SYN包(SYN=J)到服务器,并进入SYN_SEND状态,等到服务器确认。
- 第二次握手:服务器收到SYN包,必须确认客户的SYN(ACK=J+1)到服务器,同时自己也发送一个SYN包(SYN=K),即SYN+ACK包,此时服务器进入SYN_RECV状态。
- 第三次握手: 客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ACK=K+1),此包发送完毕,客户端和服务器进入ESTABLISHED状态。
完成三次握手,客户端和服务器开始传送数据,也就是ESTABLISHED状态。
结束连接
TCP有个概念叫作半关闭,TCP的连接时全双工连接,因此在关闭连接的时候,必须关闭穿和送两个方向上的连接。客户端给服务器发送一个FIN的TCP报文,然后服务器返回给客户端一个确认ACK报文,并且发送一个FIN报文,当客户机回复ACK报文。连接就结束了。
为什么建连接要3次握手,断连接需要4次挥手。
对于建连接的3次握手,主要是要初始化Sequence Number的初始值。通信的双方要互相通知对方自己的初始化的Sequence Number----所以叫SYN。 这个号做作为以后的数据通信的序号,以保证应用层接收到的数据不会因为网络上的传输问题而乱序。
对于4次挥手,其实仔细看则是两次。因为TCP是全双工的。所以,发送方和接收方都需要FIN和ACK。只不过,有一方是被动的,所以看上去就是所谓的4次挥手。
上图是著名的TCP状态流转图。
- CLOSED: 表示初始状态
- LISTEN: 表示服务器的某个socket处于监听状态,可以接受连接
- SYN_SENT: 在服务端监听后,客户端socket执行CONNECT连接时,客户端发送SYN报文,此时客户端就进入SYN_SENT状态,等待服务端的确认。
- SYN_RCVD: 表示服务端接收到了SYN报文。当收到客户端的ACK报文后,它会进入到ESTABLISHED状态。
- ESTABLISHED:表示连接已经建立了。
- FIN_WAIT_1: 这个是已经建立连接后,其中一方请求终止连接,等待对方的FIN报文。FIN_WAIT_1状态是当socket在ESTABLISHED状态时,它想主动关闭连接,向对方发送了FIN报文,此时该socket即进入到FIN_WAIT_1状态,而当对方回应ACK报文后,则进入到FIN_WAIT_2状态。
- FIN_WAIT_2:实际上FIN_WAIT_2状态下的socket,表示半连接,即有一方要求关闭连接,但另外还告诉对方:我暂时还有点数据需要传送给你,请稍后在关闭连接。著名的半关闭状态,在关闭连接时,客户端和服务器两次握手之后的状态,在这个状态下,应用程序还有接收数据的能力,但是已经无法发送数据。
- TIME_WAIT: 表示收到了对方的FIN的报文,并发送出了ACK报文,就等2MSL后即可回到CLOSED可用状态了。如果在FIN_WAIT_1状态下,收到了对方同时带FIN标志和ACK标志的报文是,可以直接进入到TIME_WAIT状态,无需经过FIN_WAIT_2状态。
- CLOSING: 如果双方同时在关闭一个socket,就会出现双方同时发送FIN报文的情况,就会出现CLOSING状态,表示双方都正在关闭socket连接。
- CLOSE_WAIT: 等待关闭。 当对方关闭一个socket后发送FIN报文给自己时,系统将毫无疑问地会回应一个ACK报文给对方,此时则进入CLOSE_WAIT状态。在这个状态下,需要完成的事情是等待你去关闭连接。
- LAST_ACK: 被动关闭的一方在发送FIN报文后,最后等待对方的ACK报文。
- CLOSED: 收到ACK报文后,进入到CLOSED状态。
这里需要说明一个2MSL的状态。 TIME_WAIT等到状态又叫做2MSL状态,说的是客户端连接在收到服务器的结束报文段之后,并没有直接进入CLOSED状态,而是转移到TIME_WAIT状态。在这个状态,客户端连接要等待一段长为2MSL(报文段最大生存时间),才能完全关闭,2MSL就是2min。
TIME_WAIT状态的存在原因有两点: - 可靠地终止TCP连接
- 保证让迟来的TCP报文段有足够的时间被识别并丢弃
第一个原因很好理解,如果服务器发送到客户端的FIN丢失,那么服务器需要重发结束报文段。因此客户端需要停留在某个状态以处理重复收到的结束报文段。
第二个原因,在Linux系统上,一个TCP端口不能被同时打开多次。当一个TCP连接处于TIME_WAIT状态时,我们将无法立即使用该连接占用着的端口来建立一个新连接。反之,如果不存在,则应用程序能够建立一个和刚关闭的连接相似的连接(具有相同的IP和端口号)。这个新的、和原来相似的连接被称为副本,副本可能接收到属于原来的连接的、携带应用程序数据的TCP报文段,这显然是不应该发生的。
另外,因为TCP报文段的最大生存时间是MSL, 所以坚持2MSL时间的TIME_WAIT状态能够确保网络上两个传输方向上尚未被接收到的、迟到的TCP报文段都已经消失。因此,一个新连接可以在2MSL时间之后安全地建立,而绝对不会接收到属于原来连接的应用程序,这就是TIME_WAIT状态要持续2MSL时间的原因。
4.TCP超时重传
在异常网络下(出现超时和丢包),TCP如何控制数据传输以保证其承诺的可靠服务。
假设有这样几种情况:
1) 数据顺利到对端,对端顺利响应ACK
2 数据包中途丢失;
3) 数据包顺利到达,但ACK报文中途丢失
4) 数据包顺利到达对端,但对端异常未响应ACK或被对端丢弃。
当出现这些异常情况时,TCP就会超时重传。TCP每发送一个报文段,就对这个报文段设置一次计时器。只要计时器设置的时间到了,但还没有收到确认,就要重传这一报文段,这个叫做"超时重传"。这个重传时间设置多少才合适呢?这里有个关键的参数叫做RTO(重传超时时间)。RTO是指发送端发送数据后、重传数据前等待接受方收到该数据报文的ACK时间。
RTO的设置对于重传十分重要:
(1)设长了,重发就慢,没有效率,性能差
(2)设短了,重发得就快,会增加网络拥塞,导致更多的超时,更多的超时导致更多的重发。
为了动态地设置RTO的时间,TCP引入了RTT(Round Trip time), 连接往返时间,指发送端从发送TCP包开始到接收它的立即响应所耗费的传输时间。RTO与RTT:
可以根据RTT算出RTO的时间,RFC793定义的经典算法是这样的。先采样RTT,记下最近几次的RTT值; 然后做平滑计算SRTT。公式中的α取值为0.8~0.9,这个算法叫做加权移动平均。公式如下:
SRTT = α * SRTT + (1-α) *RTT
RTO = MIN(UBOUND, MAX(LBOUND, (β * SRTT)))
UBOUND是最大的timeout时间,上限值; LBOUND是最小的timeout时间,下限值;β值一般在1.3~2.0.
但是上面的算法在重传的时候会出一个问题,如果是用第一次的时间和ACK回来的时间做RTT样本,或是用重传的时间和ACK的时间做RTT样本,都会产生问题。
如上图,a是ACK没回来,发重传,如果计算第一次发送和ACK的时间,明显算大了。b是ACK回来慢了,重传不一会,ACK就回来了,所以明显会算短。所以Karn算法用了一个取巧的方式,只要发生重传,就对现有的RTO值翻倍。
新的重传时间 = r * 旧的重传时间。 r的值为2
5.TCP滑动窗口
TCP的滑动窗口的主要作用有两个:一是提供TCP的可靠性,二是提供TCP的流控特性
TCP窗口是一个16bit位字段,它代表的是窗口的字节容量,也就是TCP的标准窗口,最大为2^16 -1 = 65535个字节
另外在TCP的选项字段中还包含一个TCP窗口扩大因子,option-kind 为3, option-length为3,option-data取值范围0~14。窗口扩大因子用来扩大TCP窗口,可把原来16bit的窗口,扩大为32bit。
对于TCP会话的发送方,任何时候再起发送缓存内的数据都可以分为4类:
- 已经发送并得到对端的ack。
- 已经发送但还未收到对端的ack
- 未发送但对端允许发送
- 未发送且对单不允许发送
其中已发送未收到ack的称为滑动窗口。
当收到新的ack对于发送窗口后续字节确认时,如:
对于TCP的接收方,在某一时刻在它的接收缓存内存在3种状态,
- 已接收
- 未接收准备接收
- 未接收并未准备接收
其中未接受准备接收称为接收窗口
6.TCP拥塞控制
TCP的拥塞控制主要由4个算法组成:
- 慢开始
- 拥塞避免
- 快速重传
- 快速恢复
慢开始和拥塞避免
发送方维持一个叫作拥塞窗口cwnd的状态变量,大小取决于网络的拥塞程度,并且动态地在变化。
慢开始的思路是: 不要一开始就发送大量的数据,先探测一下网络的拥塞程度,从小到大逐渐增加拥塞窗口的大小
一次传输轮次之后拥塞窗口就加倍。进行指数增长,为了防止cwnd增长过大引起网络拥塞,还需要设置一个慢开始门限ssthresh状态变量。增长到ssthresh大小时,就开始使用拥塞避免算法,让窗口缓慢增长,每经过一个RTT就把发送方的拥塞窗口cwnd+1。
拥塞控制的过程如下:
- TCP连接初始化,将拥塞窗口设置为1
- 执行慢开始算法,cwnd按指数增长,知道cwnd = ssthresh时,开始执行拥塞避免算法,cwnd按线性增长
- 当网络发生拥塞,把ssthresh值更新为拥塞前ssthresh值的一半,cwnd重新设置为1,按照第二步执行
快重传和快恢复
快重传要求接收方在收到一个失序的报文段后就立即发出重复确认,而不要等到自己发送数据时捎带确认,快重传算法规定,发送方只要一连收到3个重复确认就应当立即重传对方尚未收到的报文段,而不必继续等待设置的重传计时器到期。
快重传+快恢复 - 当发送方连续收到三个重复确认时,就执行乘法减小算法,把ssthresh门限减半。但是接下去并不执行慢开始算法
- 考虑到如果网络没有出现拥塞的话就不会收到好几个重复的确认,所以发送方现在认为网络可能没有出现拥塞。此时不执行慢开始算法,而是将cwnd设置为ssthresh的大小,然后执行拥塞避免算法。
参考文献:
- <后台开发 核心技术与应用实践>
- <Linux高性能服务器编程>