计算机网络 - TCP协议与UDP协议

UDP协议
TCP协议
TCP协议的可靠传输机制
  确认应答
  超时重传
  连接管理
    三次握手
    四次挥手
  滑动窗口
  流量控制
  拥塞控制
  延时应答
  捎带应答
  面向字节流(粘包问题)
  TCP的异常终止


UDP协议

UDP协议:

UDP报文长度为2个字节, 也就是UDP报文的位数最大为65535

一个完整的UDP报文格式:
请添加图片描述
  校验和用来验证网络传输的这个数据是否正确(即使校验和正确, 也不能保证数据一定是正确的, 但如果校验和错误, 则说明数据一定发生了错误).

  长度指UDP数据报的长度, 范围为0~65535, 要想使用UDP协议表示一个更大的数据, 就需要在应用层对数据进行分包, 将数据拆成多个部分, 然后通过多个UDP数据报进行发送, 再让接收方将数据重新进行拼接. 但这种方法又需要考虑更多的问题, 例如出现丢包情况, 或者包的接收顺序发生了改变, 因此, 我们不推荐这种做法.

  如果报文过长, UDP报文难以满足需求, 就需要通过报文长度可变的TCP协议来传输数据.

TCP协议

TCP报文格式:
请添加图片描述
  TCP报文相对于UDP报文来说更复杂, 报文中的32位序号用于保存传输的字节数, 32位确认序号用于保存接收到的字节数, 六位保留位, 窗口大小等内容下面会提到, 而由于选项的存在, 可以通过这一字段来调整TCP报文的长度, 这就使得TCP的长度是变化的.

TCP协议的可靠传输机制

确认应答

确认应答:保证可靠传输的和新机制

  接收方接收到消息之后, 向发送方发送一个应答报文(ACK, acknowledge)表示自己已经接收到消息, 而在网络中, 数据的发送十分频繁, 可能会出现数据先发后至(数据的发送顺序与接收顺序不一致)的情况, TCP报文中的32位序号就记录了报文中每个字节的发送序号, 32位确认序号记录了当前的应答报文是针对哪一条报文的应答, 有效解决了先发后至问题.

  要注意的是, TCP协议是针对字节来进行编号的, 例如第一个字节记为编号1, 第一千个字节记为编号1000等.

一次完整的确认应答的方式为:
请添加图片描述
  主机A向主机B发送了一个长度为1000个字节的报文, 并对每个字节进行编号, 主机B接收到这个报文后, 向主机A返回一个应答报文, 应答报文中的确认序号为1001, 代表小于1001的数据都被主机B顺利收到了, 下一次A从序号为1001的字节开始发送.

超时重传

确认应答是当网络环境正常时, 接收方向发送方发送应答报文, 而超时重传机制的应用场景主要是网络传输过程中出现丢包问题时.

例如, 当主机A向主机B发送了一条报文, 此时A有两种可能无法收到B发来的应答报文:

  1. A向B发送失败, B没有接收到此条报文, 则B不会向A发送应答报文;
  2. A向B发送成功, 但B发送的应答报文发送失败, 此时A接收不到此条应答报文.

对于发送方来说, 无法区分这两种情况, 因此, 只要发送方没有接收到应答报文, 在等待一段时间后, 就会继续向接收方发送报文, 这就是超时重传机制.

如果是上面的第二种情况, 此时接收方收到了两条相同的报文, 操作系统内核就会通过TCP协议来执行一次去重操作.

去重操作的具体步骤为:

  1. 接收方收到发送方发来的数据后, 会将数据存入操作系统内核的"接收缓冲区"中
  2. 每次收到新的数据, 操作系统内核会查看TCP报文中的序号, 判断当前报文是否已经存在于接收缓冲区中
  3. 如果存在, 则直接丢弃该数据
  4. 如果不存在, 则将该数据存入接收缓冲区

注意:去重操作是在操作系统内核中执行的, 因此, 应用程序无法感知到接收方发来的数据是否为重复数据

  发送方的超时重传也不是一定成功的, 存在多次传输全部失败的情况(例如网络环境非常差, 已经无法正常通信), 在这种情况下, TCP首先会持续降低传输频率(也就是重传的时间间隔越来越长), 如果重传操作一直失败(也就是发送方始终无法收到接收方发来的应答报文), 发送方就会放弃传输, 并与接收方断开连接.

  对于主流的操作系统来说, 发送失败时进行超时重传的次数以及两次重传之间的时间间隔并非固定, 而是可以修改的.

连接管理

如何建立连接(三次握手)

客户端和服务器之间通过三次交互, 完成建立连接的过程.

注意:建立连接请求的一方是客户端(服务器无法主动向客户端建立连接请求)

建立连接请求:
请添加图片描述
第一次握手:客户端向服务器发送一个SYN同步报文段, 请求建立连接

SYN是TCP报文中六个标志位的第五位, 如果SYN=1, 表示当前报文是一个同步报文段, 证明发送方正在请求与接收方建立连接
请添加图片描述

第二次握手:服务器收到连接请求之后, 向客户端发送ACK确认报文段, 并将该报文中的SYN置为1

ACK是六个标志位的第二位, 如果ACK=1, 表示这个报文是一个确认报文段(也就是应答报文). 服务器在应答报文中将SYN置为1, 表明服务器也要向客户端请求建立连接

第三次握手:客户端接收到服务器发来的报文, 并对服务器的请求作出应答, 发送一个应答报文(ACK=1), 允许服务器与自己建立连接.

至此, 客户端与服务器的三次交互完成, 代表两者成功建立连接.

三次握手的作用是什么?

  1. 通过三次握手可以检查当前的网络状况是否满足可靠传输的基本条件
  2. 让客户端和服务器告知双方的信息
  3. 检测通信双方发送和接收数据的功能是否正常

第一次握手:检测发送方的发送能力
第二次握手:检测接收方的接收能力和接收方的发送能力
第三次握手:检测发送方的接收能力

  客户端与服务器建立连接之后, 操作系统内核中就会使用一定的数据结构来保存连接相关的信息, 其中最重要的也就是建立连接相关的五元组(源IP, 目的IP, 源端口, 目的端口, 协议类型), 此后, 如果连接中断, 这些数据也就变得不再有意义, 此时, 就需要通过四次挥手来释放这些系统资源.

如何中断连接(四次挥手)

三次握手只能由客户端主动发起, 而四次挥手可以由客户端发起, 也可以由服务器发起.

四次挥手与三次握手的目的不同(三次握手是为了确保通过TCP协议实现可靠传输, 四次握手是为了释放系统资源), 但过程是十分相似的.

四次挥手的过程如下:
请添加图片描述
客户端和服务器各自向对方发送了一个FIN(结束报文段)请求, 并且各自给对方发送一个ACK请求报文.

四次挥手能否变为三次挥手?

  服务器向客户端发送ACK和FIN的顺序是不同的, 服务器向客户端发送的ACK由操作系统内核负责, 一旦服务器收到客户端发来的FIN, 就会立刻返回一个ACK, 而FIN是由用户代码负责(只有代码中调用了socket.close()方法时才会触发FIN), 如果这两个操作的时间间隔非常小, 则有可能进行合并, 反之则无法进行合并.

滑动窗口

滑动窗口:在保证可靠性的前提下尽量提高传输效率

有两台主机A和B, A向B发送数据, 如果不采用滑动窗口机制:
请添加图片描述

  由于确认应答机制的存在, 主机A向主机B发送下一条数据时需要等待B的应答报文, 这种传输效率非常低下, 因此, 我们需要通过滑动窗口机制来提高数据的发送效率:
请添加图片描述
  使用滑动窗口机制, A向B发送数据时不再一组一组地发, 而是一次性发送多组数据, 并同时等待B返回的ACK, 这样就将多次等待压缩成了一次等待, 大大提高了数据的发送效率.

  如果每次发送的数据为N, 则N为滑动窗口的窗口大小, 所谓"滑动", 就是说并不需要收到所有报文的ACK, 而是收到第一个ACK之后就继续发送下一组数据.

  例如, 上图中主机A先发送了四组数据, 当A收到1到1000的应答报文时, 就立刻发送下一组数据(也就是4001到5000), 等再次收到1001到2000的应答报文之后, 继续发送5001到6000.
请添加图片描述
  主机A每收到一次ACK, 就继续发送下一组数据, 并等待下一个ACK, 随着ACK接连的到来, 这个窗口一直在向右滑动, 这就构成了一个"滑动窗口".

  滑动窗口的窗口越大, 可以认为传输速率越快.(窗口越大, 同一时间内等待的ACK就越多, 总的等待ACK的时间就减少)

在滑动窗口的背景下, 如果出现丢包问题, 该如何解决呢?

丢包分为两种情况:

  1. 返回的ACK丢失:

  这种情况不需要主动处理, 只需要通过后续收到的ACK进行确认即可, 例如, 发送方没有收到ACK1001, 但是收到了2001, 那就说明前面发送的数据没有问题, 只是ACK1001丢失(收到ACK2001的意思是:2001之前的所有数据都已接收到)

  1. 发送的数据丢失:
    请添加图片描述
      上图中, 由于主机A发送的1001到2000的数据丢失, 所以B在向A反复发送ACK1001, 向A索要这段数据, 即使A已经发送了后面的数据, B依然在向A发送ACK1001, 当B发送若干次时, 此时A就会触发重传机制, 重新向B发送1001~2000这段数据, B收到之后, 就会直接向A发送收到的最后一条数据的ACK(例如上图中B收到1001之后, 向A发送的ACK为7001, 代表7001之前的数据均已成功接收)

流量控制

  上面提到了, 滑动窗口越大, 数据的传输速率越高, 但是, 如果一次同时发送的数据太多, 接收方难以同时处理, 此时接收方就会丢弃新接收到的数据, 后面发送方又会重新发送这些数据, 这就得不偿失了, 因此, 需要通过流量控制处理数据流量的发送数. 流量控制的关键, 就是衡量接收方的处理速度.

  接收方在接收数据时会将数据存入接收缓冲区中, 这样的数据接收机制类似于生产者 - 消费者模型, 发送方发送数据, 接收方将数据存入接收缓冲区中,
如果缓冲区的剩余空间比较大, 就认为接收方处理数据的能力比较快, 就可以让发送方更快速地发送数据, 反之, 就让发送方更慢地发送数据. 那么, 接收方如何告知发送方剩余空间的大小呢?

  TCP报文中存在16位窗口大小, 当接收方向发送方发送应答报文时, 通过这个窗口大小来告知发送方当前剩余空间的大小.

  如果窗口大小为0, 代表此时剩余空间已满, 发送方不应该再发送数据, 但是, 随着接收方处理数据, 缓冲区中的数据又会逐渐减少, 此时, 又需要发送方发送数据, 这样, 就达到了一个动态平衡的效果.

  发送方在接收到窗口大小为0的ACK之后, 就会停止发送数据, 那么, 发送方什么时候该继续发送数据呢?

  发送方每隔一段时间, 就会向接收方发送一个探测报文, 这个报文内部没有任何数据, 仅用于获取接收方最新的窗口大小的消息.

拥塞控制

  数据传输的速度不仅取决于接收方的处理能力, 也取决于发送方和接收方之间链路的传输能力. 拥塞控制主要衡量的就是发送方到接收方整个链路之间的拥塞情况.

  拥塞控制的处理方案, 就是通过实验的方式, 逐渐调整发送速度, 找到一个合适的范围, 具体做法为:

  首先,发送方以一个比较小的窗口来发送数据, 如果数据比较流畅地到达, 就逐渐增大窗口的大小, 如果出现丢包现象, 就证明通信链路出现了拥堵, 此时再减小窗口.

  通过反复的增大/减小窗口, 就找到了窗口大小的一个合适的范围, 拥塞窗口在这个范围内不断变化, 以达到"动态平衡".

请添加图片描述
  上面的流量控制和这里的拥塞控制都能控制滑动窗口的大小, 实际的滑动窗口大小=min(拥塞窗口, 流量控制窗口).

  最开始时, 实际的窗口大小非常小, 此时可以通过指数式的增长快速增长到极限值. 之后开始线性增长, 增长到一定程度就会开始丢包, 一旦丢包, 发送方就会让窗口变小(回归初识窗口大小), 继续重复刚才的指数增长和线性增长的过程.

这里为什么要让窗口直接回归到初始大小呢?

  网络的情况是十分复杂且不稳定的, 如果出现丢包, 如果将速度稍微降下来, 可能会难以解决丢包问题的, 如果速度降低地太慢,就可能持续出现丢包问题, 这种情况会对网络通信产生非常大的影响, 因此, 我们需要快速将窗口降至最小, 以避免继续出现丢包问题.

延时应答

  流量控制是希望数据不要发送地过快, 延时应答是在这个基础上尽可能地让窗口更大.

  具体策略为:当发送方向接收方发送探测报文时, 接收方不会立即回答, 而是先处理一部分接收缓冲区中的数据, 让然后返回给发送方一个更大的窗口大小, 来让发送方尽可能多地发送数据.

延时应答就是在发送速度有限的情况下, 尽可能地让发送方多发送数据.

捎带应答

  由于延时应答的存在, 接收方在收到报文时, 不会立即返回ACK, 那么, 如果接收方也要向发送方发送报文, 这条报文和延时发送的ACK就有可能重合, 此时就可以将这两条报文合二为一, 以提高传输效率, 这就是捎带应答的机制.

面向字节流(粘包问题)

  粘包问题不仅出现在TCP协议中, 所有面向字节流的机制都可能出现这个问题, 例如文件操作中.

  TCP协议的粘包问题指的是在TCP协议缓冲区中, 多个应用层数据包混在一起.

  发送方发送多条报文之后, 接收方对这些数据进行分用, 分用就意味着将这些数据进行解析, 取出其中的应用层数据, 将其放入接收缓冲区中, 以备接收方的应用程序来取, 然后, 接收方的应用程序就会通过read()方法来从缓冲区中取出数据, 因为TCP协议是面向字节流的, 程序取出的数据就是一些字节, 那么, 从哪个地方到哪个地方的字节是一个完整的数据报呢?

  如果不加处理, 这一问题是无法得到解决的, 处理方式为:在应用层协议中加入包之间的边界, 例如每条数据都以相同的方式结尾, 这样, 就能区分多个数据报.

TCP的异常处理

  1. 进程终止

在进程正在运行时突然结束进程, 此时该进程的TCP连接是如何断开的?

  TCP连接是通过socket来建立的, socket本质是进程打开的一个文件, 文件实际上存在于每个进程PCB中的文件描述符表上, 每次打开一个文件(包括socket文件), 都会在文件描述符表中添加一项, 每关闭一个文件就会删除一项.

  如果直接杀死进程, PCB也会直接从PCB表上断开连接并被回收, 里面的文件描述符表也就不存在了, 这些文件相当于自动关闭, 这一过程类似于手动调用socket.close(), 也会触发四次挥手.

  1. 机器关机

  机器正常关机(通过关机键关机)时, 操作系统会终止所有的进程, 每个进程的关闭过程与上述过程相同.

  1. 机器掉电

例如,直接关闭电脑的电源, 此时操作系统不会有任何的反应时间, 也不会有处理措施.

  如果是接收方断电, 此时如果客户端还在向接收方发送数据, 就不会收到接收方返回的ACK, 就会触发确认重传机制, 重传依旧会失败, 此时发送方就会认为此次连接出现了严重故障, 就会尝试重新建立连接, 重新建立连接失败之后, 就会放弃进行连接, 并释放相应的连接信息.

  如果是发送方断电, 发送方无法向接收方发送数据, 此时, 接收方无法判断发送方停止发送数据的原因, 就会向发送方发送探测报文, 但始终无法收到探测报文的ACK, 因此判定发送方出现异常, 并且断开此次连接.


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

zhanglf6699

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值