前言:
本篇将要介绍UDP和TCP,篇幅略长,主要是TCP的内容较为复杂
6.3 UDP
UDP(User Datagram Protocol,用户数据报协议)
根据此前的了解,UDP不提供复杂的控制机制,其是一种利用IP提供面向无连接的通信服务。收到应用程序发来的数据的那一刻,就立即按照原样发送到网络上的一种机制。
即使出现网络拥堵,UDP也无法进行流量控制等避免网络拥塞的行为。即使过程中出现丢包,UDP也不负责重发。甚至包到达的顺序乱掉时,也没有纠正的功能。这些功能只能交由UDP的应用程序去处理。
听上去,UDP有点儿像“左耳朵进,右耳朵出”的一种通信机制,需要使用者充分考虑好上层协议类型并制作相应的应用程序。其处理简单又高效,因此常用于以下几个方面:
- 包总量较少的通信(DNS,SNMP(Simple Network Management Protocol,简单网络管理协议,用于网络层IP的管理))
- 视频、音频等多媒体通信(即时通信)
- 限定于LAN等特定网络中的应用通信
- 广播通信(广播、多播)
6.4 TCP
TCP(Transmission Control Protocol,传输控制协议)
与UDP完全不同,TCP人如其名,对“传输、发送、通信”进行“控制”的协议。
刚刚提到的那些UDP不具备的功能,在TCP中几乎都有。
正是由于这些机制,在IP这种无连接的网络上也能够实现高可靠性的通信。
此处,书中特意强调了一下连接的概念,其是指各种设备、线路,或者网络中进行通信的两个应用为了相互传递消息而专有的、虚拟的通信线路,也叫做虚拟电路。一旦连接建立,通信就只能依靠这个虚拟的通信线路发送和接收数据,保证信息的传输。TCP则负责控制链接的建立、断开、保持等管理工作。
接下来我们就将正式介绍TCP的方方面面,做好准备。
TCP通过校验和、序列号、确认应答、重发控制、连接管理以及窗口控制等机制实现可靠性传输。
首先我们要明确,既然TCP是一种有连接的通信方式,必然有一种手段来确保连接的建立。当前学校里上课的时候,一个很有名的例子就是两军打仗,当对敌方形成两面包夹之势的时候,如何确定双方一同出击。一个简单的想法就是派出一个通信员,带着“发起进攻”的消息,隐蔽自己的行踪直到到达另一侧的友方阵营,然后再带回来“确定进攻”的消息。
在TCP通信中,当发送端的数据到达接收主机时,接收端主机会返回一个已收到消息的通知,这个消息就叫做确认应答ACK
,另外还有否认应答NACK
。
TCP就是通过ACK实现可靠地数据传输。发送端将数据发出之后会等待对端的确认应答,如果有确认应答,说明数据已经成功到达对端。反之,说明数据丢失的可能性——注意,此处说的是可能性——很大。如下图所示,在一定时间内没有收到确认应答,发送端就认为数据已经丢失(联想到IP数据包首部中有生存时间这一说),并进行重发。通过这种机制,就可以保证数据能够到达对端(但没有确保到达的先后顺序)。
未收到确认应答并不意味着数据一定丢失,也可能是对方收到了,但是返回的确认应答在途中丢失了(通讯员在回来的路上被逮住了)。这时发送端也会进行重新发送,如下图所示:
此外,也有可能因为一些其他原因导致确认应答延迟到达,在发送端重发之后再收到ACK的情况也是屡见不鲜。这时,对于发送端还没什么,但是目标端就难办了(真假通讯员!),它收到了相同的数据,就必须得放弃重复的数据包(都是真的通讯员,但是多派出了一个)。为此,就需要一种机制,能够识别是否已经接收数据,又能够判断是否需要接收。
该机制就是序列号
。理解起来也很简单,序列号是按照顺序给发送数据的每一个字节(8位比特)都标上号码的编号。接收端查询数据TCP首部中的序列号和数据长度,将自己下一步应该接收的序号作为确认应答的一部分,返送回去。通过ACK和序列号两种机制,TCP已然可以实现较为可靠地传输过程了。整个过程如下图所示:
接下来我们就来谈谈,重发超时如何确定。
重发超时指在数据重发之前,等待确认应答到来的那个特定的时间间隔。如果超过了这个间隔,仍未收到应答,发送单就将进行数据重发。
那么,重发超时的时间应该如何确定呢?如果过短,那么就有可能重复发了很多遍相同的数据;如果过长,则会带来过大的延迟。
理想的是,找到一个“确认应答一定能在这个时间内返回”的时间。然而这个时间必然是要根据网络环境的不同而有所变化的。例如,在高速LAN中,这个时间就应当相对较短,而在长距离通信中,应当比LAN长一些。
既然TCP要求无论处在何种网络环境下都要提供高性能的通信,并且无论网络拥堵情况发生何种变化,都必须保持这一特性。因此,每次发包时都会计算往返时间(Round Trip Time,RTT)及其偏差(RTT时间波动的值、方差)。重发超时的时间就是比往返时间+偏差要大一点的时间。之所以要考虑偏差,也是有其理由的,如下图所示,根据网络环境的不同,数据包的分段经过不同线路到达彼端,往返时间可能会产生大幅度的摇摆。在这种情况下,TCP也要尽量进行不浪费网络流量的控制。
值得一提的是,在UNIX和Windows系统中,超时以0.5秒为单位进行控制,重发超时都是0.5秒的整倍数。不过,由于最初的数据包还不知道往返时间,所以其重发超时一般设置为6秒左右。
若数据重发之后,仍然收不到ACK,则再次发送,此时等待ACK的时间将会以2倍、4倍的指数函数延长。而且数据也不会被无限、反复的重发。到达一定的重发次数后,若仍没有ACK,就回判断网络或对端主机发生了异常,强制中断连接,并通知应用通信异常(ICMP此处应该可以派上用场)。
讲完了重发时间这样一个基础机制,接下来我们要说一下,TCP作为一种有连接的传输方式,是如何进行连接前的准备工作。
TCP在建立连接之前,会通过TCP首部发送一个SYN
包作为建立连接的请求,等待确认应答。如果对端也发来确认应答,则认为可以进行数据通信。如果对端的确认应答未到达,就不会进行数据通信。此外,在通信结束是,会进行断开连接的处理FIN
(Finish)。
以下的内容是重中之重,使用TCP首部用于控制的字段来管理TCP连接,这个过程从建立到断开,正常过程需要来回发送7个包才能完成。
这时候就会谈到我们常说的那句口号:三次握手,四次挥手。
因为建立连接的时候,需要发送三个包;而断开连接时,则需要发送四个包。
虽然上面这个过程看起来并不复杂,但确实是常考的重点内容。
让我们回想一下,数据链路层有MTU的概念,网络层也有IP分段的概念,那么传输层也是有的。在建立TCP连接的同时,也可以确定发送数据包的单位,即最大消息长度
(MSS,Maximum Segment Size)。最理想的情况,MSS正好是IP中不会被分片处理的最大数据长度。(回顾一下我们在网络层曾学习过的“路径MTU发现”)
TCP在传送大量数据时,以MSS的大小将数据进行分割发送,重发的时候也是以MSS为单位。
那么MSS到底是在什么时候被确定的呢?答案是三次握手时,会被计算得到。连接两端在发出建立连接的请求时,会在TCP中写入MSS选项(此时首部就不再是20字节,而是4的整数倍字节,例如20+4字节),告诉对方自己能够适应的MSS大小,然后两者会选择一个较小的值投入使用(如果有一方的MSS被省略,可选的IP包长度不超过576字节,这里要解释一下,IP首部20字节,TCP首部20字节,MSS则为536字节大小,总和为576字节)。整个过程可以见下图:
之前的两部分,我们讲了数据重发机制,如何保证连接的建立,以及连接建立后,数据大小如何选择。
现在我们继续对发送数据的探讨,讨论如何利用某种控制机制,提升窗口的发送速度。
结合我们此前所学,TCP以一个段为发送单位,每发一个段就进行一次确认应答,但是如此传输势必会带来一个缺点:包的往返时间越长,通信性能就越低。这也很符合我们的常识。
为了解决这一问题,TCP引入了窗口的概念,即使在往返时间较长的情况下,也可以控制网络性能的下降。如下图所示,确认应答不再是以段为单位,而是以窗口中的所有段为单位,这样发送端主机就不用在发送了一个段以后一直等待ACK,而是继续发送。
窗口的大小也顾名思义,就是无需等待确认应答二可以继续发送数据的最大值,上图中窗口大小就是4个段。本质上这是一种以空间换时间的机制,因为此机制用到了缓冲区,通过将数据保存在缓冲区中,实现对多段同时进行确认应答的功能。
如下图所示,只有当收到了确认应答后,窗口就可以向后移动,发送下一部分数据。如果没有收到的话,依旧需要根据缓存区中的数据,确认重发的内容。若后期如期收到了确认应答, 就不必重发, 在缓冲区中清除窗口内保存的数据。这种机制称为滑动窗口控制,在许多算法中也有其应用。
接下来要考虑的问题是,使用窗口控制,如果出现了段丢失,应该怎么办?
要分几种不同的情况来考虑。
-
如果是确认应答ACK没有返回(包括丢失、超时等情况),假设数据到了对端,实际上是不需要重发的。使用窗口控制时与每段都需要ACK不一样,某些ACK即使丢失,也无需重发。如下图所示:
在图中,1-1000的数据并没有收到ACK,但是也并不需要重发,因为1001~2000的数据发送到对端后,其会返回下一个序列号,表示此前的所有数据都已经收到了。
-
如果是报文段丢失,如下图所示,如果接收端收到一个自己应该接收的序号之外的数据时,会针对当前为止受到的数据返回确认应答。假设1001~2000该段报文丢失,接收端会一直发送请求1001的ACK,发送端如果连续收到3次(之所以是3次的理由,书中给出的是“即使数据段的序号被替换两次,也不会触发重发”)请求同一段的确认应答,就会对其进行重发。这种机制比此前提到的超时管理更为高效,因此被称作高速重发控制。(此处可以思考,为什么比超时管理更为高效,我想一个是没有打断发送端继续发送,二是不必空等)
接下来要讲的内容是流控制。流控制,顾名思义,是那种需要连续不断接收的数据,如果接收端收到了一个毫无关系的数据包(比如视频流里,因为各种原因迟到的几帧图像等),对其处理会花费时间,比如丢弃。如果在高负荷的情况下,甚至有可能无法接收任何数据。如果接收端将原本应当接受的数据丢弃的话,就会触发重发机制,导致网络流量的无端浪费。
为了应对这一情况,TCP中有一种可以让发送端根据接收端的实际接收能力控制发送数据量的机制,叫做流控制。该机制也很简单,接收端会告诉发送端自己可以接受数据的大小,发送端就不会发出超过该容量的数据。该大小被称为窗口大小
。
我们之后讲到TCP包首结构的时候会提到,有一个字段专门用于通知窗口大小。该字段的值越大,说明网络的吞吐量越高。当接收端的缓冲区面临数据溢出时,窗口大小的值就会被降低,控制发送端的流量。下图为一个参考示例,当接收端接收到3000,要接收3001之后的数据时,其缓冲区就只剩1000的容量了,当接收完3001~4000,若缓冲区满,接收端就不得不停止接收数据。此时发送端也不闲着,如果超过了重发超时的时间,还没有收到窗口更新的通知,发送端会认为是更新通知有可能丢失,就会时不时发出一个窗口探测的数据,确认下一个要接受的数据是什么。当收到窗口更新通知时,发送端就会继续发送之后的数据。
讲完了以上内容,接下来的内容也算是一个新的重点,就是拥塞控制。
该机制作用于通信刚刚建立时,发送端就要发送大量数据。由于网络环境通常情况下并非单对单,而是一个共享带宽的环境,有可能会因为其他主机导致网络拥堵,此时若再发送较大的数据量,甚至有可能导致整个网络的瘫痪。
TCP为了解决该问题,会通过一个叫做慢启动的算法,对发送数据量进行控制。如下图所示。
首先,发送端为了调节所要发送的数据量,定义了一个叫做“拥塞窗口”的概念。在慢启动的时候,将该拥塞窗口的大小设置为1个数据段,即1MSS(有时也会将慢启动的初始值设置为大于1MSS的值),之后每收到一次确认应答,拥塞窗口的值就加1。发送数据包时,将拥塞窗口的大小与接收端回馈的窗口大小进行比较,选择其中较小值,发送比其还要小的数据量。
如果重发采用超时机制,拥塞窗口的初设值就可以设置为1以后再启动慢启动修正。有了上述机制(流控制 + 拥塞控制),就可以有效的减少通信开始时连续发包导致的网络拥堵,还可以避免网络拥塞情况。
如果每次都追加1,有时候会导致慢启动过程太长,而且随着网络带宽的逐步提升,好似拥堵的情况会更加少见一些。所以,拥塞窗口也会以1、2、4等大小,以指数函数的方式增长,但总归来说,这么增长确实会导致网络拥塞发生的风险。为了防止该情况,便引入了慢启动阈值的概念,当拥塞窗口超时该阈值时,每收到一次ACK,只允许以下面的比例方法拥塞窗口:
下图展示了TCP拥塞窗口的变化。一开始呈指数增长,超时后重新开始,当超过阈值后,就重复ACK。
一旦超时,拥塞窗口就会初始化,超过慢启动阈值后,就会呈线性增长。
拥塞窗口越大,ACK的数量也会增多。每收到一个ACK,其涨幅也会逐渐减少,但总归会呈现一个直线上升的趋势。
TCP通信开始时,并没有设置相应的慢启动阈值,而是在超时重发时,才会设置为当时拥塞窗口一半的大小。若是因重复确认应答而触发的高速重发,因为要求至少3次的确认应答到达对方主机,相比较于超时重发机制,拥堵会稍轻一些。此时慢启动阈值的大小被设置为当时窗口大小的一般,然后将窗口大小设置为慢启动阈值+3个数据段的大小。
TCP通信,网络吞吐量会逐渐上升,但随着网络拥堵的发生吞吐量也会急速下降,就会再次进入吞吐量慢慢上升的过程。因此,TCP的吞吐量特点,就好像是在逐步占领网络带宽的感觉。
到这里为止,TCP的主要内容大致就讲的差不多了。接下来书中进行了一些扩展内容的介绍。
首先是Nagle
算法,该算法用于提高网络的利用率。具体来说,发送端如果还有需要发送的数据,但是这部分数据量很少,就进行延迟发送。在下列任意一种条件下才能发送
- 已发送的数据都已经收到了ACK时
- 可以发送MSS的数据量时
如果两个条件都不满足,那么就暂时等待一段时间以后再进行数据发送。
虽然该算法可以提高网络利用率,但是可能会导致一定程度的延迟。因为,在窗口系统以及机械控制等领域中使用TCP时,会关闭该算法。
其次是延迟确认应答机制。如果接收端每次都立刻回复ACK的话,可能会返回一个较小的窗口。比如刚接收完数据,缓冲区已满。又会因此成为发送数据的限制,导致网络利用率降低。所以可以引入一个机制,收到数据以后并不理解返回ACK,而是延迟一段时间。
- 没有收到2xMSS的数据为止,不做确认应答(根据操作系统不同,有时不论数据大小,只要收到两个包就立刻返回ACK的情况也是有的)
- 其他情况下,最大延迟0.5秒发送ACK(很多操作系统设置为0.2秒左右)
而且实际上,为每个段都来一次ACK䦹没必要。TCP中有滑动窗口机制,通常ACK少一些也无妨。TCP文件传输中,绝大多数是每两个数据段返回一次确认应答。见下图:
第三种机制就是捎带应答。应用层协议中,发送消息到达对端进行处理后,会返回一个回执。如下图所示。例如电子邮件协议的SMTP或POP、文件传输协议FTP中的连接控制部分等,都属于回执的一种。这些应用协议,即使使用WWW的HTTP协议,1.1版本之后,也是使用同一个TCP连接进行数据交互。
在此类通信过程中,TCP的ACK和回执数据可以通过一个包发送。这种方式就叫做捎带应答(PiggyBack Acknowledgement)。通过该机制,可以使收发的数据量减小。
另外,如果接收数据后立即返回ACK,就无法实现该机制。而是将所接收的数据传给应用处理,生成返回数据以后到进行发送请求位置,必须要一直等待ACK的发送。因此,如果没有启动延迟确认应答机制,捎带应答机制也就无法实现。延迟确认应答是能够提高网络利用率从而降低计算机处理负荷的一种较优的处理机制。
最后的最后,我们来讲一下使用TCP的应用建议。此前所讲的机制,并非是全部,还存在其他很多更为复杂的控制机制。TCP利用这些机制,最终目的都是为了实现高速、可靠的通信服务。
但是,也并非完全依赖这些机制就是好的,开发应用时,也需要权衡一下,到底是交给TCP还是交给应用本身去控制。
如果需要应用自己处理一些更为细节的控制,使用UDP协议是一种不错的选择。而如果转发数据量较多,对可靠性的要求较高,则可以使用TCP。两者各有所长,各有所短,需要具体情况具体选择。
本篇小结
这节内容可是够长的,接下来我们将进行一次从头到尾的梳理过程。
首先是UDP,UDP是一种无连接的通信方式,其不保证数据一定到达,也不会因为网络拥塞而采取某种控制机制,但是相对的,其发送速度较快,简单又高效,适合包总量较少、音视频、广播等通信过程。
其次是TCP。对于TCP,我想要分三个部分进行梳理
其一,基本概念——
TCP是一种需要建立连接的通信方式,通过连接过程中的诸多信息,对整个通信过程进行控制。连接本质上与网线、电缆线不同,其是一条虚拟链路,我们此前所提到的五元组{源IP,目标IP,源端口,目标端口,连接方式},就可以认为是一条相同的虚拟链路。
那么如何确保连接建立了呢?是通过确认应答(ACK)
的机制,当发送端想要建立连接时,接收端如果回复ACK,就表明其可以建立该连接。这种机制是用来确保传输可靠性的方法之一。
在传输过程中,最常见的两种情况,一是包没有发过去,在传输过程中丢失了,导致长时间没有收到ACK;二就是ACK没有及时送达。这两种情况都会引发超时重发
机制。那么超时重发机制如何确定呢?在不同的网络环境下,无论是包的发送还是ACK的回传,其用时都是不一样的,因此每次发包的时候,都会计算往返时间(RTT,Round Trip Time)
以及偏差(RTT的波动、方差等),重发超时的时间就是要比“往返时间 + 偏差”大一点的时间。
另一个保证可靠性的机制,就是序列号
。因为在发送端重发之后,再收到ACK的情况也是屡见不鲜。这时接收端理应丢弃重复的包,判断是否重复的机制就是序列号,为发送的每个字节都进行标号,这式接收端就可以检查TCP首部中的序列号和数据长度,来判断自己下一步应该接受的数据是什么,并作为ACK的一部分回馈给发送端。
有了ACK和超时重发两种机制,就可以明确TCP的整个连接过程。发送端在连接之前,会发送SYN
包请求建立连接,对端如果回馈ACK + SYN,发送端同样会回馈ACK,表示双方正式达成连接关系。
在释放链接的时候,发送端发送FIN
包,接收端对其进行应答,并同样发送FIN
包,发送端对其应答,经过双发的应答后,TCP连接就被释放了。
此处还是要放一下整体的图,以说明该过程三次握手,四次挥手
的重要性。
最后一点基本概念,就是最大消息长度(MSS)
,在三次握手时,该值会通过计算得到。
其二,控制机制——
TCP要控制的内容,可以分为三部分
-
数据内容控制,通过
滑动窗口
机制。即发送端不必等待到每个段的ACK回复后再进行数据发送,其会按照窗口的大小,连续发送相等大小的段,而接收端则需要使用缓冲区保留这些段,便于判断有无丢失与重复。本质上是一种空间换时间的处理方式。当接收端回馈确认应答后,会告诉发送端想要的数据,发送端就会紧接着发送后续的内容。
这时也会出现ACK没有返回以及发送包丢失的情况。对于前一种,如果接收端告诉发送端,此前的数据其实都已经接收到了,那么对于此前数据的ACK即使丢失,也是没必要重发的。而对于后一种情况,如果发送端连续收到3次,就会进行重发,此机制也叫做
高速重发机制
。 -
数据大小控制,通过
流控制
机制。即接收端会告知发送端自己剩余的缓冲区大小,发送端根据此信息控制数据的发送量。
在这个过程中,接收端会发送
窗口更新通知
,告知发送端“我好了!”;为了防止此消息丢失,发送端也会一直试探性的询问接收端“能不能继续发了啊?”,即窗口探测
信息。 -
针对网络环境控制,通过
拥塞控制
机制。此机制针对的是在连接建立初期,如发送大量数据,很有可能会导致网络拥塞。此时,TCP中使用了一种叫做
慢启动
的算法,用以控制发送端发送数据的大小,即拥塞窗口
。其从1MSS开始,每收到一次ACK,其大小就加一。而真正的发送数据量小于min{拥塞窗口,接收端通知窗口}
。且采用了超时重发机制和高速重发机制的处理也有所不同,高速重发的拥堵会更轻一些。
同样,为了避免发送数据量过大,设置了慢启动阈值。超时重发的慢启动阈值为当前拥塞窗口的一半。而高速重发下,慢启动阈值的大小严格来说为“实际已发送但未收到确认应答的数据量”的一半。拥塞窗口的大小为慢启动阈值+3个数据段的大小。
其三,扩展内容——
- Nagle算法,用以延迟发送“发送端剩下的较少数量的数据”,发送的满足条件为:
- 已发送的数据都已收到确认应答时
- 可以发送MSS的数据时
- 延迟确认应答,用已处理如果每次都立刻回复ACK的话,可能会返回一个较小的窗口。情况有二:
- 没有收到
2xMSS
的数据为止,不做出ACK - 其他情况下,最大延迟0.5秒(很多OS设置为0.2秒左右)
- 没有收到
- 捎带应答,一些应用层数据的回执数据和TCP的ACK可以通过一个包发送,以减少收发数据量。此机制必须要结合延迟确认应答。
到此为止,TCP与UDP的内容就结束了,这次无论是正文内容还是小结,都是相当的长,希望各位有所收获。