TCP/IP参考模型-传输层TCP

系列文章目录


由于因特网的实现是一个分层架构,为了防止篇幅过大,笔者不想一篇文章就写完想表达的所有内容,于是会以系列文章的方式,主要内容包括TCP/IP分层架构、网络层的最短路径算法:Dijkstra算法的实现细节,传输层的TCP传输控制协议的拥塞控制、流量控制以及握手和挥手,和应用层的DNS服务。

《TCP/IP参考模型-分层架构》

《TCP/IP参考模型-应用层的DNS》

《TCP/IP参考模型-传输层TCP》

《TCP/IP参考模型-网络层Dijkstra算法》

前言


TCP(Transmission Control Protocol 传输控制协议)是TCP/IP中位于传输层的一个协议,主要用于在尽力而为的无连接的IP层之上,提供可靠的、面向连接的网络服务。与它同层的另一个协议叫UDP(User Datagram Protocol 用户数据报协议),它是一种无连接、不可靠的协议,可以简单地认为TCP是UDP的一个超集(IP协议的基本传输单元是datagram,我猜UDP是以这个来命名的)。由于UDP只管往目标服务器发送数据报,而不管收没收到,先后顺序,会不会淹没接收方,线路会不会变拥塞,所以TCP的握手、挥手、确认重传、流量控制和拥塞控制等功能就值得我们去探索,接下来让我们看看它是怎么实现这些功能的。

连接建立

在操作系统中有一个端口(Port)的概念,相信大家已经很熟悉了,最常用的端口就是80端口,如果看到访问网页没有加端口,其实就是默认访问了该网站的80端口。两台电脑中的程序要进行通信时,需要服务端监听一个端口,等待入境请求,客户端也是启动一个程序监听一个端口,因为客户端也需要接受服务端的应答。在传输层,通过以下这个五元组来唯一标识一对客户端和服务端:

客户端IP地址 客户端端口 服务端IP地址 服务端端口 协议

最后一列协议是为了区分TCP和UDP(没错,UDP也需要这个,它最大的作用大概就是,在IP层的端到端之上提供了两个应用程序之间的通信服务)。

在两个应用程序进行通信之前,需要确定双方是否都有能力通信,TCP通过三次握手来建立一个连接,连接建立过程中是作为TCP基本传输单元的(segment)的段头在起区分作用,其格式如下:

源端口 目标端口
序号
确认号
TCP头长度 CWR ECE URG ACK PSH RST SYN FIN 窗口大小
校验和 紧急指针
选项(0或32位的倍数)
数据(可选)

三次握手的过程是这样的,客户端首先发送一个设置了SYN标志位并且序号为x的段到服务端(序号是一个自增的序列号,表示发送方当前段的顺序,确认号代表期待对方的下一个序号),表示请求建立连接,如果服务端事先监听了指定的端口,那么它会回复一个设置了SYN和ACK标志位的并且序号为y,确认号为x+1段,表示我已收到你的连接请求,并且同意你的连接,期待你下次x+1的请求。

当客户端收到这个请求时,它就可以确定连接已经建立了,但问题是服务端并不知道自己发送的段有没有被客户端收到,于是就需要第三次握手,客户端向服务端发送一个设置了ACK标志位,序号为x+1,确认号为y+1的段。因为在第三次握手前服务端已经同意了连接请求,于是第三次握手时客户端可以顺带发送数据了,当服务端收到这个请求后,它也可以确定连接已经建立,整个过程如下图所示:

TCP/IP参考模型-传输层TCP_第1张图片

确认重传

在连接建立之后,双方发送的段都需要另一方的回应,以确定自己发送的段被接收到了。比如第三次握手时发送的[ACK,序号x+1,确认号y+1],如果服务端收到这个请求,则会回复[ACK,序号y+1,确认号x+2],客户端收到这个回应时就知道自己的段被正确接收了。

上面说的是正常情况下的反应,不正常情况中代表性的大致有以下两种:

  1. 客户端到服务端之间的传输线路中某个子网通信断开,导致段丢失,服务器没收到。
  2. 客户端到服务端之间的传输线路没有问题,但是其中某个子网出现了拥塞,导致段被卡在了那里,段可能延迟很久才会到达服务端。

这两种情况分别代表着段丢失、段延迟,而在客户端看来,它发的序号为x+1的段就是没被接收到的,判断机制是这样的,客户端等待一段时间,如果这个段没被对方回复确认,则会重新发送该段,直到收到对方的回复为止。因为第四种情况会导致段延迟到达,可能客户端的重发段还比第一次发出的段早到达,TCP采用累积确认(更多的细节请看下面流量控制小节)的机制,来防止序列号重复的段被接收。

流量控制

客户端在不知道服务端承载能力的情况下,可能会发出超过服务端承载能力的数据,从而导致服务端承受不住,出现丢包、内存溢出等现象。为了防止这种情况发生,TCP发明了自己的流量控制机制。

缓冲区

在讨论缓存区这个概念之前,我们先想象一个场景,假设接收方一次只能接收一个段,绑定接收方端口的应用程序现在被操作系统进程调度机制给阻塞了,段在这个时候被发送方给发了过来,接收方没有办法处理,就会导致丢包现象发生。

于是TCP会维护一个缓存区,或者说消息队列,操作系统收到段时不直接提供给对应的应用程序,而是先缓存在内存中来,当应用程序有消费能力时,再到队列中消费。而且队列的大小是有限的,接收方可以在应答发送方时,在段头的窗口大小里告知我方的队列大小。(这里的消息队列是被精简描述了,实际上会涉及操作系统内核缓冲区、用户态缓冲区、DMA等相关细节,并且里面有很多优化空间)

滑动窗口

把缓冲区的概念升华一下,就可以变成滑动窗口,滑动窗口是实现流量控制的关键。滑动窗口,主要分为两部分,一部分是发送窗口,一部分是接收窗口,通信双方都持有这两个窗口,一个用来发送段,一个用来接收段。

发送窗口的格式如下所示:

TCP/IP参考模型-传输层TCP_第2张图片 图1

[已发送,已确认]表示数据发出并且得到了确认,这部分数据其实已经可以清出缓存了,[发送窗口]表示发送方允许发送的序列范围,它的大小等于接收方的[接收窗口]大小,如图2所示。

其中[已发送,未确认]表示还未得到接收方回复的段,这些段后面可能会重发,所以需要一直保存在缓存里面。 TCP为了保证给应用程序消费的数据是按照它原本的顺序来的,采用了累计确认机制。一个段可以被消费的前提是小于它的段都已经被接收,就像图2中序号13没有被接收到,所以14-17的段都不可被消费。

TCP/IP参考模型-传输层TCP_第3张图片 图2

[未发送,待发送]可以被应用程序写入,然后等待TCP实体(TCP实体和上文的TCP的不同在于,TCP实体是操作系统实现TCP协议的软件,一般以内核软件的形式存在)发送。

[不可发送]则表示应用程序提交的段超过了接收方允许的范围,因此TCP实体不会发送它。当[已发送,未确认]中的某个段收到确认时,发送窗口会发送右移,属于[已发送,未确认]的段变成[已发送,已确认]的段,[不可发送]中最左边的一个段变成[未发送,待发送]的一个段,这样来达到持续确认、发送的目的。

流量控制除了滑动窗口机制,还有一些优化机制,比如在接收方告知发送方窗口大小时的Clark算法、发送方等不及接收回应的窗口探测算法等。

拥塞控制

拥塞控制和流量控制有些相似,主要的区别在于:流量控制解决的是高速的发送方淹没低速的接收方的问题,拥塞控制解决的是发送方和接收方之间链路的带宽与延时问题,即网络拥塞。拥塞控制解决的不仅仅是接收方承载能力问题,还有发送方与接收方之间相关路径的链路的负载问题,更明白的讲,是为了防止超过相关链路上的路由器可以承受的数据报数量。

另外一个原因是,TCP不仅仅是一家公司的协议,而是整个Internet上都在用的协议,所以它不能只顾及发送接收双方的利益,而要考虑同在这些链路上传输信息的更多的发送和接收方,为了在有限的资源里,利益最大化,只有人人都不内卷而是朝着共同目标努力!

确认时钟

确认时钟指的是发送方发送数据的速率,一个段从发送方到接收方会经过很多能力不同的网络,发送方所处的Ethernet带宽是1Gbps,发送过程中有个5Mbps的ADSL线路,那么一个段被发出去到确认段回来,肯定会受到带宽较小的网络的影响,所以最好的发送速率就是接收到确认段的速率,也就是5Mbps,如果以这个速率发送数据包,沿途中的任何路由器都不会产生缓存队列。

慢启动

速率之后是窗口大小,也就是一次性突发数据大小,它的值是拥塞窗口和流量窗口中的较小者,分别代表着发送方认为的大小和接收方认为的大小。

需要注意的是,突发数据大小并不是指可以同时发送的数据大小,而是指以确认时钟的速率发送并且可以不用等待ack的数据大小。

拥塞窗口大小一开始被设定为1个MSS(最大段大小Maximum Segment Size,一种防止IP层分段的技术,1个MSS就是一个段)大小,得到一个确认之后就+1。第一个段被确认后,窗口大小变成2,于是会发送2个段,这时候就会收到2个确认,从而变成4个段,4个段都确认之后变成8个段。表现为2的N次方式增长。图3展示了一个慢启动过程的例子。

TCP/IP参考模型-传输层TCP_第4张图片 图3

慢启动阈值

慢启动不是无限制的增长的,它必须有一个阈值,一开始可以将慢启动阈值设置为流量窗口的大小,因为它不能超过接收方给出的流量窗口大小。

TCP采用丢包表示出现拥塞的信号,因为这代表着网络中有路由器出现队列,并且数据量超过了队列长度,从而导致了丢包。所以当TCP发送方的某个段计时器超时时,慢启动阈值将被设为拥塞窗口一半的大小,为什么是一半呢?打个比方,通过慢启动算法,拥塞窗口被设为了16,然后这个时候出现了丢包,那么在上一个往返时间里的拥塞窗口就应该是一个更好的估算值,也就是8。然后拥塞窗口被设为0,重新开始慢启动。

线性增长/拥塞避免

当慢启动一切正常,到达慢启动阈值之前都没有发送丢包,同时拥塞窗口超过慢启动阈值时。这种情况下不能继续使用慢启动了,因为很可能导致拥塞出现,于是将增长方式从慢启动的指数增长变成线性增长。也就是说原来收到一个确认增加一个MSS,现在是一个往返时间只增加一个MSS,而不管往返时间里共发送了多少个段。这个算法在其他文章里也称为拥塞避免。如图4所示。

快速重传

前面通过超时来判断丢包有一个优化点:必须等保守的超时器超时才能检测到丢包出现。于是可以采用这种机制:假设接收方收到了序列号为5的包,这时应该预期收到6,结果收到了7,这说明了段6很可能丢失了,所以接收方直接发送一个重复确认段给发送方,确认号是5。

当发送方收到这个重复确认时,它重发确认号的下一个段(5的下一个是6)。这样可以快速反应段的丢失。TCP使用三个重复确认代表出现丢包的标识,这时的处理方式和出现超时时一样,慢启动阈值变成拥塞窗口的一半,重新开始慢启动。

快速恢复

慢启动+线性增长+快速重传的组合算法被称为TCP Tahoe 是1988年的一个拥塞算法版本,在后来的1990年出现了一个优化版本TCP Reno。它就是在TCP Tahoe的快速重传里增加了一个快速恢复机制所谓快速恢复就是指,除了三次握手后的第一次启动和超时丢包时使用慢启动算法,其他时候不应该重新来一遍慢启动,应该继续以当前拥塞窗口一半的速率(确认时钟)来发送段,这样可以防止网络从一个非常大的速率突然降到极小,其目的是优化在快速重传时拥塞窗口又从0开始的问题,可以起到缓冲作用。所以快速恢复将慢启动阈值设置为拥塞窗口的一半,但是以线性增长的方式继续递增,,TCP Reno的全貌如图4所示。 

TCP/IP参考模型-传输层TCP_第5张图片 图4

连接释放

当客户端不再想发送数据时,它发送一个设置了FIN标志位的段给服务端,表示我现在没有数据可发了,服务端收到后会先看自己还没有没未回复的信息,如果有则先回复一个置了ACK的段,表示我收到了,但是还有点事。这次客户端不会立即关闭连接,等到服务端认为自己没有数据再发时,它就再发送一个设置了FIN标志位的段给客户端,这时连接才算释放。这里总共经过了四次,所以连接释放叫四次挥手,如果在服务端收到第一个客户端发来的FIN时,已经确定没有消息回复了,那么可以同时设置FIN和ACK,由此变成3次挥手。四次挥手整个过程如下所示:

TCP/IP参考模型-传输层TCP_第6张图片

总结

TCP/IP体系结构在Internet上工作了这么多年没有被淘汰,一定有其深层次原因,所以我认为学习它的必要性很大,对日后的工作也有指导作用,所谓树要想长得越高,根就要越深。在本文章中,有一些内容没有完全展开,比如为什么连接建立需要三次握手,连接释放需要四次挥手,读者如果对这方面好奇,可以继续深入研究,笔者就不展开了。

你可能感兴趣的:(计算机网络,tcp/ip,网络,网络协议)