计算机网络 (三) 传输层 :一文搞懂UDP与TCP协议


传输层是整个网络体系结构中的关键层次之一,主要负责两个主机中进程之间的数据传输。
在传输层中常见的协议就是TCP协议(传输控制协议),UDP协议(用户数据报协议)

UDP

UDP协议,即用户数据报协议


UDP的协议格式

在这里插入图片描述

  • 16位源端端口/目的端口:表示源端/对端的端口号,源端端口有时候可以不设置(不关心通信的端口),可以设置为0。
  • 16位数据报长度:标志UDP首部与发送数据的长度之和,大小为2^16,即64K,65535。
  • 16位校验和:用于检验接收的数据与发送的数据是否一致,不一致则丢弃。校验方法:二进制反码求和,即对报文从头开始的每个字节进行取反相加,高出16位则截断高位,与低16位相加,得到校验和。

UDP的特点

其主要特点为:无连接,不可靠,面向数据报

  • 无连接
    即不需要建立连接也可以发送数据。只需要知道对端的端口号和ip地址就可以直接传输。就比如寄快递,只需要知道人家的地址就可以送过去,不需要与别人提前建立联系
  • 不可靠
    即不能保证数据安全有序的到达对端。因为UDP不存在重传机制与确认机制,所以并不能保证数据能够到达对端,可能会在途中出现丢包,即使丢包了也不会报错和重传。UDP的报头中不存在序号,并且是无连接的,所以他没法保证数据到达后的顺序,需要我们自己在应用层进行包序管理。
  • 面向数据报
    即不能灵活的控制读写次数与数据的长度,是一种限制了传输数据大小的传输方式。并且UDP的数据传输是整条的,不会拆分和合并数据

面向数据报:
数据报长度字段只有16位,报头要占8个字节,所以数据报的长度不能大于(64K- 8) 。
UDP给应用层传下来的报文添加首部后就会直接转交给网络层,所以对于较大的数据,需要我们自己在应用层进行分包和包序管理进行多次发送。

UDP在报头中定义了数据长度,所以传输的时候都是整条收发。
所以接收时缓冲区必须要足够大,如果缓冲区大于或者小于一条数据的大小时都会接收失败,因为UDP不会交付半条或者多条数据。


基于UDP的应用层知名协议

  • NFS: 网络文件系统
  • TFTP: 简单文件传输协议
  • DHCP: 动态主机配置协议
  • BOOTP: 启动协议(用于无盘设备启动)
  • DNS: 域名解析协议

UDP如何实现可靠传输

如果光光依靠UDP本身是无法实现可靠传输的,因为其无法保证数据有序且到达,所以可以参考TCP的可靠性机制,在应用层也为其引入类似逻辑
例如

  1. 引入序列号,保证数据有序
  2. 确认应答机制,保证对端能够收到数据
  3. 引入超时重传机制,保证数据不会丢失

TCP

TCP协议,即传输控制协议


TCP的协议格式

在这里插入图片描述

  • 16位源端端口/目的端口:表示源端/对端的端口号,源端端口有时候可以不设置(不关心通信的端口),可以设置为0。
  • 32位序号:序号是指发送数据的位置。每发送一次数据,就累加一次该数据字节数的大小。序号不会从0或1开始,而是在建立连接时由计算机生成的随机数作为其初始值,通过SYN包传给接收端主机。
  • 32位确认序号:确认序号是指下一次应该收到的数据的序列号。实际上,它是指已收到确认序号减一为止的数据。发送端接收到这个确认序号以后可以认为在这个序号以前的数据都已经被正常接收。
    TCP通过序号和确认序号来实现包序管理,确保TCP数据是有序交付的。
  • 4位数据偏移(首部长度):表示TCP首部的长度,单位为4字节即32位,因为数据偏移具有4位,所能表示的最大数据为15即2^4 - 1,所以TCP首部的最大长度为15 * 4 = 60字节,因为数据偏移最少为5(除选项),所以TCP首部的最小长度为20字节。
  • 6位保留位:保留为今后使用,一般设置为 0。
  • 6位标志位:有六种标志位,用来描述本报文的性质

URG(Urgent Flag):该为为1时,表示包中有需要紧急处理的数据。对于需要紧急处理的数据,会在后面的紧急指针中再进行解释。
ACK(Acknowledge Flag):该位为1时,确认应答的字段变为有效。TCP规则除了最初建立时的SYN包之外该为必须设置为1。
PSH(Push Flag):该位为1时,表示需要将受到的数据立即传输给上层的应用。PSH为0时,则不需要立即上层而是先进行缓存。
RST(ResetFlag):该位为1时表示TCP连接中出现异常必须强制断开连接。例如,一个没有被使用的端口即使发来连接请求,也无法通行。此时就可以返回一个RST设置为1的包。此外,程序宕掉或切断电源等原因导致主机重启的情况下,由于所有的连接信息将全部被初始化,所以原有的TCP通行也将不能继续进行。这种情况下,如果通信对方发送一个设置为1的RST包,就会使用心强制断开连接。
SYN(Synchronize Flag): 用于建立连接。SYN为1表示希望建立连接,并在其序列号的字段进行序列号初始值的设定。
FIN(Finish Flag):该位为1时,表示今后不会再有数据发送,希望断开连接。当通信结束希望断开连接时,通信双方的主机之间就可以相互交换FIN位置为1的TCP段。每个主机又对对方的FIN包进行确认应答以后就可以断开连接。不过,主机收到FIN设置为1的TCP端以后不必马上回复一个FIN包,而是可以等到缓冲区中所有数据都已成功发送而被自动删除之后再发。

  • 16位窗口大小:指定滑动窗口的大小,即从TCP首部的确认序号所指位置开始能够接收的数据大小,TCP不允许发送超过此处所示大小的数据。用于实现滑动窗口机制,来进行流量控制
  • 16位校验和:用于检验接收的数据与发送的数据是否一致,不一致则丢弃。校验方法:二进制反码求和,即对报文从头开始的每个字节进行取反相加,高出16位则截断高位,与低16位相加,得到校验和。
  • 16位紧急指针: 标识哪部分数据是紧急数据(带外数据),这段数据的优先级更高,会提前传送
  • 0-40字节选项:选项字段用于提高TCP的传输性能,主要协商和描述一些信息。因为TCP首部大小最高为60字节,而前面必须的有20字节,所以选项的大小可以为0-40字节。
    在这里插入图片描述
  • 填充位:保证TCP首部大小为4字节的整数倍,不够则填充

TCP的特点

其主要特点为:面向连接,可靠,面向字节流。

  • 面向连接:通信必须在建立连接之后,通过连接管理机制实现。
  • 可靠传输:保证数据安全有序的到达对端
  • 面向字节流:以字节流的方式传输

连接管理机制

TCP通过首部的标志位来实现连接的管理,完成通信。
通常情况下,需要经过三次握手来建立连接,四次挥手来断开连接
这里我简单的画了一个图
在这里插入图片描述

三次握手

在这里插入图片描述
开始时,客户端处于CLOSED状态,服务端处于CLOSED状态。
之后服务端进入LISTEN状态,开始监听(只有处于监听状态才会处理连接)。

第一次握手(客户端向服务端挥手):客户端向服务端发送一个SYN请求建立连接,客户端转为SYN_SENT状态。
第二次握手(服务端向客户端挥手):服务端一旦监听到连接请求,就会转为SYN_RECV状态,并且向客户端发送一个SYNACK来告诉客户端我已经收到了你的SYN连接请求,并且也发起连接。
第三次握手(客户端向服务端挥手):客户端收到后转为ESTABLISHED状态。并且向服务端发送一个ACK给服务端,告知服务端我也收到了你的SYN请求,服务端收到后也进入ESTABLISHED状态。

简单点来说:
第一次握手:客户端:在吗,我是XX,你听到我说话了吗?
第二次握手:服务端:我听到你说话了,你听到我说话了吗?
第三次握手:客户端:我也听到你说话了,既然我们互相都能听到,就开始通信吧。


四次挥手

在这里插入图片描述
第一次挥手(客户端向服务端挥手):客户端向服务端发送一个FIN请求断开连接,客户端处于FIN_WAIT_1状态。发送FIN表示发送端不再发送数据,但不代表不接收数据。
第二次挥手(服务端向客户端挥手):服务端向哭护短回复一个ACK确认收到了FIN。并且转为CLOSE_WAIT状态(此时服务端已经不再接受数据(关闭读操作),但是还会继续发送,所以此时会等待上层程序处理)
第三次挥手(服务端向客户端挥手):此时服务端也完成了写操作,所以向服务端发送一个FIN请求断开连接,并且进入LASK_ACK状态。
第四次挥手(客户端向服务端挥手):客户端收到了服务端的FIN,并且向服务端回复一个ACK表示已经收到。同时客户端进入TIME_WAIT状态。服务端收到ACK后断开连接进入CLOSED状态,客户端也进入CLOSED状态。

简单来说
第一次挥手:客户端:我要说的话已经说完了。
第二次挥手:服务端:你要说的我都听到了,但是我的话还没说完。
第三次挥手:服务端:我要说的话也说完了。
第四次挥手:客户端:既然我们都说完了,那就结束通话吧。


保活机制

在TCP通信中,如果两端长时间没有数据往来(默认7200秒),则每隔一段时间(默认75秒),服务端就会向客户端发送一个保活探测数据报,让客户端进行回复,如果多次(默认9次)没有收到响应,则代表连接已经断开。

通过保活机制来确保如果有一端断开,能够及时处理。


问题补充

TCP握手为什么三次,能不能两次或者四次
两次不安全,四次没必要。SYN的目的是为了确认对方是否具有收发数据的能力,并且得到ACK回复后,则证明对方收到数据并且当前在线。如果要建立连接,就必须确保双方都具有收发数据的能力并且当前处于在线状态。

为什么不能两次?
如果只有两次就能建立连接,那就代表着客户端发起SYN连接,服务端确认后回复ACK+SYN就直接建立连接。
1.如果客户端连续发送多次请求,服务端就会建立多次连接,严重的浪费资源。
2.如果客户端发起SYN请求后就断开,或者是因为延迟很久才发到,等服务端收到时客户端已经断开连接,这时这个连接是失败的,但是服务端还是创建了一个毫无意义的套接字,严重浪费了资源。

为什么不用四次?
四次握手完全没有必要,建立连接的SYN和确认回复的ACK报文是可以一起发送的,没有必要分开来增加操作。


TCP挥手为什么要四次,三次可以吗?
不行,发送FIN包只能代表着主动关闭防不再发送数据,但不代表着不再接受数据,所以被动关闭方在回复ACK后还是有可能继续发送数据,等到被动关闭方所有数据发送完后才会发送FIN,主动关闭方收到后回复ACK才会断开连接。这也就是为什么建立连接时ACK可以和SYN一起发送,而断开连接时FIN不能和ACK一起发送的原因。


TCP三次握手失败时,服务端如何处理?
1.如果服务端没有收到SYN,则什么都不做(因为压根没有建立起来连接,出现情况可能是SYN丢失)
2.服务端发送了SYN和ACK后,没有收到客户端的ACK,此时则说明客户端可能不在线,此时发送一个RST重置连接,并且释放已有资源。


TIME_WAIT有什么用?
在TIME_WAIT状态时,主动关闭方没有直接调用close释放资源,而是等到确保被动关闭方收到ACK确认后调用close,主动关闭方才调用。

如果没有TIME_WAIT,主动关闭方直接再发送完ACK后断开连接,就会有如下几种情况。

  • 被动关闭方没有close释放资源,则新启用的客户端就有可能会出现和原来的客户端具有一样的地址信息
  • 主动关闭方发送的ACK丢失,被动关闭方没有收到这个ACK,就会卡在LACK_ACK状态,所以超时后被动关闭方会重传一个FIN。

这时就会有两种情况
1.刚启用的新客户端绑定地址信息成功,但是此时却收到了被动关闭方重传的FIN,对新连接产生影响。
2.如果新启用的客户端向服务端发送SYN连接,但是此时服务端处于LACK_ACK状态,此时他需要的是ACK而不是SYN,所以他会发送一个RST来重置连接。

所以为了避免上述几种情况,就添加了TIME_WAIT,等待一段时间确保被动关闭方收到ACK,即使没有收到,也留有了足够的时间来给被动关闭方进行FIN的重传。

等待的时间:默认为两个MSL时间(报文最大生存时间)


一台主机上出现大量的TIME_WAIT是什么原因?如何处理?
TIME_WAIT状态是出现在主动关闭方的,如果出现大量的TIME_WAIT,就说明有大量的连接被主动关闭,可能是恶意攻击或者爬虫等。处理方法,调整TIME_WAIT的等待时间或者开启地址重用(运行新的套接字使用已绑定的地址端口,因为TIME_WAIT中的套接字无法绑定,启用后就可以直接顶替掉原来的)。


一台主机上出现大量的CLOSE_WAIT是什么原因?如何处理?
CLOSE_WAIT状态是在被动关闭方收到主动关闭方的FIN后进入的,此时主动关闭方已经不再发送数据,所以此时被动关闭方的读端已经被关闭,但是被动关闭方还需要等到他所有的数据发送完才会结束,所以此时被动关闭方会等待上层应用进行处理,处理结束后才会发送FIN。而如果出现大量的CLOSE_WAIT,则说明被动关闭方的上层应用处理有问题,没有正确的关闭SOCKET释放资源,所以此时就不会发送FIN,导致一直卡在CLOSE_WAIT。


可靠传输

TCP与UDP不一样,他能够确保数据安全有序的到达对端。
通过以下方式

  1. 面向连接
  2. 确认应答机制(确保对端是否收到数据,并且根据序号使数据有序)
  3. 超时重传机制(确保数据丢失后能够重传)
  4. TCP首部报文中的序号和确认序号(确保数据有序)
  5. TCP首部报文中的校验和(确保收发数据一致,不一致则要求重传)

确认应答

TCP在首部中给每一个发送的数据都设置了序号和确认序号。
通过序号和确认序号,发送方告诉了接收方我发送的数据的序号,以及数据的长度。而接收方则回复发送方,我已经收到了哪些数据,你下一次应该从哪一个位置开始发送。

seq:本条数据的起始序号。为上一条数据的ack
ack:对对方发送数据的确认序号,告诉对方这个位置之前的所有数据已收到。确认序号为本条数据的起始序号加上数据长度。也就是上一条的seq + len
len:本条数据的长度。

这里简单的画个图
在这里插入图片描述
在这里插入图片描述
如果在传输过程中,前面的某一个数据丢失,即使这里的1025-2048和2049-3072已经到达,但是这些数据的依旧无法确认回复,因为确认回复必须要保证确认序号ack前的所有数据都要到达这么做的目的是为了防止因为确认回复的丢失导致的重传。
在这里插入图片描述
即使是因为丢包或者延迟而导致前面的数据晚到或重传,等到接收到重传的数据后,再一个个对之前发送的数据进行确认应答,并通过序号在接受缓冲区中进行排序。


超时重传

在通信时,某一主机在给另一个主机发送数据后,可能会因为延迟或者丢包等网络原因,导致数据不能到达,为了确保数据能够到达对端,TCP加入了超时重传机制,当发送端在特定的时间内没有收到来自接收端的确认应答时,就会重新发送,
在这里插入图片描述
同时,不仅发送的数据会丢失,确认应答也有可能丢失,这时也会重传。
在这里插入图片描述
这个时间间隔也需要合理设置

最理想的情况下, 找到一个最小的时间, 保证 “确认应答一定能在这个时间内返回”.
但是这个时间的长短, 随着网络环境的不同, 是有差异的
如果超时时间设的太长, 会影响整体的重传效率;
如果超时时间设的太短, 有可能会频繁发送重复的包

TCP为了保证无论在任何环境下都能比较高性能的通信, 因此会动态计算这个最大超时时间

Linux中(BSD Unix和Windows也是如此), 超时以500ms为一个单位进行控制, 每次判定超时重发的超时
时间都是500ms的整数倍.
如果重发一次之后, 仍然得不到应答, 等待 2500ms 后再进行重传
如果仍然得不到应答, 等待4
500ms 进行重传. 依次类推, 以指数形式递增
累计到一定的重传次数, TCP认为网络或者对端主机出现异常,强制关闭连接


避免丢包重传

滑动窗口

从前面的确认应答机制可以看到,如果对于每一个数据,发送后等需要等到接受其回复确认,才发送下一个数据,就会导致效率的低下,尤其是网络较差时,数据的往返之间过长的情况。
既然这样一收一发的效率过慢,那么一次性发送多条,就可以解决这种问题,但是如果发送的数量过多,就可能会导致缓冲区满而出现丢包,为了控制发送数据的规模,于是TCP就引入了滑动窗口机制。

在TCP首部中我们看到有一个窗口大小的字段,那个就是滑动窗口的大小,其限制了发送方最多发送多少数据。同时还有MSS(最大数据段大小,选取两边最小的一个MSS作为最大数据段大小),在三次握手的时候双方进行协商,规定好通信时的MSS和窗口大小。

窗口大小不能大于接收方的接受缓冲区中剩余空间大小,否则多出来的数据会直接丢弃,导致丢包。

发送端维护发送窗口:用于限制一次能够发送的数据
后沿:数据发送的起始位置,等待确认回复的数据,如果得到了回复,则后沿移动。
前沿:数据发送的结束位置,前沿减去后沿必须小于等于窗口大小,移动取决于窗口大小。

例如这张图,窗口大小为4000,当1001-2001的数据得到回复后,窗口前沿后沿都向后移动1000.

摘自图解TCP/IP

接收端维护接受窗口:用于进行数据的排序。
后沿:数据接受的起始位置,移动取决于是否收到后沿数据。
前沿:接收缓冲区剩余空间大小加上后沿,移动取决于接收缓冲区剩余大小。

滑动窗口机制允许发送端根据窗口大小和mss连续发送多条数据,并且为丢包的情况,也做好了准备。
其又引入了下面三个协议。

停止等待协议

停止等待协议就是得到一条回复,才发送下一条数据。
如图
在这里插入图片描述

回退N步协议

一条数据丢失了,则需要发送端将这条数据以后的数据全部重传
在这里插入图片描述

选择重传协议

如果一条协议丢失了,就仅仅对丢失的数据进行重传
在这里插入图片描述


流量控制

接收端处理数据的速度是有限的. 如果发送端发的太快, 导致接收端的缓冲区被打满, 这个时候如果发送端继续发送,
就会造成丢包, 继而引起丢包重传等等一系列连锁反应.
因此TCP支持根据接收端的处理能力, 来决定发送端的发送速度。这个机制就叫做流量控制

  • 接收端将自己可以接收的缓冲区大小放入 TCP 首部中的 “窗口大小” 字段, 通过ACK端通知发送端;
  • 窗口大小字段越大,说明网络的吞吐量越高;
  • 接收端一旦发现自己的缓冲区快满了, 就会将窗口大小设置成一个更小的值通知给发送端;
  • 发送端接受到这个窗口之后, 就会减慢自己的发送速度
  • 如果接收端缓冲区满了, 就会将窗口置为0; 这时发送方不再发送数据, 但是需要定期发送一个窗口探测数据段,使接收端把窗口大小告诉发送端

拥塞控制

虽然滑动窗口能够高效的发送大量的数据,但是在开始阶段就发送大量的数据,也可能会出现问题。 假设此时网络状态不是很好,可能有很多主机在使用,导致网络的拥挤,在不了解网络状态的情况下贸然发送大量的数据,可能就会导致发送的数据越多,丢包也就越多。
所以TCP又引入了一个拥塞控制的机制来解决这个问题。

拥塞控制:以一种慢启动,快增长的传输方式,进行根据网络状态调整发送速度的机制

先发少量的数据,搞清楚当前的网络状况,再根据网络的状态来调整速度,如果网络状态正常,则快速的增加发送的速度。

在这里插入图片描述
此处引入一个概念程为拥塞窗口
发送开始的时候, 定义拥塞窗口大小为1;
每次收到一个ACK应答, 拥塞窗口加1;
每次发送数据包的时候, 将拥塞窗口和接收端主机反馈的窗口大小做比较, 取较小的值作为实际发送的窗口
在这里插入图片描述
少量的丢包, 我们仅仅是触发超时重传; 大量的丢包, 我们就认为网络拥塞;
当TCP通信开始后, 网络吞吐量会逐渐上升; 随着网络发生拥堵, 吞吐量会立刻下降;

拥塞控制,归根结底就是TCP想尽可能快的传送数据,但是又怕给网络造成巨大的压力,所以采取这种当网络状态好是吞吐量上升,网络状态不好时吞吐量下降的折中方法。


挽救传输性能

快速重传

如下图,当某一个报文段丢失时,接收端会一直回复后沿数据的确认应答,提示发送端我想要的是从1001开始的数据,即使中间发送了后面的数据,接收端也不会进行确认,而是不断的回复后沿数据的确认应答。而如果发送端连续三次收到了同一个确认应答,他就了解到了有数据的丢失,他此时就会对确认序号ack位置的数据进行重传。

这样的方法比之前的超时重传要快,因为发送三次确认回复的时间比起等待超时要少了很多,所以这种机制也被称为快速重传机制。
在这里插入图片描述
为什么是三次?
三次提供了一个缓冲的时间,可以适当避免因为网络延迟而导致的数据延迟到达。三次其实也是相当于一个确认,如果在第一次或者第二次后收到了该数据,则就不会再发送后面几条,发送端也不需要重传。


延迟应答

接收方接收到数据后并不会马上进行确认回复,因为一旦回复就会因为接收缓冲区的剩余空间变小,导致发送方的滑动窗口变小,使传输的吞吐量变小。
所以引入延迟应答机制, 延迟一段时间,等待上层进行数据处理一段时间再进行答复,可能在应答的时候上层就已经将数据取出了,这样就保证了窗口的大小不会变小,吞吐量不会变慢,并且等待的时候上层也在处理,所以不会出现缓冲区不足的情况。

延迟应答保证网络不拥塞的情况下尽量提高传输效率


捎带应答

接收方接受到数据之后,需要进行确认回复。而确认回复其实就是在报头中标记一个ACK确认序号,发送这样的只有响应的空报文十分浪费,所以为了减少空报头的响应占据带宽,所以引入了捎带应答机制,在即将要发送的数据头部中加上上一条接收到的数据的确认回复。比如三次握手中第二次的ACK+SYN就是捎带应答。


面向字节流

对于TCP来说,每当创建一个socket的时候,就会同时在内核中创建一个接收缓冲区和发送缓冲区。

接收:对于接收到的数据,并非像UDP一样一条一条往上交付,而是先将接收到的数据放入缓冲区,再根据上层所需要的长度,从接收缓冲区中取出相应的数据交付。
发送:对于发送的数据并不会直接发送,而是先存入发送缓冲区。如果数据过大,则会被拆分成多个TCP数据包发送。而如果数据过小,则会在发送缓冲区中等待,直到大小合适后再从发送缓冲区中取出数据发送(延迟发送可关闭)。

优点:这种传输方式比较灵活,对于多个小数据,会合并为一条大的数据一次性发送过去,这样就大大的减少了IO的次数。接收方也更加灵活,他可以任意取出想要的数据,不会像UDP一样必须交付一条完整的报文。
缺点:因为数据会在缓冲区中进行合并或者拆分,这就导致了数据直接的边界无法控制,所以TCP交付的这条数据可能并非一条完整的数据,而是半条或者多条数据,所以可能会导致上层会将多条数据按照一条来处理。这也就是TCP粘包问题。

TCP粘包问题:即TCP可能将多条数据按照一条处理(UDP不会有这种问题,UDP在首部中定义了数据报长度,确保每次只交付一条完整的数据)

解决方案:需要我们自己进行边界的管理。
1.每条数据之间以特殊字符进行间隔(如果数据中有该字符可能要转义处理)
2.数据定长传输,不够则补位(数据如果过短,则会传递大量无用的补位数据)
3.应用层协议头部定义数据长度(这里可以参考http协议和udp协议的做法)

http:头部以\r\n\r\n表示结束,并且在头部的Content-Length确定正文长度。
udp:传输层就解决了,在头部中就已经定义数据报长度。


基于TCP的应用层知名协议

  • HTTP
  • HTTPS
  • SSH
  • Telnet
  • FTP
  • SMTP

猜你喜欢

转载自blog.csdn.net/qq_35423154/article/details/106906220