爆锤TCP/UDP

TCP/UDP

TCP: 传输控制协议(英语:Transmission Control Protocol,缩写为TCP)是一种面向连接的、可靠的、基于字节流的传输层通信协议,由IETF的RFC 793定义。

UDP:用户数据报协议(英语:User Datagram Protocol,缩写为UDP),又称使用者资料包协定,是一个简单的面向数据报的传输层协议,正式规范为RFC 768。

前言:

关于TCP和UDP这两个协议是面试中非常常见的问题,本文讨论以下几个问题:

  1. 什么是TCP
  2. 什么是UDP
  3. TCP和UDP的异同点
  4. TCP和UDP的应用场景

1. TCP和UDP的异同点

1.1 相同点

  • UDP和TCP同属于传输层的协议

1.2 不同点

  1. TCP面向字节流,而UDP面向数据报。

解释:

TCP收发的是一堆数据,是一个数据流,而每次取多少由主机决定;

而UDP发的是数据报,客户发送多少就接收多少

  1. TCP是面向连接的,而UDP不是面向连接的。

解释:

TCP是面向连接的,也就是说,在连接持续的过程中,socket中收到的数据都是由同一台主机发出的,因此,知道保证数据是有序的到达就行了,至于每次读取多少数据自己看着办。

而UDP是无连接的协议,也就是说,只要知道接收端的IP和端口,且网络是可达的,任何主机都可以向接收端发送数据。

  1. TCP是可靠的,而UDP是不可靠的。

解释:

TCP为提供可靠性传输实行“顺序控制”或“重发控制”机制。此外还具备“流量控制”、“拥塞控制”、提高网络利用率等众多功能,并且只有确认通信端存在才会发送数据,从而可以控制通信流量的浪费。

在UDP的情况下,虽然可以确保发送信息的大小,却不能保证信息一定会到达。因此,应用有时会根据自己的需要进行重发处理。 UDP不提供复杂的控制机制,利用IP提供面向无连接的通信服务。它并不需要确认通信端是否存在,可以随时发送数据。

  1. TCP是全双工的,UDP支持多播和广播。

解释:

TCP是全双工的,所谓全双工就是数据在两个方向上同时进行传送操作,例如我们打电话,说话的同时也能够听到对方的声音,一旦两个通信端口简历TCP连接,那么两个端口只能一对一进行数据传输。

UDP因为不需要一对一建立连接,所以它可以做到一对一,一对多等传输方式,承担广播或者多播,虽然它不会建立连接,但是会监听这个端口,谁都可以给这个端口传数据,它也可以给任何人传数据。

  1. UDP 不处理堵塞,应用需要发,就会发送。TCP 还拥有堵塞控制,TCP 会根据网络环境调整发包的频率。
  2. TCP的传输效率低,UDP的传输效率高。

解释:因为UDP不用对收发的数据进行确认校验,有什么发什么,所以使得UDP的开销更小数据传输速率更高。

来一个直观的对比?:

爆锤TCP/UDP_第1张图片


造成TCP和UDP如此大的不同的原因是什么?想要理解TCP和UDP的区别,首先我们要明白什么是UDP,什么是TCP。

2. UDP协议解析

2.1 UDP包头

爆锤TCP/UDP_第2张图片

由上图可知,UDP包头的组成很简单,包含的字段有:

  • 目标端口号字段
  • 源端口号字段
  • UDP数据长度字段
  • 校验和字段

有用的也就源端口和目标端口,其中校验和字段是可选项,而TCP数据段中的校验和字段是必须有的。

包头结构的简单,也决定了UDP功能上的简单。

2.2 UDP特点

我们可以总结出以下UDP的特点:

  1. 它不需要大量的数据结构,处理逻辑和包头字段,也就说明它的通信方式很简单。
  2. UDP不会建立连接,但是会监听这个端口,这也决定了谁都可以向这个端口传输数据,并且它也可以传给任何人数据,甚至可以一对多传输(多播)。
  3. UDP并不会根据网络状况进行堵塞控制,也不会对包进行校验,该怎么发就怎么发。
  4. 因为UDP的简单,所以UDP较TCP被攻击者利用的漏洞就要少一些。

3 TCP协议解析

3.1 TCP包头

爆锤TCP/UDP_第3张图片由上图可知TCP包头较于UDP复杂很多,TCP包含的字段有:

  1. 源、目端口号:占16比特。TCP协议通过使用"端口"来标识源端和目标端的应用进程
  2. 顺序号字段:占32比特。用来标识从TCP源端向TCP目标端发送的数据字节流,它表示在这个报文段中的第一个数据字节。
  3. 确认号字段:占32比特。只有ACK标志为1时,确认号字段才有效。它包含目标端所期望收到源端的下一个数据字节。
  4. 首部长度字段:占4比特。给出头部占32比特的数目。没有任何选项字段的TCP头部长度为20字节;最多可以有60字节的TCP头部。
  5. 标志位字段(U、A、P、R、S、F):占6比特。各比特的含义如下:
    • URG:紧急指针(urgent pointer)有效。
    • ACK:确认序号有效。
    • PSH:接收方应该尽快将这个报文段交给应用层。
    • RST:重建连接。
    • SYN:发起一个连接。
    • FIN:释放一个连接。
  6. 窗口大小字段:占16比特。此字段用来进行流量控制。单位为字节数,这个值是本机期望一次接收的字节数。
  7. TCP校验和字段:占16比特。对整个TCP报文段,即TCP头部和TCP数据进行校验和计算,并由目标端进行验证。
  8. 紧急指针字段:占16比特。它是一个偏移量,和序号字段中的值相加表示紧急数据最后一个字节的序号。
  9. 选项字段:占32比特。可能包括"窗口扩大因子"、"时间戳"等选项。

3.2 TCP特点

因为有了较于UDP复杂了多的包头,也让TCP的功能多样化,例如:

  • 数据排序
  • 数据检测确认,数据重发
  • 连接维护
  • 恢复丢失数据
  • 流量控制和网络拥塞

3.3 TCP功能实现

3.3.1 三次握手

为什么TCP需要进行三次握手?

为了防止已失效的连接请求报文段突然又传送到了服务端,因而产生错误。—— 谢希仁《计算机网络》

简单来说,三次握手其实是为了同步序列号和确认号的相关信息,而序列号和确认号又是保障数据的正确性以及窗口滑动机制的最基本的根据。

那么为什么同步序列号需要三次握手?

举个例子说明:

大雄给静香寄了个水果包裹,并且通知了静香,但是因为种种原因,包裹并没有按照约定时间送达到静香手中,于是大雄又重新寄了个包裹过去又通知了静香,狗血的是,在第二个包裹到达之前,第一个包裹先到了,但由于没有相关同步的"序列号"和"确认号",所以静香没有向大雄确认这是哪一次的包裹,因为时间太久的原因包裹中的水果都烂了,导致了静香误会了大雄。

抽象来讲就是 A 给 B发送一个数据包,这个包由于网络的原因,很久才到 B 端,而这段时间,A 和 B 已经断开,并且重新建立了连接关系。没有相关同步序列号和确认号,B 端认为这个数据还是认为 A 这个时候要发给我的,但是其实这个数据上一次传输的数据,相当于这次传输的数据中插入了其他的数据,那么就会导致这次的数据出现异常。

那么我们就可以明白为什么不是两次握手❓

爆锤TCP/UDP_第4张图片

在上面一个过程中,对于大雄来说,他通知了静香寄了包裹,静香也回复了大雄收到了包裹,在A端实现了信息的一来一回,但是对于静香来说,她并没有向大雄确认这个包裹是哪一次送的包裹,导致了误会的发生。

要实现误会的消除,那么静香收到包裹的时候需要向大雄确定这是哪一次的包裹,也就是传说中的三次握手

爆锤TCP/UDP_第5张图片

由于增加了一次”握手“,让静香确认了包裹是第一次的还是第二次的,消除了大雄和静香之间的误会。

所以TCP 的三次握手除了建立连接外,主要还是为了沟通 TCP 包的序号问题,没有了三次握手,也就无法保证数据的时效性。

那么可以四次握手五次握手乃至一百次握手吗❓

理论上其实这个同步序列号和确认号的过程,大于三次也不是没有的,应该说几十次上百次都是可以的,但是三次握手的过程已经实现了序号数据同步,在进行太多次的序号同步,已经没有意义。无故浪费宽带。

实际的三次握手:

爆锤TCP/UDP_第6张图片

这是网上经常见到的关于三次握手的一张图,上图展示三次握手的流程图,文字描述过程如下:

符号意义:SYN(发起一个连接) 、seq(序列号)、ACK(确认序列号有效)、ack(确认号的值)

  1. 客户端向服务端发起一个连接。报文中包含了: SYN = 1, seq=x「序列号」的值, 发送之后客服端进入到 SYN-SENT 状态。
  2. 服务端接收到 SYN 和 seq「序列号」信息后,需要对这个报文进行的确认。服务端收到客户端发送过来的seq「序列号」的值,服务端会返回一个ack「确认号」的值(序列号+1)(静香询问大雄这是第几次发送的包裹)。同时返回 SYN = 1 表示还在同步阶段。 ACK = 1 表示确认号有意义。服务端发送之后,进入到SYN-RCVD状态。

此时-------序列号:表示服务端的序列号 y。 确认号:针对客户端发起连接的一个确认号。

  1. 客户端接收到服务端返回的数据,使得客户端这一边的相关通信建立,并且同步了相关序列号和确认号的数值,但是服务端还没有接收到客户端回复的确认号。所以客户端需要再发送一个数据到服务端,主要包含: ACK = 1,这是确认号的数据有意义(告诉静香包裹可用)。 SYN = 0 序列号已经同步完成,不需要再同步序列号。
  2. 序列号:因为 SYN 已经不是第一次同步序列号的信息了。这个时候的序列号,就表示的是一个单纯的基于序列号最新的数据包的序列号。其值为:x+1。 确认号:返回服务端序列号 y 的确认号,为 y+1。 发送这个值之后,客户端进入到ESTABLISHED状态,服务端接受到这个值之后也进入到ESTABLISHED 状态。然后就可以开始传递数据通信了。

至此三次握手已经完成。

3.3.2 四次挥手

三次握手可以让服务端和客户端建立连接,那么断开连接也需要一个过程,那就是四次挥手

TCP四次挥手过程:

爆锤TCP/UDP_第7张图片

  1. 任意一端( A 端)发出一条 FIN(释放连接) 的数据。数据包含: FIN = 1, 序列号 A 端发出这个数据之后,进入到 FIN - WAIT -1的状态,等待 B 端的回复.
  2. B 收到 A 端的 FIN 的信息就回复ACK=1,ack=seq+1,表示已经知道 A 端请求断开连接这个事情了。此时 B 端进入到 CLOSED - WAIT的状态。
  3. A 端接收到这个数据进入到FIN - WAIT -2 的状态。
  4. 然后返过来B 端给 A 端发起一次 FIN 的数据。请求结束连接,发送完成之后 B 端进入到LAST - ACK 状态。发送的数据包含: FIN = 1、ACK = 1、seq、ack
  5. A 端接收到这个 B 端发送的 FIN 数据进入到TIME- WAIT状态,同时回复 B 端,已经接收到了 FIN 数据。回复的包中包含: ACK = 1; ack
  6. 然后A端状态变为CLOSED状态,B端收到A端恢复的数据之后,也变为了CLOSED状态。

用例子说明就是:

A:B 啊,我不想玩了
B:哦,你不想玩了啊,我知道了
这个时候,只是 A 不想玩了,即不再发送数据,但是 B 可能还有未发送完的数据,所以需要等待 B 也主动关闭。
B:A 啊,好吧,我也不玩了,拜拜
A:好的,拜拜

断开的时候,当 A 说不玩了,就进入 FIN_WAIT_1 的状态,B 收到 A 不玩了的消息后,进入 CLOSE_WAIT 的状态。

A 收到 B 说知道了,就进入 FIN_WAIT_2 的状态,如果 B 直接跑路,则 A 永远处与这个状态。TCP 协议里面并没有对这个状态的处理,但 Linux 有,可以调整 tcp_fin_timeout 这个参数,设置一个超时时间。

如果 B 没有跑路,A 接收到 B 的不玩了请求之后,从 FIN_WAIT_2 状态结束,按说 A 可以跑路了,但是如果 B 没有接收到 A 跑路的 ACK 呢,就再也接收不到了,所以这时候 A 需要等待一段时间,因为如果 B 没接收到 A 的 ACK 的话会重新发送给 A,所以 A 的等待时间需要足够长这也是为什么需要TIME-WAIT这个状态。

TIME-WAIT这个状态:

  1. 保证TCP协议的全双工连接能够可靠关闭
  2. 保证这次连接的重复数据段从网络中消失

一、保证TCP协议的全双工连接能够可靠关闭 先说第一点,如果Client直接CLOSED了,那么由于IP协议的不可靠性或者是其它网络原因,导致Server没有收到Client最后回复的ACK。那么Server就会在超时之后继续发送FIN,此时由于Client已经CLOSED了,就找不到与重发的FIN对应的连接,最后Server就会收到RST而不是ACK,Server就会以为是连接错误把问题报告给高层。这样的情况虽然不会造成数据丢失,但是却导致TCP协议不符合可靠连接的要求。所以,Client不是直接进入CLOSED,而是要保持TIME_WAIT,当再次收到FIN的时候,能够保证对方收到ACK,最后正确的关闭连接。

二、保证这次连接的重复数据段从网络中消失 再说第二点,如果Client直接CLOSED,然后又再向Server发起一个新连接,我们不能保证这个新连接与刚关闭的连接的端口号是不同的。也就是说有可能新连接和老连接的端口号是相同的。一般来说不会发生什么问题,但是还是有特殊情况出现:假设新连接和已经关闭的老连接端口号是一样的,如果前一次连接的某些数据仍然滞留在网络中,这些延迟数据在建立新连接之后才到达Server,由于新连接和老连接的端口号是一样的,又因为TCP协议判断不同连接的依据是socket pair,于是,TCP协议就认为那个延迟的数据是属于新连接的,这样就和真正的新连接的数据包发生混淆了。所以TCP连接还要在TIME_WAIT状态等待2倍MSL,这样可以保证本次连接的所有数据都从网络中消失。

TCP 为什么进行是四次挥手,而不是三次?

断开比连接更复杂,比较直接的理解是资源回收比资源分配会更麻烦。使得所有资源能够有效并且不产生错误的情况下释放。

三次握手的本质是:将“四次握手”中的第二次、第三次握手合为一次,因为“四次握手”中的第二次、第三次握手都是由B向A传递报文,而且这两次发送报文的目的允许这两次报文合并为一次。那么,TCP四次挥手中的第二次、第三次挥手,能否也能合为一次呢?

答案是否定的。将TCP四次挥手中的第二次、第三次挥手,合为一次。也就是将CLOSE_WAIT状态的停留时间变为0。然而,B之所以存在CLOSE_WAIT状态,是因为B可能还存在着需要发送给A但是未发送的数据,如果存在着这些数据,那么这个状态的时间,就是用来发送这些数据的,所以,TCP四次挥手中的第二次、第三次挥手无法合并为一次。所以,也就无法实现“TCP三次挥手”。

3.3.3 累计确认

首先为了保证顺序性,每个包都有一个 ID。在建立连接的时候会商定起始 ID 是什么,然后按照 ID 一个个发送,为了保证不丢包,需要对发送的包都要进行应答,当然,这个应答不是一个一个来的,而是会应答某个之前的 ID,表示都收到了,这种模式成为累计应答累计确认

双方各自维护一个窗口以控制流量(rwnd),然后,发送方维护一个拥塞窗口以调节网络拥塞(cwnd)。

为了记录所有发送的包和接收的包,TCP 需要发送端和接收端分别来缓存这些记录,发送端的缓存里是按照包的 ID 一个个排列,根据处理的情况分成四个部分

  • 已发送,已经确认
  • 已发送,未确认
  • 未发送,可以发送
  • 未发送,不可发送

这里的第三部分和第四部分就属于流量控制的内容

接收方的 rwnd 主要是三个部分:

  • 接收并已确认的
  • 等待接收未确认的
  • 不能接收的

在 TCP 里,接收端会给发送端报一个窗口大小,叫 Advertised window。这个窗口等于发送端的已交代未做完的和马上要交代的,这个窗口应该等于发送端的第二部分加上第三部分,超过这个窗口,接收端做不过来,就不能发送了。

于是,发送端要保持下面的数据结构?

爆锤TCP/UDP_第8张图片

接收端锁对应的数据结构:

爆锤TCP/UDP_第9张图片

3.3.4 顺序和丢包问题

结合上面的图看,在发送端,1、2、3 已发送并已确认(ACK)的;4、5、6、7、8、9 都是发送了还没确认;10、11、12 是还没发出的;13、14、15 是接收方没有空间,不准备发的。

在接收端来看,1、2、3、4、5 是接受并且已确认的;6、7 是等待接收且未ACK的;8、9 是已经接收还没有 ACK 的。

发送端和接收端当前的状态如下?:

  • 1、2、3 没有问题,双方达成了一致
  • 4、5 接收方说 ACK 了,但是发送方还没收到回复
  • 6、7、8、9 ,发送方已经发出,但是如果 8、9 先到,6、7 没到,则出现了乱序,先缓存着但是没办法 ACK。

根据这个例子可以知道顺序问题丢包问题都有可能存在,所以我们先来看确认与重传机制

假设 接受端 4 的确认收到了,5 的 ACK 丢了,6、7 的数据包丢了,该怎么办❓

爆锤TCP/UDP_第10张图片

一种方法是超时重试即对每一个发送了但是没有 ACK 的包设定一个定时器,超过了一定的时间就重新尝试。这个时间必须大于往返时间,但也不宜过长,否则超时时间变长,访问就变慢了。

如果过一段时间,5、6、7 都超时了就会重新发送。接收方发现 5 原来接收过,于是丢弃 5;6 收到了,发送 ACK,要求下一个是 7,7 不幸又丢了。当 7 再次超时的时候,TCP 的策略是超时间隔加倍。每当遇到一次超时重传的时候,都会讲下一次超时时间间隔设为先前值的两倍。

超时重传的机制是超时周期可能相对较长,是否有更快的方式呢?

有一个快速重传的机制,即当接收方接收到一个序号大于期望的报文段时,就检测到了数据流之间的间隔,于是发送三个冗余的 ACK,客户端接收到之后,知道数据报丢失,于是重传丢失的报文段

例如,接收方发现 6、8、9 都接收了,但是 7 没来,所以肯定丢了,于是发送三个 6 的 ACK,要求下一个是 7。客户端接收到 3 个,就会发现 7 的确又丢了,不等超时,马上重发。

3.3.5 流量控制的问题

在流量控制的机制里面,在对于包的确认中,会携带一个窗口的大小

简单的说一下就是接收端在发送 ACK 的时候会带上缓冲区的窗口大小,但是一般在窗口达到一定大小才会更新窗口,因为每次都更新的话,刚空下来就又被填满了

3.3.6 拥塞控制的问题

也是通过窗口的大小来控制的,但是检测网络满不满是个挺难的事情,所以 TCP 发送包经常被比喻成往谁管理灌水,所以拥塞控制就是在不堵塞,不丢包的情况下尽可能的发挥带宽。

水管有粗细,网络有带宽,即每秒钟能发送多少数据;水管有长度,端到端有时延。理想状态下,水管里面的

水 = 水管粗细 * 水管长度。对于网络上,通道的容量 = 带宽 * 往返时延

如果我们设置发送窗口,使得发送但未确认的包为通道的容量,就能撑满整个管道。

爆锤TCP/UDP_第11张图片

如图所示,假设往返时间为 8 秒,去 4 秒,回 4 秒,每秒发送一个包,已经过去了 8 秒,则 8 个包都发出去了,其中前四个已经到达接收端,但是 ACK 还没返回不能算发送成功,5-8 后四个包还在路上,还没被接收,这个时候,管道正好撑满,在发送端,已发送未确认的 8 个包,正好等于带宽,也即每秒发送一个包,也即每秒发送一个包,乘以来回时间 8 秒。

如果在这个基础上调大窗口,使得单位时间可以发送更多的包,那么会出现接收端处理不过来,多出来的包会被丢弃,这个时候,我们可以增加一个缓存,但是缓存里面的包 4 秒内肯定达不到接收端,它的缺点会增加时延,如果时延达到一定程度就会超时重传

TCP 拥塞控制主要来避免两种现象,包丢失和超时重传,一旦出现了这些现象说明发送的太快了,要慢一点。

具体的方法就是发送端慢启动,比如倒水,刚开始倒的很慢,渐渐变快。然后设置一个阈值,当超过这个值的时候就要慢下来

慢下来还是在增长,这时候就可能水满则溢,出现拥塞,需要降低倒水的速度,等水慢慢渗下去。

拥塞的一种表现是丢包,需要超时重传,这个时候,采用快速重传算法,将当前速度变为一半。所以速度还是在比较高的值,也没有一夜回到解放前。

4 总结

TCP协议需要三次握手通信成功后进行建立,应用场景:互联网和企业网上客户端应用,数据传输性能让位于数据传输的完整性,可控制性和可靠性。
UDP协议是直接发送,不会判断是否接收和发送成功,应用场景:当强调输出性能而非完整性时,如音频和多媒体的应用。

TCP 为什么是可靠连接

  • 通过 TCP 连接传输的数据无差错,不丢失,不重复,且按顺序到达。
  • TCP 报文头里面的序号能使 TCP 的数据按序到达
  • 报文头里面的确认序号能保证不丢包,累计确认及超时重传机制
  • TCP 拥有流量控制及拥塞控制的机制

TCP 的顺序问题,丢包问题,流量控制都是通过滑动窗口来解决的
拥塞控制时通过拥塞窗口来解决的

参考资料:

  • 《图解TCP/IP》
  • https://blog.csdn.net/zhang6223284/article/details/81414149

————————————————————————————————————————————

版权声明:本文为吴恺的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://www.cnblogs.com/zaijianba/p/11537581.html
推荐至我的个人博客查看本文。
个人博客地址:www.wukailiving.cn
如有不足之处,欢迎指正!

转载于:https://www.cnblogs.com/zaijianba/p/11537581.html

你可能感兴趣的:(爆锤TCP/UDP)