文章目录
- [一. TCP和UDP简介](#一. TCP和UDP简介)
- [二. UDP 协议详解](#二. UDP 协议详解)
-
- [1. UDP报文格式](#1. UDP报文格式)
- [2. UDP的使用场景](#2. UDP的使用场景)
- [三. TCP 协议详解](#三. TCP 协议详解)
-
- [1. TCP报文格式](#1. TCP报文格式)
- [2. TCP协议的重要机制](#2. TCP协议的重要机制)
- [3. TCP的使用场景](#3. TCP的使用场景)
前言:
本文是对计算机网络学习中传输层两个重要协议 TCP和UDP 特性的介绍和部分细节的详细说明。
一. TCP和UDP简介
TCP(Transmission Control Protocol):中文名为传输控制协议,是一种面向连接的、可靠的、基于字节流的传输层通信协议。
UDP(User Datagram Protocol):中文名为用户数据报协议,是一种无连接、不可靠的、面向数据报的传输层通信协议。
TCP和UDP 有以下特点:
- TCP是有连接的,UDP是无连接的
- TCP提供可靠传输,UDP提供不可靠传输
- TCP数据传输面向字节流,UDP面向数据报
- TCP和UDP都是全双工的(共同点)
================
- 有连接 VS 无连接
"连接"是一种抽象的概念,指客户端和服务器在进行数据传输前需要保存对端的关键信息(如IP地址、端口号等)并且依靠保存的信息来发送/接收数据。
TCP 要想通信需先建立连接,只有连接完成后才能完成后续的通信(若连接过程中一方拒绝了,则无法进行数据的发送或接收)。例如:当我们通过社交软件或电话向某人发起通话请求时,只有对方接听才可进行后续通信过程。
相比之下 UDP 无需经过对方的同意即可直接发送数据,且不会保存对方的信息。由于 UDP 这种"健忘"的特点,客户端/服务器每次发送数据时都需指定 IP和端口号 才能完成数据的传输(由写程序的人指定)。
- 可靠传输 VS 不可靠传输
在复杂的网络环境中,数据传输可能存在丢包的情况,这些缺失的数据可能导致得到的信息不能被正确解析。面对这种情况,TCP 会将丢失的数据包重新传输来保证通信双方都尽量得到正确的数据,原因是 TCP 内部提供了一系列重要机制来保证这种可靠传输;而 UDP 则没有这种可靠传输机制,如果数据包在网络传输过程中丢失了,UDP 是"没有感知"的,因此不会将丢失数据包重传。
看起来 TCP 提供的传输机制似乎"很香",那么为啥 UDP 不实现可靠传输呢?
原因其实很简单:1. 保证可靠传输需要实现更多复杂的机制 2. 可靠传输以传输效率作为交换条件,因此 TCP的传输效率远比 UDP更低。
- 面向字节流 VS 面向数据报
TCP 与文件操作一样,都是以字节为单位进行数据传输的,所有的数据都被视为一连串的字节流,在传输时会被分割成若干大小合适的数据段,到达后再按顺序 被重新组装成完整的字节流。
由于数据以一连串字节流的形式存在,因此在写入或读取时没有严格的对应关系,即数据读取时可根据需要读取任意字节数的数据。
UDP 是面向数据报传输数据的,所有的数据都被视为一个个的数据报,其中每个 UDP数据报都包含了完整的数据以及源/目的端口等信息,因此数据在写入/读取时以单个数据报位为单位。
- TCP与UDP 都是全双工
全双工即通信双方既可以发送数据,也能够接收数据,且发送和接收数据能够同时进行,不需要等待对方完成其中某个操作。
二. UDP 协议详解
1. UDP报文格式
-
源/目的端口号:用于绑定通信双方主机上的进程,表示数据从哪个进程发出,由哪个进程接收。
-
UDP报文长度 :UDP报头大小 + UDP载荷大小,即整个UDP报文的大小。它可以表示一个2个字节的数据,最大为 2^16 - 1 = 65535 ≈ 64Kb,单位为字节,因此一个UDP数据报最大可以携带 64KB的数据。
实际上最大可传输的数据大小应为 64K - 8 字节(报头部分),但由于8与64K相差较大,一般可忽略不计。
-
UDP校验和 :即将UDP载荷中的数据通过一定方式计算得到的16位数据,用于检测UDP头部和数据部分在传输过程中是否发生错误。
为什么需要校验和?
我们都知道数据是通过 光信号/电信号的"频率"和"强弱"来表示的,其中高电频表示 1,低电频表示 0。数据在网络传输过程中,由于受到某些外部因素的干扰(如电场、磁场、高能离子等),可能让表示0的低电频变为表示1的高电频(也称作比特翻转),导致数据在传输的过程发生错误。因此,数据包携带的校验和就变得十分必要。
如何通过校验和验证数据是否出错?
先将数据在发送前进行校验和的计算,并将校验和与数据等信息一起发送给通信对端;当数据送达后,接收方再通过收到的数据进行校验和的计算,然后将得到的校验和与数据包中的校验和进行比较,若比较的结果不一致,则说明数据在传输的过程中大概率出错了。
这时候可能有人会好奇,如果接收方收到的数据只有非常少的地方不同,那么是否可能得到一样的校验和?
答案是否定的,因为就算只有十分微小的不同,计算出来的校验和往往是天差地别的。如MD5,它是一种hash算法,能够把任意长度的原始数据映射成为 128bit 的数据。(如下图,只改变一个字母,计算出来的结果差别非常大)
也许也有人会有这种疑问,如果数据传输过程中出错了,并且校验码也发生了错误,那么是否可能得到一样的校验和?
答案是理论上存在这种可能,但是概率非常非常小,基本可以忽略不计。
2. UDP的使用场景
UDP适用于对高速传输和实时性要求较高、对稳定性和可靠性要求不高、可以容忍少量数据丢失的场景。如早期的QQ、音视频传输、广播等。
基于 UDP 的部分应用层协议
- NFS:网络文件系统
- TFTP:简单文件传输协议
- DHCP:动态主机配置协议
- BOOTP:启动协议(用于无盘设备启动)
- DNS:域名解析协议
三. TCP 协议详解
1. TCP报文格式
由TCP报文格式我们可以粗略知道以下信息:
-
源/目的端口号:这里的端口号与UDP报文中的端口号作用一致,都用于定位通信双方主机的进程。
-
校验和:用于检查 TCP头部和数据部分是否发生错误。
-
首部长度 :也称作 "数据偏移",用于确认 TCP首部 的长度,它表示一个 0 ~ 15 的数据,单位为 4个字节。由于 TCP拥有固定20字节的首部,因此数据偏移最小值为 5 (若数据偏移小于5,则该TCP报文违反了TCP协议的规范,被视为格式错误),又因此一个 TCP数据包的大小为 20 ~ 60字节。
-
保留位:表示一个 6位 的数据,目前暂未使用,置为0。保留位的作用是为了将来改进 TCP协议时提供一定的灵活性和可拓展性,以适应不断变化的网络环境和应用需求。
-
校验和:用于检测 TCP首部和数据部分在传输过程是否发生错误。
===============
除了以上信息,TCP 相比于 UDP 还存在许多额外的字段来 保证TCP的可靠传输 及 支持其他功能。
- 序列号 :占4个字节,范围是 0 ~ 2^32 - 1,当序列号超过最大值重置为0,序列号用于标识此次发送数据的顺序,它的值为该报文段中数据部分的第一个字节的序号。例如:某个 TCP报文的序列号为101,发送的数据为 200字节,则下一个 TCP报文的序列号应该为 301......(如下图)
- 确认序列号 :确认序列号的值为某个TCP数据包中 序列号 + 数据的字节数。用于确认接收到的数据:表示在该值之前的所有数据都已确认接收到,且期待数据发送方下一次发送的数据为确认序列号后的数据。(如下图)
TCP首部中的 6个标志位用于控制 TCP连接的建立、终止和数据传输过程中的各种状态。各标志位作用如下:
- URG(Urgent):当URG被置为1时,表示紧急指针字段的值有效,该标志位的作用是告知接收方该 TCP报文中紧急指针字段所指示的数据被视为紧急数据,需要优先处理。
- ACK(Acknowledgment):当ACK被置为1时,表示确认序列号字段的值有效,它用于确认接收到的数据。在 TCP连接的数据传输过程中,数据接收方每次收到数据后都会返回一个带有ACK标志位的 TCP报文,通知发送端已成功接收到数据。
- PSH(Push):当PSH被置为1时,表示数据接收端应尽快把接收到的数据推送给应用层,而不是等待缓冲区满后再传输。
- RST(Reset):当RST标志位被置为1时,表示发生了严重的错误或连接出现异常,需要中断TCP连接并重置连接状态。
- SYN(Synchronized):当SYN标志位被置为1时,表示当前通信双方处于正在建立连接的状态,通常出现在"三次握手"中。
- FIN(Finish):当FIN标志位被置为1时,表示发起方已经完成了数据发送,告知接收方即将断开 TCP连接。
============
- 窗口大小:表示数据接收端允许发送端发送的最大数据量。
- 紧急指针:表示紧急数据的结束位置在数据流中的偏移量,与URG标志位配合使用。
- 选项:为可选字段,且长度不定,用于提供一些额外的功能和控制信息。若使用了选项,则该字段的大小为 4字节的倍数,最大为偏移量的最大值 15 * 4 - 20= 60 - 20 = 40字节。
2. TCP协议的重要机制
TCP 相比于 UDP 之所以有更多的应用场景,最最核心的因素就是 TCP提供可靠传输(初心),可靠传输并不是要求网络数据被 100% 送达,而是当数据包丢失时TCP 能够依靠内部一系列重要机制实施相应的"补救"措施。下面是对 TCP 内部一些重要的机制的介绍。
确认应答(保证可靠传输的最核心机制)
在使用 TCP 进行网络通信时,当发送方把数据发给接收方之后,接收方如果收到数据会返回一个 "应答报文";当发送方收到"应答报文"后,就可以知道自己的数据是否发送成功了。这个返回"应答报文"的机制就是确认应答,确认应答是 TCP保证可靠传输最核心的机制。
确认应答的实现主要依靠 序列号和确认序列号 这两个字段来完成。
若通信双方约定好开始传输数据时的起始序列号为 1,发送方每次发送的数据为 1000个字节,即此次数据的"编号"为 1 ~ 1000,则接收方收到数据后会返回一个不携带业务数据、确认序列号为 1001的 TCP报文,发送方收到该报文后,就知道了 1001之前的所有数据都已成功送达;若数据发送方收到 确认序列号为 4001 的报文,则意味着序号为 3001 ~ 4000 的数据已成功送达,且期待发送方下次发送序号 4001 后的数据......(如下图)
在真实的网络环境中,网络线路往往十分复杂,每个 TCP 数据包经过哪些路线到达目的地是不确定的,因此可能出现数据"晚发先至"的情况,导致数据被错误解析。(如下图)
面对数据"后发先至"的情况, 传输层会将接收到的 TCP报文都存放到缓冲区,因此先到达的数据会"等一等"晚到达的数据,并将所有数据根据 序列号 进行排序,保证数据顺序的正确性。
序列号和确认序列号 作用总结:
- 确认数据是否成功送达。
- 确保发送数据与应答报文能够一一对应;确保出现"后发先至"的情况时,应用程序能够按照正确的顺序进行解析。
超时重传
由于网络环境存在复杂性,当出现网络拥塞、链路错误、路由器故障等情况时,网络数据包可能出现丢失的情况。
当数据发送方超过一定时间都没有收到ACK报文时,会将原先的 TCP数据包进行重传。触发超时重传可能存在两种情况:1.发送方发送的数据包丢失 2. 数据接收方返回的 ACK数据包丢失。
-
发送数据包丢失
当发送的数据包丢失时,接收方没有收到数据,则一定不会返回应答报文,这时发送方由于迟迟没有收到 ACK为1 的TCP数据包,在经过一定时间后便将 数据2 进行重发。
-
应答报文丢失
由于接收方返回的应答报文在传输过程中丢失了,发送方无法收到应答报文,但它不能确定是 发送的数据丢失还是ACK数据包丢失,因此会将数据2进行重发,然后等待数据接收方第二次返回的应答报文,当确认数据送达后再继续发送后续的数据。
在这个过程中,数据接收方会收到两份同样的数据,如果不对这两份数据进行相应的处理可能会造成一些严重后果,如:在进行扣款操作时,付款人已经完成了一次扣款,由于返回的应答报文丢失触发了超时重发,导致多进行了一次扣款操作。
因此,当收到多份同样的数据时,接收方会将多余的 TCP数据包丢掉。
思考:若传输过程中数据包丢失,经历重传操作后发送方仍然没有收到应答报文,那发送方是否无限制触发重传操作呢?
答案是否定的。这里我们假设当前网络数据包的单次丢包率为 20%(这个概率是非常大的,在正常的网络通信中基本不会达到这个值),则两次数据传输的丢包率为 4%,三次传输的丢包率为 0.8%......可以发现,随着重传次数的提高,数据被正确送达的概率会大大提高。
因此当重传达到一定次数后,发送方可能会采取 直接断开或断开重置 TCP连接的操作。在数据重传过程中,触发重传的间隔会逐渐增长(对数据传输成功持悲观态度),原因是随重传次数增加丢包率逐渐降低,如果多次重传都没有响应,说明当前网络已经出现了严重故障。
连接管理(三次握手、四次挥手)!!!
- 建立连接(三次握手)
在网络通信中,通信双方若通过 TCP协议传输数据,需要在数据传输前通过发送不协议业务数据的 TCP数据包 建立连接。如下图:
如上图所示,通信双方都需要发送SYN和收到ACK 为1的 TCP数据包,完成上述过程后,才算真正地建立连接。
可能有人会好奇不是叫三次握手吗,为什么上述交互过程有 4个步骤?
原因是当对端收到建立连接的请求后,会立即返回 ACK报文,而一个 TCP报文中有 6个标志位,因此可以在返回 TCP报文时把 ACK和SYN 同时置为1,将两个报文合二为一,因此通信双方建立连接总共经历了 "3次握手"。(如下图)
或许有人还有疑问,为什么一定是"3次握手",不能"2次握手"、"4次握手"吗?
假设有以下通话过程(电话刚接通)
为了保证后续正常的通信交流,小明和小红需要先用约定好的特殊暗号向对方"打招呼",以确认双方的麦克风和听筒都是正常工作的状态。对方只有在听到前面的暗号,才会按照约定进行下一步的回应。(如下图)
"三次握手"的核心作用:
- 起到"投石问路"的作用,确认当前网络是否通畅
- 让通信双方确认各自的发送能力和接收能力是否正常
- 针对通信过程中一些重要的参数进行"协商"
因此为什么经过"3次握手"是显而易见的,如果是"2次握手",则一方不能得到通信所需要的全部信息;而"4次握手"实际上是可行的,但第4次握手其实是多余的,因为"3次握手"就可完成信息交换的操作。
- 断开连接(四次挥手)
为避免占用网络资源和系统资源导致的性能下降或资源耗尽,当数据传输完毕后应断开 TCP连接。(如下图)
如上图所示,通信双方都需要发送FIN和收到ACK 为1的 TCP数据包,完成上述过程后,才算真正地断开连接。
思考一:可以发现断开连接的过程与建立连接的过程非常相似,唯一的不同之处就是"3次握手"过程中 ACK + SYN标志位可以合并,那断开连接的过程中,客户端发送的 ACK + FIN标志位是否也能合并为1个 TCP报文呢?
答案是:不一定。原因是当客户端收到 FIN 为1的 TCP报文时,操作系统内核会立即返回 ACK为1的 TCP报文,而是否立即发送 FIN,取决于客户端的代码实现中是否存在其他代码逻辑需要处理,因此通常情况下,断开连接过程中,ACK 和 FIN 往往不能合并发送。
思考二:当服务器收到 带有FIN标志位的TCP报文时,可以知道客户端已确认释放连接,但为什么没有立刻进入 CLOSED状态(断开连接),而是进入 TINE_WAIT状态(等待一段时间)后再真正释放连接?
如果服务器收到 FIN后立即断开连接、销毁了之前保存的对方信息,而返回带有 ACK的TCP报文在网络传输的过程中丢失了。由于客户端迟迟没有收到最后一个ACK,它会触发超时重传,等待服务器重发的 ACK,又因为服务器此时已经销毁了客户端的 IP、端口号等信息,对于接收到的FIN,它并不知道要如何处理。而客户端经过多次重传无果后,才会强制断开 TCP连接(非正常断开)。
因此,之所以服务器收到 FIN后会等待一段时间后再断开连接,是为了防止最后一个 ACK丢失时,能够对客户端重发的 FIN进行响应。
滑动窗口
当通信双方利用 TCP 进行数据传输时,实际上并不是每发送一条数据,等待收到 ACK(确认应答)再继续发送下一条数据,因为那样做虽然保证了数据的可靠传输,但效率十分低下。因此,在实际通信过程中,发送方往往是一次性发送多条数据,收到其中某个响应再继续发送数据。(如下图)
在上图中,主机A发出第一条数据,还没等 ACK回来便发送第二、三条数据......在这样"批量发送"的机制下,数据的传输效率会大大提高。理论情况下,当一次性发送的数据量越大,数据传输效率越高;但在实际情况中,因为受到接收方的数据处理能力、网络畅通程度、网络链路中结点的拥塞情况等因素的影响,数据发送方在不等待的情况下,能够批量传输的数据有一个上限值,这个值称为"窗口大小"。
在窗口大小固定的情况下,当主机A接收到第一个返回的ACK时,可立即发送余下的数据,在视觉上就像一个会滑动的窗口,因此发送方批量发送数据的机制也称作"滑动窗口 "。
==========
思考1:若在数据的传输过程中,主机B已接收到主机A发送的所有数据,但某个返回的 ACK 在传输过程中出现丢失,主机B需要重传吗?
答案是:不需要。因为确认应答报文中确认序列号的作用是"说明该确认序列号前的所有数据已经收到"。因此,就算前面的 ACK丢失了,只要主机A收到后面的 ACK,就说明主机B已成功接收前面的数据。
思考2:由于主机A是批量发送数据的,若某条数据在传输过程中出现丢失,主机B收到其他数据会怎样返回应答报文? (如下图)
如上图所示:若序号为 1001 ~ 2000 的数据在传输过程丢失了,又因为 1 ~ 1000 的数据已经收到,主机B"期望"下次收到序列号为 1001的 TCP数据包,因此每次收到数据时都会对 TCP首部进行检查,若不是想要的数据则继续返回确认序列号为 1001的确认报文,直到收到数据为止。
虽然主机B 期望得到 1001 ~ 2000的数据,但对于已经接收的其他数据包并不会直接丢弃,而是在缓冲区中保存着,等到收到期待的数据,下次返回的应答报文中确认序列号则为所有收到数据中最大序号的下一位数。
流量控制
滑动窗口的机制就是在保证可靠传输的前提下尽量提高数据传输效率。理论情况下,窗口值越大等待 ACK 的时间就越长,数据的传输效率也就越高;但如果数据的发送量太大且超过了接收方的处理能力,一些数据包可能会被接收方直接丢弃,因此数据接收方也需要采取某些手段去限制数据发送方,即限制窗口大小。
如上图所示,主机A 发送的数据会通过网络到达主机B的操作系统内核中,在操作系统 API 提供的TCP Socket对象中有一个数据接收缓冲区,由主机A发送过来的数据会先到达该缓冲区中,应用程序再采用read()方法从缓冲区中读取数据。
上述场景就是一个典型的生产者消费者模型,数据发送方称为生产者,数据接收方称为消费者。当发生方的生产能力超过了接收方的消费能力时缓冲区的空间会逐渐变小,直到缓冲区被占满时多余的数据会被直接丢弃,此时主机A 再发送数据是"没有意义"的,应先暂停对数据的发送。
思考1:在数据传输过程中主机A是如何得知主机B的缓冲区还有多大空间的呢?
答案是:接收方在返回的应答报文中通过 TCP报文中 "窗口大小"字段来告知数据发送方此时自己最多还能接收多少数据,通过减小"窗口大小"去限制发送方数据的发送。
思考2:当窗口大小为 0 时发送方会暂停发送数据,那它如何知道何时可以重新发送数据呢?
答案是:虽然发送方暂时不传输业务数据,但仍然会周期性的发送一个"不带业务数据的窗口探测包",发送该探测包主要是为了触发ACK,通过返回的应答报文来得知此时窗口的大小,由此判断是否恢复数据的发送及可发送的数据量。
如下图:
拥塞控制
数据在实际的网络传输过程中,不仅要权衡发送方和接收方的数据发送和接收能力,还需考虑通信过程中间节点的情况。(如下图)
由"木桶原理"可知:整个通信过程中,数据传输量取决于数据处理能力最弱的节点。即使接收方能够处理发送方发送的数据,但由于某些因素导致中间节点数据处理能力下降,也会出现丢包的情况。
在实际的网络通信链路中存在很多的节点、传输路径,路径上的每个节点处理能力达到上限都可能对发送方产生影响,影响到可靠传输。因此,我们往往将网络上的所有传输节点当作一个整体,通过"测试"的方法在发送方发送能力、接收方数据处理能力、中间节点数据处理能力之间找到一个"平衡点",进而确认一个合适的"窗口大小"。
如何进行测试?TCP 引入了慢启动 机制,在刚开始传输数据时先发送少量的数据"探探路","探路"是为了以摸清当前的网络拥塞情况,若当前网络已存在严重阻塞,盲目发送大量数据反而会造成"雪上加霜"的情况。随后逐渐加大数据的传输量,最后再决定按照多大的传输速率去传输数据。(如下图)
上图的拥塞窗口反映了数据发送量的大小,可以发现:刚开始只发送少量数据,随后数据发送量呈指数形增长,当发送量达到"慢启动的阈值"(ssthresh)后呈线性增长,直到出现网络拥塞后数据发送量会直线下降到 1 。后面数据的发送量增长趋势与之前相似,但每一次增长都会重新确定一个"阈值",以避免过快的增长导致数据发送量出现频繁"大起大落",从而白白消耗网络资源。
上图中窗口值出现的"断崖式下降"其实也是不科学的,因为网络出现拥塞的情况可能是短暂的,并不需要将拥塞窗口一下子降为 1,而是降到一个合适的值,这样同样保持数据在网络的稳定传输。因此,后来TCP协议对拥塞窗口的变化也进行了改善。(如下图)
注意:发送方每次发送数据包的时候,会将拥塞窗口和流量控制中反馈的窗口大小做比较,取较小的值作为实际发送的窗口。
延时应答
正常情况下,接收方收到数据时操作系统内核立即返回 ACK,但在某些情况下,操作系统也可能等一会再返回 ACK。这种"等一等再返回ACK"的机制称为延时应答。
延时应答本质上是为了提高传输效率。在流量控制中,数据接收方是通过数据缓冲区剩余空间的大小来决定返回窗口值大小的,而窗口值决定了发送方的传输速率,如果让窗口值更大也就提高了数据的传输效率。
因此,操作系统如果适当延时返回 ACK,应用程序就能从缓冲区中读取更多数据,从而增大了应答报文中的窗口值。例如:如果接收缓冲区的大小为5MB,在某一刻接收方收到了 512K的数据,如果立即返回响应则窗口大小为 4.5MB,但如果此时接收端处理数据的速度很快,只需 100ms 就能将这 512KB 的数据处理完,那么延时 100ms 后再返回 ACK,窗口的大小就仍为 5MB。
捎带应答
在应用层中,不同主机上的进程的交互通常也是"一问一答"的形式。如:主机A发送"查询余额",服务器回复"当前剩余金额为100元"。正常情况下,数据到达传输层后操作系统内核会立即返回 ACK,接着数据再被应用层的程序读取,等待数据处理完毕后将响应封装成 TCP数据包发送给主机A。
在上述过程中,经历了两次 TCP数据包的封装和发送。但由于延时应答机制的存在,ACK可以不被立即返回,等待应用程序处理完数据后,将数据与 ACK 统一封装再发送给客户端。这种通过延时应答将响应数据与ACK统一返回的机制称为捎带应答。
面向字节流
由于 TCP 是以字节流的形式传输数据,因此可能出现"粘包"问题(面向字节流的机制都存在类似情况)。
什么是粘包问题?什么情况会出现粘包问题?
"粘包"中的包指应用层数据包,当多个应用层数据包被同时传过去时,就可能出现"粘包"问题。(如下图)
如上图:在接收缓冲区中,3个 TCP数据包 所携带的应用层数据以字节流的形式仅仅挨在一起的。接收方的应用程序在读取数据时,可以一次性读取 1个字节的数据,也可以读 2个字节的数据,具体取决于程序的实现;字节流形式的数据虽然能被自由读取,但也带来了应用程序无法区别哪些数据是一个完整的应用层数据包的问题。
相比之下,使用 UDP 传输数据则不存在这样的问题,因为 UDP 是以一个个 UDP报文传输数据的,因此每个报文都有明显的分界线,并且每个 UDP 数据包携带的就是完整的数据。
思考:如何解决字节流的"粘包"问题?
答案是:1. 引入分隔符 2. 引入长度
- 引入分隔符:使用"约定好的分隔符"作为每条完整的应用层数据的分界线。 (如下图)
- 引入长度:在每条完整的数据之前引入该数据的长度,应用程序在读取数据时,先确定该条数据的长度,再根据长度读取对应的字节数以读取完整的应用层数据包。
异常情况处理
在使用 TCP 传输数据时,可能出现某些意外,当意外发生时通信双方会如何处理?
-
进程崩溃
进程崩溃属于进程异常终止,文件描述符表也就释放,相当于调用了 socket.close()(触发FIN),对方收到 FIN后自然也会返回 ACK和FIN,此时最后一个 ACK仍然可以被正常返回,这个过程相当于完成了正常4次挥手的流程。原因是虽然进程结束了,但 TCP 连接是可以独立于进程之外存在的。
-
主机关机(正常流程)
当主动关闭电源时,计算机会在真正关闭之前销毁正在运行的进程,此时也相当于调用了 socket.close()(触发FIN),TCP连接的对端收到 FIN后会返回 ACK和FIN,假如在 ACK和FIN 到达之后,计算机还没真正被关闭就仍然可以返回ACK,完成4次挥手的过程;如果 ACK和FIN到达之前,计算机已经销毁掉所有进程且被关闭了,则不能返回 ACK,因此对方会进行多次超时重传,多次重传仍无响应则自动放弃连接(把持有的对端信息删除)。
-
主机掉电(非正常)
主机掉电是一瞬间的事情,来不及杀进程,也来不及发送FIN便直接停机了。面对这种情况,作为 TCP连接的对端可能存在两种处理情况。
1)如果掉电的是数据发送方,对端作为数据接收方 。接收方面对长期没有数据送达的情况,其实并不知道是对端没有发消息,还是对方挂了。TCP 内部提供了"心跳包"的机制 ,面对上述情况,数据接收方会周期性地向对方发送一个不携带任何业务数据的 TCP数据包,发送该数据包是为了得到对方的响应。如果多次发送"心跳包"无果后,接收方会认为对方已经挂了,随后放弃 TCP连接。
2)如果掉电方是数据接收方,对端作为数据发送方。发送方在发送了若干数据后,都没有收到应答报文,此时会触发超时重传,多次重传无果后便触发 TCP 连接重置功能,即将复位报文段 RST 置为1,若依然没有效果,则直接释放连接。
- 网线断开
网线断开与主机掉电的情况非常类似。若掉电方为数据发送方,则接收方会周期性发送"心跳包",如果没有得到任何响应,则最终放弃连接;若掉电方为数据接收方,则发送方会先触发超时重传,重传无果后尝试进行连接重置,若连接重置也没有效果,则放弃 TCP连接。
3. TCP的使用场景
TCP适用于对可靠性、对数据传输的完整性有较高要求的场景。如:文件传输、Web浏览、电子邮件、远程登录等。
基于 TCP 的部分应用层协议:
- HTTP
- HTTPS
- SSH
- Telnet
- FTP
- SMTP
以上就是本篇文章的全部内容了,如果这篇文章对你有些许帮助,你的点赞、收藏和评论就是对我最大的支持。
另外,文章的不足之处,也希望你可以给我一点小小的建议,我会努力检查并改进。