TCP协议
一、网络模型
七层(IOS) | 五层 | 四层(TCP/IP) |
---|---|---|
应用层 | ||
表示层 | ||
会话层 | 应用层 | 应用层 |
传输层 | 运输层 | 传输层 |
网络层 | 网络层 | 网间层 |
数据链路层 | 数据链路层 | 网络接口(链路层) |
应用层 | 物理层 |
ISO七层网络模型及其功能展示:
层数(IOS) | 名字 | 主要功能(TCP/IP) | 对应的典型设备 | 传输单位 |
---|---|---|---|---|
7 | 应用层 | 提供应用程序间通信 | 计算机:应用程序,如FTP、SMTP、HTTP等 | 程序级数据 |
6 | 表示层 | 处理数据格式、数据加密等 | 计算机:编码方式,如图像编解码、URL字段传输编码等 | 程序级数据 |
5 | 会话层 | 建立、维护和管理会话 | 计算机:建立会话,如session认证、断点续传 | 程序级数据 |
4 | 运输层 | 建立主机端到端的链接 | 计算机:进程和端口 | 数据段 |
3 | 网络层 | 寻址和路由选择 | 网络:路由器、防火墙、多层交换机 | 数据包 |
3 | 数据链路层 | 提供介质访问、链路管理等 | 网络:网卡、网桥、交换机 | 帧 |
1 | 物理层 | 比特流传输 | 网络:中继器、集线器、网线和HUB | 比特 |
二、TCP头部
16 位端口号(Source Port && Destination Port):告知主机该报文段是来自哪里(源端口)以及传给哪个上层协议或应用程序(目的端口)的。
32 位序号(Sequence Number):一次 TCP 通信(从 TCP 连接建立到断开)过程中某一个传输方向上的字节流的每个字节的编号。
32 位确认号(Acknowledgement Number):用作对另一方发送来的 TCP 报文段的响应。其值是收到的 TCP报文段的序号值加1。
4位头部长度(Offset):标识该TCP头部有多少个 32bit ( 4 Byte )。
6位保留(Reserved):保留备用。
6位标志位(TCP Flags):
- URG:紧急指针是否有效m
- ACK: 确认号是否有效——>确认报文段
- PSH: 提示接收端应用程序应该立即从 TCP 接收缓冲区中读走数据
- RST: 要求对方重新建立连接——>复位报文段
- SYN: 请求建立一个连接——>同步报文段
- FIN: 通知对方本端要关闭——>结束报文段
16位窗口大小(Window):滑动窗口流量控制,告诉对方本段的接收缓冲区还有多少。
16位校验和(Checksum):由发送端填充,接收端对其进行CRC算法,建议TCP报文段在传输过程中是否损坏。
16位紧急指针:正的偏移量。它的序号字段的值相加表示最后一个紧急数据的下一个字节的序号。
二、TCP状态流转
建立连接的过程:
在TCP /IP 协议中, TCP 协议提供可靠的连接服务,采用三次握手建立一个连接。
- 第一次握手: 建立连接时,客户端发送 SYN 包(SYN = x)到服务器,并进入SYN_SENT 状态,等待服务器确认。
- 第二次握手:服务器收到 SYN 包,必须确认客户的 SYN ( ACK= x + 1 ),同时自己也发送一个SYN 包( SYN=y),即 SYN + ACK 包,此时服务器进入 SYN_RECV 状态。
- 第三次握手: 客户端收到服务器的SYN + ACK 包,向服务器发送确认包 ACK(ACK=y+1),此包发送完毕,客户端和服务器进入 ESTABLISHED 状态,完成3 次握手。
完成三次握手,客户端与服务器开始传送数据,也就是ESTABLISHED 状态。
结束连接的过程:
- 客户端 A 发送一个 FIN(FIN = X + 2),用来关闭客户 A 到服务器B的数据传送。
- 服务器 B 收到这个 FIN,它发回一个ACK(x + 3),确认序号为收到的序号加 1。和 SYN 一样,一个 FIN将占用一个序号。
- 服务器 B 关闭与客户端 A 的连接,发送一个 FIN 给客户端 A(报文段6)。
- 客户端 A 发回 ACK 报文确认,并将确认序号设置为收到序号加 1(报文段7)。
状态详解:
CLOSED: 这个没什么好说的了,表示初始状态。
LISTEN: 这个也是非常容易理解的一个状态,表示服务器端的某个SOCKET处于监听状态,可以接受连接了。
SYN_RCVD: 这个状态表示接受到了SYN报文,在正常情况下,这个状态是服务器端的SOCKET在建立TCP连接时的三次握手会话过程中的一个中间状态,很短暂,基本 上用netstat你是很难看到这种状态的,除非你特意写了一个客户端测试程序,故意将三次TCP握手过程中最后一个ACK报文不予发送。因此这种状态 时,当收到客户端的ACK报文后,它会进入到ESTABLISHED状态。
SYN_SENT: 这个状态与SYN_RCVD遥想呼应,当客户端SOCKET执行CONNECT连接时,它首先发送SYN报文,因此也随即它会进入到了SYN_SENT状态,并等待服务端的发送三次握手中的第2个报文。SYN_SENT状态表示客户端已发送SYN报文。
ESTABLISHED:这个容易理解了,表示连接已经建立了。
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看到。
FIN_WAIT_2:上面已经详细解释了这种状态,实际上FIN_WAIT_2状态下的SOCKET,表示半连接,也即有一方要求close连接,但另外还告诉对方,我暂时还有点数据需要传送给你,稍后再关闭连接。
TIME_WAIT: 表示收到了对方的FIN报文,并发送出了ACK报文,就等2MSL后即可回到CLOSED可用状态了。如果FIN_WAIT_1状态下,收到了对方同时带FIN标志和ACK标志的报文时,可以直接进入到TIME_WAIT状态,而无须经过FIN_WAIT_2状态。
CLOSING: 这种状态比较特殊,实际情况中应该是很少见,属于一种比较罕见的例外状态。正常情况下,当你发送FIN报文后,按理来说是应该先收到(或同时收到)对方的 ACK报文,再收到对方的FIN报文。但是CLOSING状态表示你发送FIN报文后,并没有收到对方的ACK报文,反而却也收到了对方的FIN报文。什 么情况下会出现此种情况呢?其实细想一下,也不难得出结论:那就是如果双方几乎在同时close一个SOCKET的话,那么就出现了双方同时发送FIN报 文的情况,也即会出现CLOSING状态,表示双方都正在关闭SOCKET连接。
CLOSE_WAIT: 这种状态的含义其实是表示在等待关闭。怎么理解呢?当对方close一个SOCKET后发送FIN报文给自己,你系统毫无疑问地会回应一个ACK报文给对 方,此时则进入到CLOSE_WAIT状态。接下来呢,实际上你真正需要考虑的事情是察看你是否还有数据发送给对方,如果没有的话,那么你也就可以 close这个SOCKET,发送FIN报文给对方,也即关闭连接。所以你在CLOSE_WAIT状态下,需要完成的事情是等待你去关闭连接。
LAST_ACK: 这个状态还是比较容易好理解的,它是被动关闭一方在发送FIN报文后,最后等待对方的ACK报文。当收到ACK报文后,也即可以进入到CLOSED可用状态了。
几个问题:
1.为什么建立连接协议是三次握手,而关闭连接却是四次握手呢?
这是因为服务端的LISTEN状态下的SOCKET当收到SYN报文的建连请求后,它可以把ACK和SYN(ACK起应答作用,而SYN起同步作用)放在一个报文里来发送。但关闭连接时,当收到对方的FIN报文通知时,它仅仅表示对方没有数据发送给你了;但未必你所有的数据都全部发送给对方了,所以你可以未必会马上会关闭SOCKET,也即你可能还需要发送一些数据给对方之后,再发送FIN报文给对方来表示你同意现在可以关闭连接了,所以它这里的ACK报文和FIN报文多数情况下都是分开发送的.
2.为什么TIME_WAIT状态还需要等2MSL后才能返回到CLOSED状态?
这是因为虽然双方都同意关闭连接了,而且握手的4个报文也都协调和发送完毕,按理可以直接回到CLOSED状态(就好比从SYN_SEND状态到ESTABLISH状态那样);但是因为我们必须要假想网络是不可靠的,你无法保证你最后发送的ACK报文会一定被对方收到,因此对方处于LAST_ACK状态下的SOCKET可能会因为超时未收到ACK报文,而重发FIN报文,所以这个TIME_WAIT状态的作用就是用来重发可能丢失的ACK报文。
三、超时重传
TCP协议是一种面向连接的可靠的传输层协议,它保证了数据的可靠传输,对于一些出错,超时丢包等问题TCP设计的超时与重传机制。其基本原理:在发送一个数据之后,就开启一个定时器,若是在这个时间内没有收到发送数据的ACK确认报文,则对该报文进行重传,在达到一定次数还没有成功时放弃并发送一个复位信号。
这里比较重要的是重传超时时间
,怎样设置这个定时器的时间(RTO
),从而保证对网络资源最小的浪费。因为若RTO太小,可能有些报文只是遇到拥堵或网络不好延迟较大而已,这样就会造成不必要的重传。太大的话,使发送端需要等待过长的时间才能发现数据丢失,影响网络传输效率。
超时重传机制如下:
经典方法:
通过下面的公式计算得到平滑的RTT估计值(SRTT):
SRTT <- α(SRTT) + (1-α)RTTs
最新的SRTT是根据现存的SRTT和新的样本值RTTs计算出来的。 常量α为平滑因子,推荐值是0.8~0.9。这种计算方法叫做指数加权移动平均法或则低通过滤器。
根据前面求的SRTT计算出RTO:
RTO = min(ubound, max(lbound, (SRTT)β))
β是时延离散因子,推荐值1.3~2.0。 ubound是RTO上边界,lbound是下边界。这种计算方法就是经典方法。
这种方法缺点就是没法适应大规模的变动(网络不稳定情况)
基于计时器的重传:
TCP发送端得到了基于时间变化的RTT测量值,就能据此设置RTO,发送报文段时应确保重传计时器设置合理。设置计时器前要记录报文序列号,若及时收到了报文的ACK,就取消计时器。TCP的连接发送端不断的设定和取消重传计时器,如果没有数据丢失就不会出现计时器超时。
TCP将超时重传视为相当重要的事件,当发生这种情况时候,通过降低当前数据发送率来对此进行快速响应。实现有两种方法:一是基于拥塞控制机制减小发送窗口,二是当一个报文段被再次重传时候,增大RTO的退避因子。 退避因子正常情况下是1,随着多次重传会加倍成2,4,8等等,直到上限。
大多数情况下计时器超时并触发重传是不必要的,因为RTO的设置一般大于RTT的2倍,因此基于计时器的重传会导致网络利用率下降。幸运的是,TCP有另外一种方法检测和修复丢包,因为该方法不需要触发计时器超时,所以称为快速重传。
四、滑动窗口
关于这部分自己不晓得怎么叙述才好,因为理解的部分更多,下面就用自己的理解来介绍下TCP的精髓:滑动窗口协议。
所谓滑动窗口协议,自己理解有两点:1. “窗口”对应的是一段可以被发送者发送的字节序列,其连续的范围称之为“窗口”;2. “滑动”则是指这段“允许发送的范围”是可以随着发送的过程而变化的,方式就是按顺序“滑动”。在引入一个例子来说这个协议之前,我觉得很有必要先了解以下前提:
- TCP协议的两端分别为发送者A和接收者B,由于是全双工协议,因此A和B应该分别维护着一个独立的发送缓冲区和接收缓冲区,由于对等性(A发B收和B发A收),我们以A发送B接收的情况作为例子;
- 发送窗口是发送缓存中的一部分,是可以被TCP协议发送的那部分,其实应用层需要发送的所有数据都被放进了发送者的发送缓冲区;
- 发送窗口中相关的有四个概念:已发送并收到确认的数据(不再发送窗口和发送缓冲区之内)、已发送但未收到确认的数据(位于发送窗口之中)、允许发送但尚未发送的数据以及发送窗口外发送缓冲区内暂时不允许发送的数据;
- 每次成功发送数据之后,发送窗口就会在发送缓冲区中按顺序移动,将新的数据包含到窗口中准备发送;
下面我们来看一下发送方的滑动窗口示意图:
上图中分成了四个部分,分别是:(其中那个黑模型就是滑动窗口)
- 已收到ack确认的数据。
- 发还没收到ack的。
- 在窗口中还没有发出的(接收方还有空间)。
- 窗口以外的数据(接收方没空间)
下面是个滑动后的示意图(收到36的ack,并发出了46-51的字节):
下面我们来看一个接受端控制发送端的图示:
五、拥塞处理
拥塞概念
1、概念:在某段时间,如果对网络中的某一资源的需求超过了该资源所能提供的可用部分,网络的性能就要发生变化,这种情况叫阻塞
2、拥塞控制:防止过多的数据注入到网络当中,这样可以使网络中的路由器或链路不致过载。(通过拥塞窗口处理网络拥塞现象的一种机制)
- a.前提:网络能够承受现有的网络负荷。
- b.拥塞控制是一个全局性的过程,涉及到所有的主机、路由器,以及与降低1网络传输性能有关的所有因素
- c.与流量控制的区别:流量控制往往是指点对点通信量的控制,是个端到端的问题。流量控制所要做的就是控制发送端发送数据的速率,以便使接收端来得及接收。
- d.代价:需要获得网络内部流量分布的信息。在实施拥塞控制之前,还需要在结点之间交换信息和各种命令,以便选择控制的策略和实施控制。这样就产生了额外的开销。拥塞控制还需要将一些资源分配给各个用户单独使用,使得网络资源能更好的实现共享。
拥塞控制的几种方法
- 慢开始
- 拥塞避免
- 快重传
- 快恢复
1.慢开始
算法原理:当主机开始发送数据时,如果立即将大量数据字节注入到网络,那么就有可能因为不清楚当前网络的负荷情况而引起网络阻塞。所以,最好的方法是先探测一下,即由小到大逐渐增大发送窗口,也就是说,由小到大逐渐增大拥塞窗口数值。通常在刚刚发送报文段时,先把拥塞窗口cwnd设置为一个最大报文段MSS的数值。而在每收到一个新的报文段的确认后,把拥塞窗口增加至多一个MSS的数值。用这样的方法逐步增大发送方的拥塞窗口cwnd,可以使分组注入到网络的速率更加合理。(慢开始当中的“慢”并不是指cwnd的增长速率慢,而是在TCP开始发送报文段时先设置cwnd = 1,使得发送方在开始时只发送一一个报文段)
当rwnd足够大的时候,为了防止拥塞窗口cwnd的增长引起网络阻塞,还需要另外一个变量------慢开始门限ssthresh。
i.当cwnd < ssthresh时,使用上述慢启动算法;
ii.当cwnd > ssthresh时,停止使用慢启动算法,改用拥塞避免算法;
慢开始的局限性:
- i.需要获得网络内部流量分布的信息,浪费可用的网络容量,额外开销;
- ii.估算合理的 ssthresh 值并不容易,可能耗时较长;
2.拥塞避免
算法原理:让拥塞窗口cwnd缓慢的增大,即每经过一个往返时间RTT就把发送方的拥塞窗口cwnd加1,而不是加倍,这样拥塞窗口cwnd按线性规律缓慢的增长,比慢开始算法的拥塞窗口增长速率缓慢的多
无论是慢启动算法还是拥塞避免算法,只要判断网络出现拥塞,就要把慢启动开始门限(ssthresh)设置为发送窗口的一半(>=2),cwnd设置为1,然后再使用慢启动算法,这样做的目的能迅速的减少网络当中的数据传输,使发生拥塞的路由器能够把队列中堆积的分组处理完毕。拥塞窗口是按照线性的规律增长
控制过程:
- TCP连接初始化,将拥塞窗口cwnd设置为1个报文段,即cwnd = 1;
- 执行慢开始算法,cwnd按指数规律增长,知道cwnd == ssthresh时,开始执行拥塞避免算法,cwnd开始按照线性规律增长
- 当网络发生拥塞,把ssthresh值更新为拥塞前ssthresh值的一半,cwnd重新设置为1,再按照2执行;
例如:
a.在TCP连接进行初始化的时候,cwnd = 1,ssthresh = 16;
b.在慢启动算法开始时,cwnd的初始值是1,每次发送方收到一个ACK拥塞窗口就增加1,当ssthresh == cwnd时,启动拥塞控制算法,拥塞窗口按照线性规律增长;
c.当cwnd = 24时,网络出现超时,发送方收不到确认ACK,此时设置ssthresh = 12,设置cwnd = 1,然后开始慢启动算法,当cwnd = ssthresh=12,慢启动算法变为拥塞控制算法,cwnd按照线性速度增长
3.快重传
算法思路:一条TCP连接有时会因为等待重传计时的超时而空闲较长时间,慢开始和拥塞避免无法解决这类问题,因此提出了快重传和快恢复的拥塞控制方法。快重传算法要求首先接收方收到一个失序的报文段后立刻发出重复确认,而不要等待自己发送数据时才进行捎带确认,如下图
在上图中,接收方成功的接受了发送方发来的M1,M2并且分别发送了ACK,现在接收方没有收到M3,而收到了M4,显然接收方不能确认M4,因为M4是失序的报文段。如果根据可靠性传输原理接收方什么都不做,但是按照快速重传算法,在收到M4,M5等报文段的时候,不断重复的向发送方发送M2的ACK,如果接收方一连收到三个重复的ACK,那么发送方不必等待重传计时器到期,由于发送方尽早重传未被确认的报文段
4.快恢复
- 当发送方连续收到三个重复确认时,执行“乘法减小”算法,慢启动门限减半,为了预防网络发生阻塞
- 由于发送方现在认为网络很可能没有发生阻塞,因此现在不执行慢启动算法,而是把cwnd值设置为慢启动门限减半后的值,然后开始执行拥塞避免算法,拥塞窗口cwnd值线性增大。