0
点赞
收藏
分享

微信扫一扫

uniapp快速开发小程序全流程

小飞侠熙熙 2023-07-19 阅读 73

文章目录

一、TCP协议格式

在这里插入图片描述

  • TCP报头当中各个字段的含义如下
  • TCP报头当中的6位标志位:

1.1 TCP如何将报头与有效载荷进行分离?

观察TCP协议格式,报文部分把选项除去一共是20字节。所以我们可以先取20字节。而其中的四位头部长度就表示的是报头的大小,根据这个就可以计算出选项的大小。
读取完TCP的基本报头和选项字段后,剩下的就是有效载荷了。

  • 关于四位头部长度

1.2 有效载荷如何向上交付?

因为应用层的每个进程都会绑定一个端口号:

  • 服务端显示绑定一个端口号
  • 客户端由操作系统自动绑定一个端口号

上面把报头提取了出来,而报头里含有目的端口,就可以向上找到对应的协议了。

补充:内核中用哈希的方式维护了端口号与进程ID之间的映射关系,因此传输层可以通过端口号快速找到其对应的进程ID,进而找到对应的应用层进程。绑定映射关系的时机:bind端口的时候。

1.3 TCP报头的理解

跟上一章讲的UDP报头一样,TCP报头就是一个结构化对象:
【网络编程】传输层协议——UDP协议

也是内核创建一块内存,后边就拷贝有效载荷,前面就强转成结构化数据然后填写每个字段。

1.4 序号与确认序号

在讲需要与确认序号之前先引入网络可靠性的概念:

1.4.1 网络不可靠问题

现在计算机基本都是基于冯诺依曼体系结构:
在这里插入图片描述
上图的这些设备虽然都在一台机器上,但它们都是独立的硬件设备,它们之想要进行数据交互,就必须要进行通信。因此这几个设备实际是用“线”连接起来的,其中连接内存和外设之间的“线”叫做IO总线,而连接内存和CPU之间的“线”叫做系统总线

所以网络传输的不可靠问题本质就是距离变长了。

  • 不可靠问题场景

怎么保证自己说的话对方听到了呢?答案是得到对方的回复(应答),只有收到了应答,才能保证历史消息被对方收到了。只有确认了应答,才算可靠。
而双方通信一定会存在最新消息,最新消息一般是无法保证可靠的

由上面描述可知没有绝对的可靠性,只有相对的可靠性。

所以双方进行通信的时候可能除了正常的数据段,还会包含确认数据段
在这里插入图片描述
如图是双方通信使用串行方式,即只有收到了确认应答才会继续发送数据。这样的效率可想而知是非常低的。

实际工作中不会这样,而是一方同时发送多条数据段,只要保证所有数据段都有应答即可。

在这里插入图片描述
但此时就会有一个问题,那就是这么些数据段到达对面的顺序不一定就是发送的顺序
比方说发送了四个数据段,结果只收到了三个确认应答,那么怎么知道是哪个数据段发送失败了呢?

1.4.2 32位序号

解决上面的问题就是TCP报头中的32位序号字段。
TCP将发送出去的每个数据段都进行了编号,这个编号叫做序列号。

这样就保证了传递数据段的有序性。
举个例子:

1.4.2 32位确认序号

TCP报头当中的32位确认序号是告诉对端,我当前已经收到了哪些数据,你的数据下一次应该从哪里开始发

在这里插入图片描述

比方说客户端发送的数据段的序号是1,报文中含有1000字节的数据,如果服务端收到了,那么就会把返回给客户端的响应报头中的32位确认序号填写成1001,那么这个1001就有两层含义:

通过序号和确认序号就可以表示:
接收方已经收到ACK序号(确认序号)之前的所有(连续)报文。

举个例子:
发送的数据都是1000字节大小。
在这里插入图片描述
如果序号1001的数据段没有传递到主机B,其他的传递到了主机B,那么1001之后的数据段的确认序号都只能填写1001。这表明的就是序列号在1001之前的数据段都被收到了。


  • 为什么要有两组序号?

为什么不能把32位序号和32位确认序号压缩为一个字段,发送的时候就填序号,返回的时候就填确认序号?

如果是一端发送数据一段接收数据当然可以使用这种方式,但是TCP是全双工的,双方可能同时要给对方发送消息。
双方发出的报文当中,不仅需要填充32位序号来表明自己当前发送数据的序号。还需要填充32位确认序号,对对方上一次发送的数据进行确认,告诉对方下一次应该从哪一字节序号开始进行发送。

1.5 窗口大小

首先要知道TCP是有自己的发送缓冲区和接收缓冲区。

这样就会导致两种种情况:
发送数据过快,导致接收缓冲区被打满,剩下的报文都会被丢弃掉。
发送数据过慢,影响到上层的业务处理。

既然如此,TCP就要控制传输速度。所以必须要知道对方缓冲区的接受能力。也就是接收缓冲区剩余空间的大小。

  • 怎么知道对方缓冲区的剩余空间呢?
  • 窗口大小字段越大,说明接收端接收数据的能力越强,此时发送端可以提高发送数据的速度。

  • 窗口大小字段越小,说明接收端接收数据的能力越弱,此时发送端可以减小发送数据的速度。

  • 如果窗口大小的值为0,说明接收端接收缓冲区已经被打满了,此时发送端就不应该再发送数据了。

  • 补充一点

1.6 六个标志位

  • 为什么会有标志位?

在这里插入图片描述

  • SYN
  • FIN
  • ACK
  • PSH
  • URG & 16位紧急指针

这里要注意并不是说这个数据段的有效载荷的所有部分都要被紧急处理,可能只是一小部分,那么怎么找到位置呢?

URG一般用来发送带外数据,它不用走TCP流,因为接收方直接处理。比方说我们现在发了很多数据,对方正在处理,但是我们突然发现不需要这些数据了,此时就可以发送紧急带外数据,把套接字关了。

  • RST

二、确认应答机制(ACK)

TCP保证可靠性的机制之一就是确认应答机制。

确认应答机制是靠TCP报头中的32位序号和32位确认序号实现的,收到的确认应答说明该序号之前的数据全部被收到了。

  • 如何理解TCP将每个字节的数据都进行了编号?

在这里插入图片描述

我们可以把传输层的发送缓冲区看成一个数组,当我们把应用层的数据拷贝到发送缓冲区的时候,每个字节的数据就天然的有了一个编号(下标),只不过这个下标不是从0开始的,而是从1开始往后递增的。

在这里插入图片描述

2.1 超时重传机制

2.1.1 丢包的两种情况

  • 情况一

发送的数据报文丢失了,此时发送端在一定时间内收不到对应的响应报文,就会进行超时重传。
在这里插入图片描述

  • 情况二

对方发来的响应报文丢包了,此时发送端也会因为收不到对应的响应报文,而进行超时重传。
在这里插入图片描述


当出现丢包情况的时候,发送方是不会知道究竟是数据段发送的时候丢包了还是确认应答的时候丢包。所以发送方只能进行超时重传。

那么如果是第二种丢包情况,接收方就可能会收到份同样的数据。因为重复的报文也是不可靠的一种,所以主机B需要进行去重(通过序号)。

因为需要超时重传,所以数据发送出去后不会立即清除,而是保留一段时间。直到收到该数据的响应报文后,发送缓冲区中的这部分数据才可以被删除或覆盖。

2.1.2 超时重传的等待时间

我们通过超时来判断是否丢包,那么这个时间到底是多久呢?

我们知道数据发送的时间是由网络状况决定的,而网络会因为环境的变化不断变化。所以超时重传的时间一定不是固定的

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

三、连接管理机制

3.1 面向连接相关概念

面向连接是通过要连接的两台主机分别在自己的主机上开辟一块区域,然后通过TCP协议来共同维护这两块区域,来实现网络传输的可靠性。所以,面向连接就是为了保证数据的可靠性

这样说可能还不是很好理解,那么在对比一下面向无连接理解一下。UDP协议就是典型的无连接协议,在两台主机之间网络通信时,不需要知道目标主机ip和目标端口是否存在,可以按照定义的ip和端口直接发送到网络中,而面向连接则是先根据给定的目标ip和端口号发送到网络中一些消息来确认目标主机是否存在,如果不存在则不能完成接下来的网络通信。

所以,面向连接是需要先建立连接才能进行网络通信的,建立连接就是确定对方存在并协商好一些控制量来确保接下来的通信是可靠的。

  • 无连接协议和面向连接协议的概念
  • TCP为什么要建立连接?

3.2 三次握手

双方在进行TCP通信之前需要先建立连接,建立连接的这个过程我们称之为三次握手。

在这里插入图片描述
第一次握手:客户端向服务器发送的报文当中的SYN位被设置为1,表示请求与服务器建立连接
第二次握手:服务器收到客户端发来的连接请求报文后,紧接着向客户端发起连接建立请求并对客户端发来的连接请求进行响应,此时服务器向客户端发送的报文当中的SYN位和ACK位均被设置为1。
第三次握手:客户端收到服务器发来的报文后,得知服务器收到了自己发送的连接建立请求,并请求和自己建立连接,最后客户端再向服务器发来的报文进行响应

  • 为什么要三次握手?

建立连接时不是百分之百成功的,三次握手的任何一次都有可能丢包,前两次握手是能够保证被对方收到的,因为它们都有应答,如果没有,大不了超时重传,但是如果是第三次ACK应答丢了呢?
在这里插入图片描述
当客户端发送ACK应答的一瞬间,它就会认为三次握手已经建立成功了,此时如果ACK应答丢了,此时就会连接建立失败,但是根本不用担心,有解决方案:

  • 一次和两次握手行不行?

上面会导致单机攻击服务器的本质原因是客户端还没有建立连接时服务端已经建立好连接了。所以必须让客户端先建立连接,服务端再建立连接。


现在就可以说明为什么要三次握手了:

  • ddos攻击
    这里要注意三次握手并不能解决安全问题,比方说大量的主机同时发送TCP请求也会导致服务端崩溃。假设黑客黑掉了很多主机同时给服务端发送TCP连接请求:
    在这里插入图片描述
    此时再有客户端发送连接请求,服务端就提供不了服务了(连不上),这种攻击手段就是ddos攻击(服务拒绝攻击)。

  • 四次握手行不行?
  • 三次握手的状态变化

在这里插入图片描述

最开始时客户端和服务器都处于CLOSED状态。
1️⃣ 服务器为了能够接收客户端发来的连接请求,需要由CLOSED状态变为LISTEN状态。
2️⃣ 此时客户端就可以向服务器发起三次握手了,当客户端发起第一次握手后,状态变为SYN_SENT状态。
3️⃣ 处于LISTEN状态的服务器收到客户端的连接请求后,将该连接放入内核等待队列中,并向客户端发起第二次握手,此时服务器的状态变为SYN_RCVD。
4️⃣ 当客户端收到服务器发来的第二次握手后,紧接着向服务器发送最后一次握手,此时客户端的连接已经建立,状态变为ESTABLISHED。
5️⃣ 而服务器收到客户端发来的最后一次握手后,连接也建立成功,此时服务器的状态也变成ESTABLISHED。

  • 套接字和三次握手之间的关系

3.3 四次挥手

由于双方维护连接都是需要成本的,因此当双方TCP通信结束之后就需要断开连接,断开连接的这个过程我们称之为四次挥手。

哪边不想给对方发消息了,就要发送断开连接请求,比如说客户端要断开连接:

这里有一个问题,既然前面客户端都已经说明不给服务端发送数据了,为什么后边还会发送确认应答呢?

  • 四次挥手时的状态变化

在这里插入图片描述

在挥手前客户端和服务器都处于连接建立后的ESTABLISHED状态。
1️⃣ 客户端为了与服务器断开连接主动向服务器发起连接断开请求,此时客户端的状态变为FIN_WAIT_1。
2️⃣ 服务器收到客户端发来的连接断开请求后对其进行响应,此时服务器的状态变为CLOSE_WAIT。
3️⃣ 当服务器没有数据需要发送给客户端的时,服务器会向客户端发起断开连接请求,等待最后一个ACK到来,此时服务器的状态变为LASE_ACK。
4️⃣ 客户端收到服务器发来的第三次挥手后,会向服务器发送最后一个响应报文,此时客户端进入TIME_WAIT状态。
5️⃣ 当服务器收到客户端发来的最后一个响应报文时,服务器会彻底关闭连接,变为CLOSED状态。
6️⃣ 而客户端则会等待一个2MSL(Maximum Segment Lifetime,报文最大生存时间)才会进入CLOSED状态。

  • 套接字和四次挥手之间的关系

主动断开连接的一方最终状态是TIME_WAIT
被动断开连接的一方两次挥手完成后的状态是CLOSE_WAIT

我们主要研究的就是这两个状态:

  • CLOSE_WAIT状态
  • TIME_WAIT状态

次挥手前三次如果发生了丢包情况,我们都可以利用超时重传机制,最担心的自然是第四次ACK应答时丢包

所以TIME_WAIT会保证最后一个ACK应答尽量被对方收到,而且可能断开之前发送的报文还滞留在网络中,那么TIME_WAIT就可以保证双方通信信道上的数据在网络中尽可能的消散。

而TIME_WAIT状态的时间过长,也会自动关闭连接,那么这个时间多长呢?

  • TIME_WAIT的等待时长

我们把从发送方到接收方经过的最大时间叫做MSL。

TIME_WAIT的等待时长设置为两个MSL的原因:

Centos7上默认配置的值是60s

3.4 bind绑定失败

  • bind绑定失败原因

服务器不能立即重启的危害:

那么怎么解决这个问题呢?

  • 设置套接字复用
int opt = 1;
setsockopt(listenfd, SOL_SOCKET, SO_ REUSEADDR, &opt, sizeof(opt)) ;

四、流量控制

TCP支持根据接收端的接收数据的能力来决定发送端发送数据的速度,这个机制叫做流量控制

上面在讲16位窗口大小的时候就说了传输数据的时候速度要适中,所以报头中有16位窗口大小,来控制传输速度,通过填写16位窗口大小告诉对端自己的接收能力(接收缓冲区还剩多少)。

但是发送方怎么在第一次就知道对方的接受能力呢?

关于窗口大小如何控制传输速度已经在上面讲过,不做赘述。

这里并补充一点:
当发送端得知接收端接收数据的能力为0时会停止发送数据,此时发送端会通过以下两种方式来得知何时可以继续发送数据:

这两种策略在实际中是同时使用的,哪个先到就处理哪个。

五、滑动窗口

在我们发送数据但是没收到应答之前,我们必须把数据暂时保存起来,以支持后续可能出现的超时重传。那么保存在哪里呢?

前面说过,多个报文一般是并行发送,即还没收到应答,下一个报文就已经发送出去了,目的是为了提高效率。

那么我们就可以把发送缓冲区分成三个部分:

  • 已经发送并且已经收到ACK的数据。
  • 已经发送还但没有收到ACK的数据。
  • 还没有发送的数据。

在这里插入图片描述
滑动窗口的本质就是发送缓冲区的一部分。 通过不断地滑动来重新划分三段区间。

  • 如何理解滑动窗口?

在这里插入图片描述
把缓冲区看成一个数组,那么滑动窗口的移动其实就是下标进行更新。

  • 滑动窗口的大小
  • 滑动窗口一定会整体右移吗?
  • 滑动窗口如何滑动?

当发送端收到对方的响应时,如果响应当中的确认序号为ACK_SEQ,窗口大小为tcp_win,此时就可以将win_start更新为ACK_SEQ,而将win_end更新为win_start + tcp_win

在这里插入图片描述
那么如果对方的上层一直不取走数据,发送发却一直发,就会导致tcp_win越来越小,也就是滑动窗口的左侧一直向后移动,右侧却不变,最终滑动窗口会变为0。

  • 如果收到的ACK不是最左侧数据的确认,而是中间的怎么办?
  • 丢包问题

丢包可以分为两个情况:
1️⃣ 数据没丢,ACK应答丢了
根据确认序号的定义,如果收到的是3001,那么说明3000以前的数据全部都收到了,那么就把win_start移动到3001即可。

2️⃣ 数据真的丢了
在这里插入图片描述
当1001-2000的数据包丢失后,发送端会一直收到确认序号为1001的响应报文,就是在提醒发送端“下一次应该从序号为1001的字节数据开始发送”。

而如果连续收到三个同样的确认序号,就会触发重传机制。 这也叫做快重传:
快重传是能够快速进行数据的重发,当发送端连续收到三次相同的应答时就会触发快重传,而不像超时重传一样需要通过设置重传定时器,在固定的时间后才会进行重传。

总结一下:
滑动窗口的左端就是通过确认序号确定的,右端是通过左端和对方接收缓冲区的剩余空间决定的。

  • 滑动窗口空间问题

六、拥塞控制

1000个报文丢掉一两个很正常,重复发即可,但是如果1000个报文有999个都丢了,那我们还要重传么?

针对这种大面积的丢包情况,TCP就会考虑是网络拥塞问题,此时重传就没什么用了,重传也只会加重网络故障问题。

  • 如何解决网络拥塞问题?

需要注意的是,网络拥塞时影响的不只是一台主机,而几乎是该网络当中的所有主机,此时所有使用TCP传输控制协议的主机都会执行拥塞避免算法。

  • 拥塞控制

在讲慢启动机制之前先引入一个概念:拥塞窗口
其实就是一个数字,再超过这个数字的时候就可能引发网络拥塞问题。
最开始的时候定义为1,每次接收到一个ACK应答,就加1,每次发送数据包的时候,将拥塞窗口和接收端主机反馈的窗口大小做比较,取较小的值作为实际发送数据的窗口大小,即滑动窗口的大小

滑动窗口大小 = min(拥塞窗口,窗口大小(对端的接受能力))


每收到一个ACK应答拥塞窗口的值就加1,此时拥塞窗口就是以指数级别进行增长的,如果先不考虑对方接收数据的能力,那么滑动窗口的大家就只取决于拥塞窗口的大小,此时拥塞窗口的大小变化为:1 2 4 8 ……
但我们知道指数增长是非常恐怖的,此时就有可能导致网络再次拥塞。

如图:
在这里插入图片描述
前期慢开始是为了让网络自主恢复,后面指数增长是为了尽快恢复通信。

七、延迟应答&捎带应答

  • 延迟应答

现在接收方缓冲区有很多数据,但是应用层有很大概率会马上把数据拿走,如果等一等再应答就可以返回更大的窗口

需要注意的是,延迟应答的目的不是为了保证可靠性,而是留出一点时间让接收缓冲区中的数据尽可能被上层应用层消费掉,此时在进行ACK响应的时候报告的窗口大小就可以更大,从而增大网络吞吐量,进而提高数据的传输效率。

此外,不是所有的数据包都可以延迟应答。

  • 数量限制:每个N个包就应答一次。
  • 时间限制:超过最大延迟时间就应答一次(这个时间不会导致误超时重传)。

延迟应答具体的数量和超时时间,依操作系统不同也有差异,一般N取2,超时时间取200ms。


  • 捎带应答

我们知道接收方收到数据要给发送方一个应答,如果刚好接收方也要发送数据,是不是可以直接一起返回。

捎带应答最直观的角度实际也是发送数据的效率,此时双方通信时就可以不用再发送单纯的确认报文了。

八、TCP衍生问题

8.1 面向字节流

当创建一个TCP的socket时,同时在内核中会创建一个发送缓冲区和一个接收缓冲区。
由于缓冲区的存在,TCP程序的读和写不需要一一匹配,例如:

  • 写100个字节数据时,可以调用一次write写100字节,也可以调用100次write,每次写一个字节。
  • 读100个字节数据时,也完全不需要考虑写的时候是怎么写的,既可以一次read100个字节,也可以一次read一个字节,重复100次。

实际对于TCP来说,它并不关心发送缓冲区当中的是什么数据,在TCP看来这些只是一个个的字节数据,它的任务就是将这些数据准确无误的发送到对方的接收缓冲区当中就行了,而至于如何解释这些数据完全由上层应用来决定,这就叫做面向字节流

这里就可以对比UDP,UDP不是面向字节流的,发一次必须就要读一次,发10次就必须读十次。这种报文和报文在传输层有明显边界的的协议就叫做面向数据报

8.2 粘包问题

  • 什么是粘包?
  • 如何解决粘包问题?

8.3 TCP连接异常

  • 进程终止

两个已经建立连接的进程,其中一个进程突然挂掉了,此时建立好的连接会怎么样?

  • 主机重启
  • 拔网线/断电源

九、总结

TCP协议这么复杂就是因为TCP既要保证可靠性,同时又尽可能的提高性能。

  • 可靠性
  • 提高性能
  • 如何用UDP实现可靠传输?

其实就是参考上面的可靠性。



举报

相关推荐

0 条评论