【网络原理】TCP协议 | TCP的报文格式 | 三次握手 | 四次挥手 | 可靠传输 | 确认应答 | 超时重传 | 滑动窗口 | 流量控制 | 拥塞控制 | 延时应答 | 捎带应答 | 粘包问题


一、TCP协议

Transmission Control Protocol,传输控制协议

1.TCP协议报文格式

在这里插入图片描述

  • 两个字节,16位的源端口号和目的端口号,和UDP相同。
  • 16位校验和,和UDP相同。
  • TCP的报头长度是变长的。UDP的报头是固定的8个字节。
  • 选项(option),选项是报头的一部分。可以有可以没有,是可选择的。

数据报 = 首部(报头header)+ 载荷 。这里的4位首部长度就是指报头长度

报头最短,是20字节(没有选项)

TCP报头最大长度是 15 * 4 = 60字节(选项最多40字节)

  • 四位首都(报头)长度:4个bit位(0~0xF,也就是 0~15)。表示TCP头部有多少个4字节(有多少个32位bit)。而此处的单位是4字节(选项都是4字节一个单位)

  • 保留(6位):UDP是限长的,最大64kb。所以TCP设置了保留位,以便进行后续扩展。

2.TCP的特点

1.有连接:

    Socket clientSocket = serverSocket.accept();

TCP的建立连接,是操作系统内核进行的。代码中通过serverSocket来获取到建立好的连接。

2.可靠传输:可以感知到是否发送成功

3.面向字节流:

        try (InputStream inputStream = socket.getInputStream();
             OutputStream outputStream = socket.getOutputStream()) {

4.全双工:一个socket对象,即可以读也可以写。

其中:可靠传输是TCP协议最核心的特点。

可靠传输

在网络通信中,不可能做到100%的传输。为了弥补这个缺陷,进而采用可靠传输的概念。

可靠传输最核心的机制,就是确认应答。

1.发送方发出数据后,能够知道接收方是否收到数据

2.一旦发现对方没有收到,就可以进行补救的手段,比如重新再发一遍。

3.确认应答机制

​ 发送方,把数据发给接受方后,接收方收到数据,就会给发送方返回一个 应答报文(acknowledge,ack)

​ 发送方,如果收到这个ack应答报文,就知道自己的数据是否发送成功了。

​ 一个数据包在进行传输的过程中,走的路径可能是非常复杂的。不同的数据包,可能走不同的路线。就可能会出现数据“后发先至”的情况。

TCP要在此时完成两个工作:

1.确保应答报文和发出去的数据,能对上号,不要出现歧义。

2.确保在出现后发先至的情况时,能让应用程序按照正确的顺序来理解数据。

此时,就需要报头中的32位序号,和32位确认序号。

TCP将每个字节的数据都进行了编号,即为序列号,

序号就是一个整数、它的大小关系描述了数据的先后顺序。是按照字节来编号的。

在这里插入图片描述

假设最开始传输的第一个字节,序号是1

在这里插入图片描述

​ 第一次发送是数据是1~1000。一个TCP数据包里一共有1000个字节的载荷数据。其中第一个字节的序号是1,就在TCP报头的序号字段中写“1”。TCP报头中记录的序号,是这一次传输的载荷数据中第一个字节的序号,剩下其他字节的序号,都需要依次推出。

​ 在应答报文中,就会在 “确认序号” 字段中填写1001,因为收到的数据是1-1000,1001之前的数据,都被B收到了。可以理解为,B向A索要1001开始的数据。

  • 通过ack数据包里面携带的“确认序号”告诉发送方,这些数据已经确认收到了。此时发送方就知道自己发送是数据已经送到了。从而实现可靠传输。
怎么区分一个数据包,是普通的数据,还是ack应答数据?

​ TCP报头中有6个标志位。其中第二个标志位就叫ACK。如果这一位的结果为1,就表示当前数据包是一个应答报文。此时该数据包当中的“确认序号字段”就能够生效。如果是0,此时确认序号字段是不生效的。

4.超时重传机制

  • 超过了等待时间再重传

​ 确认应答描述的是一种比较理想的情况。如果网络传输的过程中,出现丢包了。此时的发送方就无法收到ack确认应答报文。此时使用超时重传机制,对确认应答进行补充。重传能够大幅度提高数据传过去的概率。

丢包是一个"随机"的事件。因此在上述tcp传输过程中,丢包就存在两种情况。

1.传输的数据丢了。数据没送达

2.返回的ack丢了。数据已经送达

​ 在发送方是视角,无法区分这两种情况。所以发送方会一视同仁的进行重新传输。

​ 发送方在发出数据之后,会等待一段时间,如果这个时间内ack回来了,就视为数据到达。如果到了这个时间后,ack还没到,就会触发重传机制。初始的等待时间可以进行配置,同时每多经历一次超时,等待时间就会变长(悲观视角)。

  • 如果只是ack丢了,再次重传,B就会接收到两条一样的数据,系统内核会根据存的数据和序号进行去重。

​ TCP会有一个“接收缓冲区”这样的一个内存空间,会保存当前已经收到的数据,以及数据的序号。当B接收到数据后,会进行对应的存储。当接收方发现,当前发送方发的数据,已经在接收缓冲区中存在了。接收方就会直接把后来的数据丢弃掉,确保应用程序在执行时,只读到一条数据。接收缓冲区不仅能够进行去重,还能进行重新排序。确保发送的顺序和应用程序读取的顺序是一致的。

5.连接管理机制

  • 建立连接(三次握手)+ 断开连接(四次挥手)

握手(handshake)-> 打个招呼,打招呼的内容没有实际意义。

​ TCP这里的握手,就是给对方传输一个简短的,没有业务数据的数据包。通过这个数据包唤起对方的注意,从而触发后续的操作。挥手也是类似。

计算机中很多操作都涉及到握手,不是网络通信独有的(充电口、USB口等待)

1.TCP的三次握手

TCP在建立连接的过程中,需要通信双方一共“打三次招呼”,才能完成连接的建立。

在这里插入图片描述

​ 1.A想和B建立连接,A就会主动发起握手操作。在实际开发中,主动发起的一方,就是“客户端”。被动接受是一方就是服务器。A先给B发送一个syn同步报文段。

​ 2.B收到后,会给A返回一个ack应答报文。然后B会给A也发送一个syn同步报文段(可以用一个数据包,标志位的第2位和第5位同时为1 ,把两次合并成一次进行发送)。

​ 3.A在接收到之后,也会返回给B一个ack应答报文。此时。握手完成。A和B记录了对方的信息,也就构成了逻辑上的连接。

  • syn同步报文段 。是一种特殊的TCP数据包。没有载荷(不携带业务数据)。TCP报头中的标志位,第五位就是SYN。如果这一位为1,就表示当前报文的一个同步报文段。
TCP的三次握手要解决什么问题?

在这里插入图片描述

麦就是发送能力,耳机就是接收能力

​ TCP的初心就是为了实现“可靠传输”,在进行确认应答和超时重传这两个机制的前提,要确保当前的网络环境是流畅的。如果网络存在故障,从根源就无法实现可靠传输。

  • 三次握手的核心作用其一,就是“投石问路”,确认当前网络环境是否是畅通的。

  • 其二,就是要让发送方和接收方都能确定自己的发送能力和接受能力均正常。

  • 其三,让通信双方,在握手的过程中,针对一些重要的参数,进行协商。

    ​ 比如序号从即开始,就是协商出来的。每次连接建立的时候,都会协商出一个比较大的,和上次不一样的值。这样做的目的,是为了避免“前朝的剑,斩本朝的官”。网络不好的时候,会断开重连。等重连好后,收到了姗姗来迟的数据。此时就通过序号来区分丢弃。

TCP的状态

LiSTEN:服务器端的状态

服务器这边socket创建好,并且把端口号绑定好。此时允许客户端随时来建立连接(餐厅开门营业了)

ESTABLISHE: 表示连接建立完成。接下来可以进行正常通信。

客户端服务器都会有的状态。

2.TCP的四次挥手
  • 断开连接

建立连接,一般都是客户端主动发起的。

断开连接,客户端和服务器都可以主动发起。

在这里插入图片描述

​ 1.A先给B发送一个FIN “结束报文段”(TCP报头标志位的第6位)。

​ 2.B收到之后会返回给A一个ACK应答报文。

​ 3.同时,B也给A发送了一个FIN结束报文段。

​ 4.A收到后给B返回一个ACK应答报文。此时连接断开 ,就相当于A和B都把对端的信息删除了。

  • 与三次握手不同的是,此次挥手中间的两次交互不一定能合并。因为ACK和第二个FIN的触发时机是不同的。ACK是内核响应的,当B收到FIN就会立即返回ACK。第二个FIN是应用程序的代码处罚的,B调用了一个close方法,才会触发FIN。
        }finally {
            clientSocket.close();
        }

​ 从服务器收到FIN(同时内核响应返回ACK),再到执行到close方法发起FIN。中间经历的多少时间,执行多少代码是不确定的,具体要看代码是实现方式。FIN是在Socket对象被close的时候发起的(可能是手动调用的close,也可能是进程结束)。而三次握手中,ACK和第二个SYN都是内核触发的,属于同一时间,所以可以合并。在四次挥手中,ACK是内核触发的,第二个FIN是应用程序执行close触发的。时间不相同,所以不能合并。但是TCP中还有一个应答延时机制,能够拖延ACK的应答时间。一旦ACK滞后了,就有可能和FIN合并在一起。

在这里插入图片描述

  • CLOSE状态:连接彻底断开,可以释放了。

  • TIME_WAIT状态:哪一方主动断开连接,哪一方就会进入TIME_WAIT状态,进行等待一段时间。然后再进入CLOSE状态。(这样做主要是为了防止最后一个ACK丢失。如果最后一个ACK丢了,B会认为发送的FIN没有到达,触发超时重传,重新发送一遍FIN。A在这个等待的时间,就可以对重传的FIN响应,再次发送ACK)。假设网络上两个节点通信的最大时间是MSL(可配置的参数),TIME_WAIT的等待时间就是2MSL.

6.滑动窗口机制

  • 前面三个机制都是为了保证TCP的可靠性。滑动窗口机制是为了提高TCP的效率。

    “亡羊补牢”,可靠传输本身就会影响传输的效率。(会多出一些等待ACK的时间)。滑动窗口用来尽可能的降低对效率的影响。

  • 缩短确认应答的等待时间

在这里插入图片描述

在之前的例子中,每发送一段数据,会收到一个应答报文,才会再发送下一个数据,当中的等待时间会很长。

在这里插入图片描述

​ 所以,需要批量的传输数据。不等ACK回来,直接再发下一个数据。批量传输,也不是“无限的传输”。批量传输也存在一定的上限。达到上限后,再统一等待ACK。在不等待的情况下,批量最多发多少数据,这个数据量,称为“窗口大小”

窗口大小指的是无需等待确认应答而可以继续发送数据的最大值。

  • 操作系统内核为了维护这个滑动窗口,需要开辟 发送缓冲区 来记录当前还有哪些数据没有 应答;只有确认应答过的数据,才能从缓冲区删掉;

在这里插入图片描述

​ 当前A给B批量的发了四份数据,此时B也要给A回应四组ACK。此时A已经到达窗口大小,再收到ACK之前,不能继续往下发了。需要等待有ACK回来了之后,才能继续往下发。这四个ACK的到达顺序优先有后,没必要等待四个ACK都回来。只要回来一个ACK,就继续发一个数据。窗口就向后“滑动”。窗口越大,等待的ACK越多,此时的传输效率也就越高。

出现的丢包,怎么重传
情况一:数据包已到达,ACK丢了

在这里插入图片描述

  • 这种情况,不需要任何重传,不用管。

确认序号,表示的含义,是当前序号之前的数据,已经确认收到了。下一个应该从确认序号这里开始,继续发送。

如果1001这个AGK丢了,但是2001这个ACK到了。就意味着2001之前的数据都已经确认传输成功了。涵盖了1001的情况。

情况二:数据包丢了

在这里插入图片描述

​ 数据包丢了,就必须进行重传。此时,主机A就需要知道是哪个数据丢了。主机B也就得告诉A是哪个数据丢了。1001-2000的数据丢了。但是之前的ACK返回的确认序号是1001。后续拿到的数据是2001-3000。没找到1001,此时返回是ACK仍然是索要1001,无论当前传输的数据具体是什么,都在索要1001。此时主机A看到B这边连续的几个ACK都在索要1001,A就知道了1001这个数据丢了,于是重传1001-2000。重传后的下一个ACK,索要的就不再是1001了,而是7001了。上述重传中,没有额外的操作,哪个数据丢了,就重传哪个(快速重传),是滑动窗口下,超时重传的变种。

  • 接收端收到了 1001 之后,再次返回的ACK就是7001了(因为2001 - 7000)接收端 其实之前就已经收到了,被放到了接收端操作系统内核的接收缓冲区中

​ 如果通信双方,传输的数据量比较小,也不频繁,就采用普通的确认应答和超时重传。

​ 如果通信双方,传输的数据量更大,也比较频繁,就会进入滑动窗口模式,按照快速重传的方式处理。

​ 通过滑动窗口来传输数据,会提高效率。窗口越大,传输的效率也就越大。但是如果传输的速度太快,接收方就可能处理不过来。此时接收方也会出现丢包,发送方还得重传。TCP的前提是可靠性,需要在可靠性的基础上,再去提高效率.

7.流量控制机制

​ 站在接收方的角度,反向制约发送方的传输效率。发送方的发送速率,不应该超过接收方的处理能力。

在这里插入图片描述

​ 接收方的处理能力,就是B的消费速度,如果接收方缓冲区的剩余空间越大,意味着消费速度越大,处理能力越强。剩余空间越小,消费速度越慢,处理能力就越弱。

  • 接收方每次收到数据后,都会把接收缓冲区剩余空间的大小,通过ACK返回给发送方。放送方就会按照这个数值来调整下一轮的发送速度。

在这里插入图片描述

  • TCP报头中,有一个“16位窗口大小”,用来描述接收方缓冲区剩余空间的大小。
  • TCP报头的选项中,有一项是“窗口扩展因子”,能把窗口大小,进行左移操作。通过扩展因子,就可以让窗口大小表示一个更大的值。超出16位的限制。

​ 假设当前的接收方缓冲区的大小是4000,拿到1000的数据后,通过ACK报文,把剩余的3000大小,返回给发送方。发送方根据3000的大小,确认了这一轮发送的窗口大小为3000。发了三个1000大小的数据。此时假设接收方的应用程序还没来得及处理任何数据。此时每收到一个数据,缓冲区的剩余空间就缩小1000。每个ACK都会把剩余空间的实时情况返回给主机A。当返回0时,意味着告诉A,接收方已经满了,暂时先别发数据,此时A就要暂停发送。

​ 此时,暂停发送数据后,A虽然不传输业务数据了,仍然会周期性的发送一个“窗口探测包”,并不携带业务数据,只是为了触发ACK,从而查询接收方的接收缓冲区剩余空间。B消费一些后,等缓冲区剩余大小为2000时被A查询到后,下一轮就以2000为窗口大小进行数据的传输。从而达到主机B限制 主机A的发送速率。

8.拥塞控制机制

​ 流量控制,只是考虑的接收方的处理能力,实际上,整个通信的路径上,都需要考虑到各个结点的处理能力。而拥塞控制,就考虑到了这些中间结点的情况。

​ 问题在于,接收方的处理能力很方便进行量化。但是这些中间结点,结构更复杂,无法进行量化。因此可以使用“实验” 的方式,来找到一个合适的值。让A先按照比较慢的速度发送数据(小的窗口),如果数据传输过程非常顺利,没有丢包,再一步步尝试使用更大的窗口,更高的速度进行发送。随着窗口大小不断的增大,达到一定程度后,中间结点就有可能出现问题。这个结点就有可能会出现丢包的情况。发送方发现丢包了,就把窗口大小再调整小。此时如果发现还是继续丢包,继续缩小,如果不丢包了,就继续尝试变大。

​ 在这个过程中,发送方不停的调整窗口大小,逐渐达成 ” 动态平衡 “。就相当于把这些中间结点看成一个整体。通过实验的方式,来找到中间结点的瓶颈在哪里。

在这里插入图片描述

经典版本的拥塞控制,一旦丢包,就会一落千丈,回到最开始 的慢启动。TCP后续进行了改进。落到了新的阈值上,从新的阈值开始,进行线性增长。

  • 流量控制和拥塞控制,都是在限制发送方的发送窗口大小。最终实际发送的窗口大小,是取决于流量控制和拥塞控制中,窗口的较小值。

9.延时应答机制

​ 在正常情况下,A把数据传给B,B就会立即返回ACK给A。延时应答,说的是:A传输数据给B,B等了一会儿,再给A返回ACK。本质上也是为了提高传输效率。

​ 发送方的窗口大小,就是传输效率的关键。流量控制机制,就是根据接收方缓冲区的剩余空间,来决定发送速率的。如果能有办法,让得到的窗口再大一点,发送的速率就能更快一点。(前提是接收方能处理过来)。

​ 延迟应答,就是延迟返回的ACK,给接收方更多的时间,来读取接收方缓冲区的数据。此时,接收方读了数据之后,缓冲区剩余空间就变大了。返回的窗口大小也就更大了。从而提高传输效率。

10.捎带应答机制

  • 在延时应答 的基础上,进一步提高效率。

在网络通信中,往往是“一问一答”的通信模型。

​ ACK是内核立即返回的。response是应用程序来返回的。两者的时机不同。由于TCP的延时应答机制。ACK不会立即返回。会进行一定时间的等待。此时B正好把response计算好了。此时就会把response返回,顺便就把刚才要返回的ACK也带上。(两个数据合并成一个数据,只需要传输一次)

之前的四次挥手,有了延时应答和捎带应答,才能促成四次挥手在三次挥完。

11.面向字节流

粘包问题

面向字节流的机制,都会存在此类情况

这里的“包”,指的是应用层数据包。如果同时有多个应用层数据包被传输过去,此时就容易出现粘包问题。

在这里插入图片描述

  • 应用程序,无法知道哪些数据是一个完整的数据包
解决方法

思路:通过定义好应用层协议,明确应用层数据包之间的边界。

1.引入分隔符。

比如使用\n作为分隔符。\nccc\nbbb\naaa

2.引入长度。

应用程序在读的时候,先读两个字节,得到数据包的长度,再根据长度继续读取对应的字节个数

ccccc5bbbb4aaa3

应用层常见的自定义格式,比如xml、json等,都可以明确的区分数据包的边界。

12.异常情况的处理

在使用TCP的过程中,出现意外,该怎么处理?

1.进程崩溃

​ 进程没了,异常终止了。文件描述符表也就释放了。相当于调用了Socket.close()。此时就会触发FIN,对方收到后,会返回ACK和FIN,断开方再返回一个ACK(是一个正常的四次挥手过程)。

  • TCP的连接,独立于进程的存在,进程没了,TCP连接不一定消失。
2.主机关机(正常关机)

​ 在进行关机的时候,会先触发强制终止进程的操作。(相当于1的进程崩溃)

​ 此时就会触发FIN, 对方收到后会返回ACK和FIN。

​ 但是由于关机,不仅是进程没了,系统也可能会关闭。如果在系统关闭之前,对端返回是FIN和ACK到达了,此时系统还可以返回ACK,进行正常的四次挥手。但是如果系统已经关闭了,对端发送的ACK和FIN才来,就无法进行ACK的响应。站在对端的角度来看,对端以为是自己的FIN和ACK丢包了,会进行重传,重传几次都没有反应,就会放弃连接(删除对端的信息)

3.主机掉电(不正常关机)

​ 此时,是一瞬间的事情,来不及杀进程,也来不及发送FIN。主机直接就停机了,来不及任何操作。

1.站在对端的角度,如果对端(发送方)在发送数据(接收方掉电),发送方发送完数据后,会一直等待ACK ,进而触发超时重传。如果仍无法收到,就会触发TCP连接重置功能,发起RST“复位报文段”(TCP报头标志位的第四位)。如果复位报文段发出后,没有效果,此时就会释放连接(删除对端的信息)

2.如果对端(接收方)是在接收数据(发送方掉电),对端还在等待数据,等了半天没有消息。接收方此时是无法区分,是发送方没发消息,还是发送方挂了。通过心跳包机制来判断发送方是否存活。

心跳包机制

TCP提供了“心跳包”机制。

​ 接收方会周期性的向发送方发起一个特殊的、不携带业务数据的数据包,并且期望对方返回一个应答。如果这个过程重复了多次,对方仍没有应答,就视为对方挂了(心跳没了)从而单方面断开连接。

4.网线断开
  • 和主机掉电类似。

假设A正在给B发送数据时,网线断开了。

A迟迟无法收到ACK,会触发超时重传,进而触发连接重置,仍没有反应,进行单方面释放连接。

B就会触发心跳包机制,发现对方没有响应,就会单方面释放连接。

二、TCP和UDP的区别

TCP的优势:
  • 可靠传输 ,TCP适用于绝大部分场景
  • 如果要传输比较大的数据包,TCP更有优势(UDP有64KB的限制)
UDP的优势:
  • 更高的传输效率,适用于“对可靠性不敏感,对性能敏感”的场景(在局域网内部的主机进行通信)
  • 如果要进行“广播传输”,优先考虑UDP(支持广播)(比如投屏)
如何基于UDP实现可靠传输?

本质上是在考察TCP,在应用程序中实现TCP的机制。

1.确认应答 2.引入序号和确认序号 3.超时重传 4.滑动窗口

点击移步博客主页,欢迎光临~

偷cyk的图

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值