计算机网络之:TCP和UDP

92 阅读21分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第9天,点击查看活动详情

本文是我之前在微信公众号上的一篇文章记录。原链接为:彻底搞懂系列之:TCP和UDP

不可置否,在当今互联网时代,TCP和UDP有着无可比拟的地位,可以说只要有网络传输就会用到它哥俩,这篇文章的目的就是要彻底搞懂这哥俩。

TCP

TCP介绍

传输控制协议(TCP,Transmission Control Protocol)是为了在不可靠的互联网络上提供可靠的端到端字节流而专门设计的一个传输协议。TCP是一种面向广域网的通信协议,目的是在跨越多个网络通信时,为两个通信端点之间提供一条具有下列特点的通信方式:

  • 面向连接 是指发送数据之前必须在两端建立连接。建立连接的方法是“三次握手”,这样能建立可靠的连接。建立连接,是为数据的可靠传输打下了基础。
  • 仅支持单播传输 每条TCP传输连接只能有两个端点,只能进行点对点的数据传输,不支持多播和广播传输方式。
  • 面向字节流 TCP不像UDP一样那样一个个报文独立地传输,而是在不保留报文边界的情况下以字节流方式进行传输。
  • 可靠传输 对于可靠传输,判断丢包,误码靠的是TCP的段编号以及确认号。TCP为了保证报文传输的可靠,就给每个包一个序号,同时序号也保证了传送到接收端实体的包的按序接收。然后接收端实体对已成功收到的字节发回一个相应的确认(ACK);如果发送端实体在合理的往返时延(RTT)内未收到确认,那么对应的数据(假设丢失了)将会被重传。
  • 提供拥塞控制 当网络出现拥塞的时候,TCP能够减小向网络注入数据的速率和数量,缓解拥塞。
  • TCP提供全双工通信 TCP允许通信双方的应用程序在任何时候都能发送数据,因为TCP连接的两端都设有缓存,用来临时存放双向通信的数据。当然,TCP可以立即发送一个数据段,也可以缓存一段时间以便一次发送更多的数据段。

为满足TCP协议的上面这些特点,TCP协议做了如下的规定:

  • 数据分片:在发送端对用户数据进行分片,在接收端进行重组,由TCP确定分片的大小并控制分片和重组;
  • 到达确认:接收端接收到分片数据时,根据分片数据序号向发送端发送一个确认;
  • 超时重发:发送方在发送分片时启动超时定时器,如果在定时器超时之后没有收到相应的确认,重发分片;
  • 滑动窗口:TCP连接每一方的接收缓冲空间大小都固定,接收端只允许另一端发送接收端缓冲区所能接纳的数据,TCP在滑动窗口的基础上提供流量控制,防止较快主机致使较慢主机的缓冲区溢出;
  • 失序处理:作为IP数据报来传输的TCP分片到达时可能会失序,TCP将对收到的数据进行重新排序,将收到的数据以正确的顺序交给应用层;
  • 重复处理:作为IP数据报来传输的TCP分片会发生重复,TCP的接收端必须丢弃重复的数据;
  • 数据校验:TCP将保持它首部和数据的检验和,这是一个端到端的检验和,目的是检测数据在传输过程中的任何变化。如果收到分片的检验和有差错,TCP将丢弃这个分片,并不确认收到此报文段导致对端超时并重发。

TCP Header

一般来说,一个数据包免不了要增加控制信息,也就是常常说的头部信息,用来记录数据包的各种特性,TCP数据包也一样,由一个头部信息,一般一个TCP Header有20个字节,如果启用了options,header的长度可以达到60个字节。下图中每一行是4个bytes,32个bits。下面我们主要学习前五行,也就是一般情况的20个字节: TCP Header

  • 第一行,由Source port和Destination port构成,二者各占2个字节,刚好一起占据第一行的4个字节。这两个字段分别表示TCP连接中的发送方端口号和接收方的端口号,大家都知道端口值的范围是0~65535,所以各占2个字节啦。
  • 第二行,Sequence number表示发送方的序列号。这个序列号表示的是什么呢?一个TCP流是有无数个0和1构成,这些0和1以8个bit为单位(8 bit=1 byte),可以分割成一个个的 byte,TCP 是可靠传输协议,每一个 byte 都是有标号的,因为我们需要追踪每个 byte 是否被成功传输了,每个 byte 的标号就是我们这里的 sequence number。假设我们建立 TCP 连接的时候,发一个 SYN 包,我们就以 0 标记 sync 包的第一个字节,那么 sync 包中的 Sequence 值就是 0。实际应用中,处于安全考虑,TCP 流的第一个 Sequence number 一般不会是 0,而是一个随机数。Sequence number占据4个字节,也就是 2 的 32 次方,这个数字并不算大,每个包都会用掉一些,如果达到最大值之后,就取余从0重新开始。
  • 第三行,Acknowledge number,表示接收方ack的序列号。接收方收到发送方一个的 TCP 包之后,取出其中的sequence number,在下一个接收方自己要发送的包中,设置ACK比特位为1,同时设置acknowledge number为 sequence number + 1。所以接收方的acknowledge number表示的是,接收方期待接收的下一个包起始字节的标号,大家可以仔细理解下这一句话。所以acknowledge number和sequence number是配对使用的。
  • 第四行,这一行尤其重要,这里简单提下从CWR到FIN的8个bit,这8个bit里每一位都是一个标记位,用来标记当前TCP包的特殊含义。比如我们所说的三次握手,第一个SYN包,就是将SYN位置为1。第二个SYN+ACK包就是将header的ACK和 SYN位都置为1。第三个ACK包即将ACK位置1。剩余的几个bit位暂时不展开讲了,表示的意思是:SYN(synchronous建立连接),FIN(finish关闭连接),ACK(acknowledgement响应),PSH(push数据传输),RST(reset重置连接),URG(urgent紧急)。后面的是16bit的窗口大小(window size),它是TCP流量控制的一个手段。这里说的窗口,指的是接收数据的窗口(Receiver Window,RWND)。它告诉对方本端的TCP接收缓冲区还能容纳多少字节的数据,这样对方就可以控制发送数据的速度。
  • 第五行,这一行只有两个字段,即Checksum和Urgent pointer。checksum 是个通用的计算机概念,做完整性校验之用,在很多协议(IP,UDP,ICMP)中都有应用,这个值由包的发送方去计算,之后由包的接收方取出来进行校验。Urgent pointer为两个字节的偏移量,加上当前包的sequence number,用来标记某一个范围内的bytes为特殊用途数据。

TCP三次握手

既然是端到端的可靠传输,那就必须要建立一个稳定的连接,TCP建立连接的过程通常叫“三次握手”的过程:

  • 第一次握手:客户端将SYN标志位置为1(表示请求连接),随机生成一个seq number=J,并将该数据包发送给服务器,并进入SYN_SENT状态,等待服务器确认;
  • 第二次握手:服务器收到数据包后由SYN=1知道这是一个建立连接的请求,那么服务器就将SYN和ACK标志位都置为1(表示这是一个请求连接的响应包),然后确认客户端的seq号码,也就是将ack number=J+1(客户端发送的seq number+1,ack number必须是ACK标志位为1时才有效),同时自己也随机生成一个seq number=K,将此数据包发回给客户端,此时服务器进入SYN_RECV状态;
  • 第三次握手:客户端收到服务器的连接响应包后,检查ACK标志位是否为1,检查ack number是否为J+1,如果都正确则将ACK标志位置1和ack number=K+1数据包发回给服务器。服务器收到数据包后检查ACK标志位是否为1,检查ack number是否为K+1,如果都正确则表示连接建立成功,客户端和服务器进入ESTABLISHED状态,完成三次握手。 TCP connect

握手过程中传送的包里不包含数据,三次握手完毕后,客户端与服务器才正式开始传送数据。理想状态下,TCP连接一旦建立,在通信双方中的任何一方主动关闭连接之前,TCP连接都将被一直保持下去。

TCP四次挥手

TCP“三次握手”已经搞懂了,当然也还要搞懂断开连接的过程,与建立连接的“三次握手”类似,断开一个TCP连接则需要“四次挥手”。断开TCP连接的请求发起方可以是客户端也可以是服务器。

  • 第一次挥手:主动关闭方发送一个FIN,用来关闭主动方到被动关闭方的数据传送,也就是主动关闭方告诉被动关闭方:我已经不会再给你发数据了(当然,在FIN包之前发送出去的数据,如果没有收到对应的ack确认报文,主动关闭方依然会重发这些数据),但是,此时被动方还在正常发送数据,所以主动关闭方还可以接收数据。
  • 第二次挥手:被动关闭方收到FIN包后,发送一个ACK给对方,确认序号为收到序号+1(与SYN相同,一个FIN占用一个序号),这个只是告诉主动关闭方你的请求我收到了,但是我这边还有数据发送还没有准备好,请继续等我的消息。
  • 第三次挥手:当被动关闭方确认数据已经发送完毕,被动关闭方就会发送一个FIN给主动关闭方,用来关闭被动关闭方到主动关闭方的数据传送,也就是告诉主动关闭方,我的数据也发送完了,不会再给你发数据了,这时,被动关闭方进入LAST_ACK状态。
  • 第四次挥手:主动关闭方收到FIN后(这是被动关闭方的最后一个包),就知道连接可以彻底关闭了,但是主动方还是不太相信网络,它担心被动关闭方还不知道要关闭连接,所以发送一个ACK给被动关闭方,确认序号为收到序号+1,至此,主动关闭方进入TIME_WAIT状态,被动关闭方收到ACK后就知道可以最终关闭连接了,主动关闭方等待一定时间后没有收到任何回复就知道被动关闭方已经断开连接了,那它自己也就关闭连接了。至此,完成四次挥手。 TCP close

TCP如何保证可靠性

确认应答和序列号

在TCP中,当发送端的数据到达接收端时,接收端回返回一个已收到的消息给发送端,这个消息就是确认应答(ACK),如果一定时间内没有收到应答,则认为数据已经丢失,发送端会进行数据重发。因此,即使产生了丢包,仍然能够保证数据可靠的传达。 当然未收到应答消息也不一定意味着数据丢失,有可能是数据对方已经收到,但是返回的应答消息丢失了。这种情况也会导致发送端以为是数据丢失而重发数据。 此外,还有可能是因为一些其他原因导致确认应答延迟到达,在发送端重发数据之后到达的情况也经常发生。 以上这些重发势必会导致接收端收到重复数据,对此,接收端必须放弃重复的数据包,因此序列号的重要性就体现出来了。 序列号是按照顺序给发送数据的每一个字节都标上一个号码。接收端查询接收到的数据包中头部中的序列号和数据长度来确定下一个应该接收的数据包序列号,并以此序列号作为确认应答返回给发送端。TCP能够识别到是否已经接收数据,又能判断是否需要接收,从而实现了可靠传输。

超时重传

那么上面提到的等待应答超时是怎么计算的呢?如果这个等待的时间过长,那么会影响TCP传输的整体效率,如果等待时间过短,又会导致频繁的发送重复的包。如何权衡? 由于TCP传输时保证能够在任何环境下都有一个高性能的通信,因此这个最大超时时间(也就是等待的时间)是动态计算的。

一般情况下,超时是以500ms为一个单位进行控制,每次判定超时重发的超时时间都是500ms的整数倍。重发一次后,仍未响应,那么等待2*500ms的时间后,再次重传。等待4*500ms的时间继续重传。以一个指数的形式增长。累计到一定的重传次数,TCP就认为网络或者对端出现异常,强制关闭连接。

流量控制

接收端在接收到数据后,对其进行处理。如果发送端的发送速度太快,导致接收端的结束缓冲区很快的填充满了。此时如果发送端仍旧发送数据,那么接下来发送的数据都会丢包,继而导致丢包的一系列连锁反应,超时重传呀什么的。

TCP能够根据接收端对数据的处理能力,决定发送端的发送速度,这个机制就是流量控制。在TCP协议的报头信息当中,有一个16位字段的窗口大小。在介绍这个窗口大小时我们知道,窗口大小的内容实际上是接收端接收数据缓冲区的剩余大小。这个数字越大,证明接收端接收缓冲区的剩余空间越大,网络的吞吐量越大。接收端会在确认应答发送ACK报文时,将自己的即时窗口大小填入,并跟随ACK报文一起发送过去。而发送方根据ACK报文里的窗口大小的值的改变进而改变自己的发送速度。如果接收到窗口大小的值为0,那么发送方将停止发送数据。并定期的向接收端发送窗口探测数据段,让接收端把窗口大小告诉发送端。

拥塞控制

TCP传输的过程中,发送端开始发送数据的时候,如果刚开始就发送大量的数据,那么就可能造成一些问题。网络可能在开始的时候就很拥堵,如果给网络继续发送大量数据,那么这个拥堵就会加剧。拥堵的加剧就会产生大量的丢包,就对大量的超时重传,严重影响传输。

所以TCP引入了慢启动的机制,在开始发送数据时,先发送少量的数据探路。探清当前的网络状态如何,再决定多大的速度进行传输。这时候就引入一个叫做拥塞窗口的概念。发送刚开始定义拥塞窗口为1,每次收到ACK应答,拥塞窗口加1。在发送数据之前,首先将拥塞窗口与接收端反馈的窗口大小比对,取较小的值作为实际发送的窗口。

拥塞窗口的增长是指数级别的。慢启动的机制只是说明在开始的时候发送的少,发送的慢,但是增长的速度是非常快的。为了控制拥塞窗口的增长,不能使拥塞窗口单纯的加倍,设置一个拥塞窗口的阈值,当拥塞窗口大小超过阈值时,不能再按照指数来增长,而是线性的增长。在慢启动开始的时候,慢启动的阈值等于窗口的最大值,一旦造成网络拥塞,发生超时重传时,慢启动的阈值会为原来的一半(这里的原来指的是发生网络拥塞时拥塞窗口的大小),同时拥塞窗口重置为1。

拥塞控制是TCP在传输时尽可能快的将数据传输,并且避免拥塞造成的一系列问题。是可靠性的保证,同时也是维护了传输的高效性。

校验和

在数据传输的过程中,将发送的数据段都当做一个16位的整数。将这些整数加起来。并且前面的进位不能丢弃,补在后面,最后取反,得到校验和。 发送方:在发送数据之前计算检验和,并进行校验和的填充在TCP Header中。 接收方:收到数据后,对数据以同样的方式进行计算,求出校验和,与发送方的进行比对。 如果接收方比对校验和与发送方不一致,那么数据一定传输有误,需要重传。

UDP

UDP介绍

UDP协议全称是用户数据报协议(User Datagram Protocol),在网络中它与TCP协议一样用于处理数据包,是一种无连接的协议。在OSI模型中,在第四层——传输层,处于IP协议的上一层。UDP有不提供数据包分组、组装和不能对数据包进行排序的缺点,也就是说,当报文发送之后,是无法得知其是否安全完整到达的。

它有以下几个特点:

  • 面向无连接 首先 UDP 是不需要和 TCP一样在发送数据前进行三次握手建立连接的,想发数据就可以开始发送了。并且也只是数据报文的搬运工,不会对数据报文进行任何拆分和拼接操作。 具体来说就是:在发送端,应用层将数据传递给传输层的UDP协议,UDP只会给数据增加一个UDP头标识下是UDP协议,然后就传递给网络层了。在接收端,网络层将数据传递给传输层,UDP只去除IP报文头就传递给应用层,不会任何拼接操作。
  • 有单播,多播,广播的功能 UDP不止支持一对一的传输方式,同样支持一对多,多对多,多对一的方式,也就是说UDP提供了单播,多播,广播的功能。
  • UDP是面向报文的 发送方的UDP对应用程序交下来的报文,在添加首部后就向下交付IP层。UDP对应用层交下来的报文,既不合并,也不拆分,而是保留这些报文的边界。因此,应用程序必须选择合适大小的报文。
  • 不可靠性 首先不可靠性体现在无连接上,通信都不需要建立连接,想发就发,这样的情况肯定不可靠。并且收到什么数据就传递什么数据,并且也不会备份数据,发送数据也不会关心对方是否已经正确接收到数据了。再者网络环境时好时坏,但是UDP因为没有拥塞控制,一直会以恒定的速度发送数据。即使网络条件不好,也不会对发送速率进行调整。这样实现的弊端就是在网络条件不好的情况下可能会导致丢包,但是优点也很明显,在某些实时性要求高的场景(比如电话会议)就需要使用UDP而不是TCP。

UDP Header

UDP首部有8个字节,由4个字段构成,每个字段都是两个字节,如下图: UDP header

  • 源端口: 主机的应用程序使用的端口号。
  • 目标端口:目的主机的应用程序使用的端口号。
  • 长度:是指UDP头部和UDP数据的字节长度。因为UDP头 部长度为8字节,所以该字段的最小值为8。
  • 校验和:检测UDP数据报在传输中是否有错,有错则丢弃。

UDP提高可靠性

UDP它不属于连接型协议,因而具有资源消耗小,处理速度快的优点,所以通常音频、视频和普通数据在传送时使用UDP较多,因为它们即使偶尔丢失一两个数据包,也不会对接收结果产生太大影响。传输层无法保证数据的可靠传输,只能通过应用层来实现了。实现的方式可以参照tcp可靠性传输的方式,只是实现不在传输层,实现转移到了应用层。

关键在于两点,从应用层角度考虑:

  • 提供超时重传,能避免数据报丢失。
  • 提供确认序列号,可以对数据报进行确认和排序。

本端:首先在UDP数据报定义一个首部,首部包含确认序列号和时间戳,时间戳是用来计算RTT(数据报传输的往返时间),从何计算出合适的RTO(重传的超时时间)。然后以等-停的方式发送数据报,即收到对端的确认之后才发送下一个的数据报。当时间超时,本端重传数据报,同时RTO扩大为原来的两倍,重新开始计时。

对端:接受到一个数据报之后取下该数据报首部的时间戳和确认序列号,并添加本端的确认数据报首部之后发送给对端。根据此序列号对已收到的数据报进行排序并丢弃重复的数据报。

TCP与UDP适用场景

TCP一般是用在那些效率要求相对低,但对准确性要求相对高的场景。因为传输中需要对数据确认、重发、排序等操作,相比之下效率没有UDP高。比如文件传输(准确高要求高、但是速度可以相对慢)、接受邮件、远程登录。

UDP协议一般作为流媒体应用、语音交流、视频会议、QQ聊天等所使用的传输层协议,还有许多基于互联网的电话服务使用的VOIP(基于IP的语音)也是基于UDP运行的,实时视频和音频流协议旨在处理偶尔丢失的数据包,因此,如果重新传输丢失的数据包,则只会发生质量略有下降,而不是出现较大的延迟。

我们大家都知道的DNS 协议底层也使用了UDP协议,这些应用或协议之所以选择UDP主要是因为以下这几点:

  • 速度快,采用UDP协议时,只要应用进程将数据传给UDP,UDP就会将此数据打包进UDP报文段并立刻传递给网络层,然而TCP有拥塞控制的功能,它会在发送前判断互联网的拥堵情况,如果互联网极度阻塞,那么就会抑制TCP的发送方。使用UDP的目的就是希望实时性。
  • 无须建立连接,TCP在数据传输之前需要经过三次握手的操作,而UDP则无须任何准备即可进行数据传输。因此UDP没有建立连接的时延。
  • 无连接状态,TCP需要在端系统中维护连接状态,连接状态包括接收和发送缓存、拥塞控制参数以及序号和确认号的参数,在UDP中没有这些参数,也没有发送缓存和接受缓存。因此,某些专门用于某种特定应用的服务器当应用程序运行在UDP 上,一般能支持更多的活跃用户。
  • 分组首部开销小,每个TCP报文段都有20字节的首部开销,而UDP仅仅只有8字节的开销。
参考资料

TCP/IP 系列之 Header 篇

TCP IP

UDP协议详解