OSI参考模型详解之玩转传输层

前言

在OSI参考模型中,传输层是相对接近用户的一层,在进行网络编程的时候,是必不可少必须要了解的一层。对于传输层的协议有UDP协议,TCP协议,接下来将详细讲解这两种协议的作用以及底层原理。帮助大家更好的深入了解传输层的内容。

UDP

TCP

在传输层中进行处理过的数据叫做数据段(Segment),是双工通信的(对于后面理解TCP3次握手和4次挥手有帮助)就是服务端可以主动和客户端通信,客户端端同时也能与服务端发送消息,理解是两条通道。

1. 为啥要使用TCP

对于用户发送重要数据,则希望数据要么发送到对方,要么发送失败,总要有个明确结果。不然用户发送数据出去了,有没有被接收方接收到这些都不能确定的话,纯属在一个黑匣子里面,这样的话用户很不放心,所以TCP就是来解决这个问题的。

2. TCP概述

传输控制协议(TCP,Transmission Control Protocol)是一种面向连接的、可靠的、基于字节流的传输层通信协议,由IETF的RFC 793 [1] 定义。【百度百科】

3. TCP结构

在OSI每一层中都是有协议支持的,所以想了解TCP,就要去了解TCP是如何设计的,类别与应用程序代码中对于某个功能的实现,了解内部定义的一些细节方能更好的了解其原理。
在这里插入图片描述

3.1 TCP首部

首部占用20~60字节,其中20个字节是基础字段信息,40个字节是一些补充信息。头部定义了以下字段

  • 来源端口号(占用2个字节16位)进而知道端口号的范围是2^16-1=65535
  • 目的端口号(占用2个字节16位)
  • 序列号(Sequence Number):传输数据的一个字节编号
  • 确认号(Acknowledge Number)对于发送方的一个回应,只有当标志位ACK=1的时候才有效
  • 数据偏移:占用4位(0101~1111)。
  • 保留位:占用3位,目前是不用的
  • 标志位(Flag):占用9位,用来标识发送什么样类型的数据
  • 窗口(Window)占用2个字节,用来流量控制
  • 校验和(CheckSum)占用2个字节,用来校验数据的完整性
  • 紧急指针:占用2个字节,只有当标志位URG=1的时候才有效
  • 选项:占用40个字节。后面会详细介绍。

3.1.1 源端口/目的端口

在传输层中,会将数据进行封装。其中就会加入源端口号,和目的端口号。其实这两个字段其实到后面就会理解到是为了数据传输能够准确的送到到哪个端口。并且不同的端口号之间数据不串通。通过网络层封装的源IP+源端口+目的IP+目的端口表示一个socket。

  • 占用2个字节16位
  • 最大的数量也=2^16-1=65535
  • 所以客户端服务端中端口号最大就是65535

3.1.2 序列号(Sequence Number)、确认号(Acknowledge Number)

传输的数据的第一个字节编号。
因为一个大应用层数据要想发送给对方,一次性是不能发送的,所以需要切割成一段一段的在网络中传输给到对方。对方接受到数据要将数据都重新组合呀!所以他们要知道如何组合,并且接受到的数据段中,有没有丢失的段。

  • 占用4个字节
  • 建立连接之后,代表TCP数据的第一个字节编号

TCP三次握手序列号跟踪
使用wireshark进行抓包

  1. 客户端发起服务端连接
    发送标志位SYN
    在这里插入图片描述
    a) 客户端第一次发送建立连接请求,会给到一个初始化序列号到服务端,表明以后发送数据的时候,数据段第一个字节的序列号为初始化序列号+相对的序列号
    b) 相对序列号:(因为是数据段第一个字节的序列号)第一次建立连接,因为发送的数据长度为0,所以相对序列号为0嘛

  2. 服务端确认客户端连接,并也想建立和客户端的连接[ACK+SYN]
    在这里插入图片描述

  3. 客户端确认服务端的请求连接
    因为服务端发送了请求连接,那我一定要回一个才对嘛,要不然服务端也不知道到底客户端有没有收到自己发送的请求。
    在这里插入图片描述

  4. 正式发送http请求
    在这里插入图片描述

  5. 服务端确认收到了客户端发送的数据
    上面说了,TCP是可靠的,所以对于每次发送数据一定要有个确认,不管失败还是成功,如果没有收到,则在有效的规则内一直发送。(有效规则后面会介绍,例如总不能一直无限发送吧)
    a) 确认收到了86个字节
    b) Ack=87,接下来发送
    在这里插入图片描述

  6. 接下来也是一样的分析,服务端给客户端发送数据,可自行分析

总结

  1. 发送建立请求连接方会初始化一个序列号给相对应的发送方
  2. Seq序列号是建立完成发送内容(内容数据,不是首部)的第一个字节编号。
  3. Ack确认号是对发送方发送的数据的确认,并且告诉发送方接下来如果发送数据则发送Ack的序列号数据

3.1.3 数据偏移

  • 占用4位
  • 范围 0101~1111 =>十进制(4到15)
  • 数据部分相对首部最前面偏移了多少。反向的就是说首部占用了多大。
  • 偏移量*4=首部长度(20到60字节)
  • 4表示含义:4其实是一个系数,约定好的,因为4位最大也就是15,但是15又不能表达真实的数据大小,两种方案,要么位数增加点,这样就要多点空间。要么就约定好一个系数,真实数据=偏移量*系数。 后面还有很多这样的设计。

3.1.4 标志位

因为发送数据段的时候,有很多中情况,例如这次是发送数据,还是建立连接,还是断开连接等,所以为了区分发送的数据是表示什么含义,进而有了标志位的出现。不同的标志位表示不同的含义。
在这里插入图片描述

3.1.4.1 UGE(Urgent)

紧急的,这个字段如果为1的时候,则紧急指针字段才有效。表示当前的数据段中有紧急数据要优先发送。

3.1.4.2 ACK(Acknowledgment)

确认标志,当该字段为1时,确认序号才有效。

3.1.4.3 PSH(Push)

因为接收方有缓存区,当应用端发完一个完整的报文时候,不想让接收方放在缓存区,而是直接向上传输到应用层。

3.1.5.4 RST(Reset)

当该字段为1的时候,表示两端连接出现问题,必须断开重新建立握手连接。

3.1.5.5 SYN(Synchronization)

该字段为1表示要建立连接请求

  • SYN=1,ACK=0 表示发送建立连接请求
  • SYN=1,ACK=1 表示确认建立请求,并且发送一个建立请求(这个是表示两个意思)

3.1.5.6 FIN(Finish)

该字段为1的时候,表示要断开连接。后面详解4次挥手~

3.1.5.7 前3位

前3位一般为0,作为保留位。很多书籍上也将这3位划分到保留位中,一共6位保留位。

可靠传输

TCP是如何实现可靠传输呢?在前面也说到了,既然要保证可靠传输,就是接收方,发送方两方一定要确保数据的最终状态(不管是收到还是没有收到),收到要给个应答,没收到应答在一定时间内等待,结束之后重试,默认是没有送达。所以可靠传输,一定是要对方收到发送的数据。

ARQ(Automatic Repeat Request)

TCP开始使用ARQ停止等待协议来确保数据的送达。
发送方发送一段数据出去之后,一直等待着对方收到消息,并发送ACK确认消息之后,才会发送下一组数据。但是这其中分好多种情况。(图来自网络)
1. 无差错的情况

  • 发送方发送数据给接收方
  • 接收方收到数据,并发送确认消息
  • 依次上面的步骤,中间没有差错
    在这里插入图片描述

2. 确认丢失数据
就是发送方发送数据给接收方,这个包在传输过程中丢失了。
2.1 A发送数据给B,B收到数据然后发送ACK消息,但是ACK消息丢了

  • A 这个时候一直等待B的ACK消息,但是一直没等到(在自己设置的等待时间内),会重新发送消息给B
  • 这个时候B发现又收到了个同样的消息,这个时候A会丢掉这个消息,再次回复ACK给B

2.2 A发送数据给B,这个数据就直接丢了,处理方式和上面2.1是一样的,只不过A是第一次收到消息~
在这里插入图片描述
3. 超时重传
在A发送消息给B的时候,由于网络延迟的关系,A在发送消息出去之后,规定时间内没收到B的ACK消息,然后又重发了同样的数据,这个时候B收到两个一样的数据,B的处理方式是直接丢掉重复的,只保留一个,然后回复一个ACK。
在这里插入图片描述
问题

上述异常情况下(超时重传,确认丢失)会一直重试?假如真的是一直有这种异常,难道无线死循环吗?

解决:一般操作系统内部有个参数,重复传了N次之后还是失败,直接就发送RST(Rest)消息断开TCP,需要重新建立连接了

总结
ARQ协议保证了数据能完整的发送出去,但是这种一段一段的发送数据效率很慢。为了解决这个问题,后来引进了联系ARQ协议。

连续ARQ+滑动窗口

ARQ协议解决了消息能准确的发送出去,并确认。但是这种一段一段效率比较慢。后来就演进了联系ARQ+滑动窗口来解决效率问题。
先看一张图(来自百度网络)
在这里插入图片描述
窗口
在TCP建立连接的时候,接收方会返回一个窗口大小的字段给到发送方,就是用来告诉发送方连续能发送多少数据给到接收方。

  • 假设建立连接接收方告诉发送方窗口大小10000字节
  • 然后发送端每次发送数据段1000字节
  • 那么可以连续发送10个数据段

连续ARQ协议+滑动窗口

  • ARQ协议还是上面所说的确认机制。
  • 连续ARQ是联系发送过N条之后再确认一次。(如何确认后面介绍)
  • 然后窗口移动,再发送接下来的一组数据。
    在这里插入图片描述

问题

如果接受方窗口能接收10个,但是发送方只有两个数据段发送,还有8个发送?服务端会一直等待着吗?

答:接收方会等待接受第3个。如果超时了没有收到第3个,则直接会确认。

SACK(选择性确认)

由于连续ARQ协议+滑动窗口保证了数据快速发送。但是这种理想状况很好,但是连续发送的过程中如果其中一个或多个丢了,也是要处理的。

使用限制
能否使用SACK取决于在建立连接的是,首部选项中有个SACK选项。
在这里插入图片描述

场景

  • 连续发送了数据段1,2,3,4,5,6
  • 数据3,4丢失了

方式一

  • 接收方直接确认收到2
  • 发送方这个时候就要重发后面的数据3,4,5,6
  • 这样在连续数据发送中,越前面的数据丢失,效率越低,因为后面的数据又要重传。

方式二
由于第一种方式效率不咋地,所以应该升级,应该是谁丢了重发谁。

  • 这个时候借助了TCP首部选项字段,将要重新发送的数据发送到这里。
  • Kind占用一个字节,值为5表示SACK选项
  • Length占一个字节。表明SACK一共有多少个字节。
  • Left Edge:占用4个字节,左边界
  • Right Edge:占用4个字节,右边界
  • 所以一对边界占用8个字节,TCP的首部选项部分占用40个字节
  • 所以最多确认消息数量=(40-2)/8=4(组)
    在这里插入图片描述

问题

拆包是放在传输层还是网络层。
由于数据链路层MTU最大是1500字节,所以网络层传给链路层会进行拆包。那么传输层超过1500字节是要拆包还是网络层拆。

答:在传输层就应该拆包,因为本质区别就是需要保证数据传输可靠,所以就要TCP拆,要不然保证不了数据被网络层拆包之后能不能送达的问题。

流量控制

流量控制其实就是接收方要控制发送方发送数据的频率以及大小。因为接收方有个缓存区,用来接收数据的,如果满了,还一直往里面存,会导致数据丢失。所以接收方要控制发送方发送。

实现方式

上面有说到窗口字段,其实还是通过确认窗口来动态限制发送方发送的速率。

  • 接收方发现缓存区快满了,就在接收到数据消息的时候,在ACK段里面调整窗口大小。
  • 因为发送方发送数据不能超过接收方窗口大小,从而限制了发送数据大小和速率
  • 如果接收方给到的窗口大小是0的时候,发送方会停止发送数据
  • 如果窗口大小是0,如果不去解决,则如果接收方有空闲窗口能接收数据,则发送方还在等待
  • 窗口为0发送方会设置定时器,定时发送个测试报文询问接收方窗口大小,还为0,则刷新定时器,否则直接发送数据
    在这里插入图片描述

拥塞控制

上面说到的流量控制是接收方和发送方之间的问题,是点对点的控制双方接受数据能力。而拥塞控制则说的是通信整条路上的数据拥塞情况,因为一个服务不只是针对于一个客户端工作的,而且一条线路不只是为一个服务提供线路的。当某个服务大量占用资源,导致别的服务流量不能到达。进而出现问题。

使用的方法

  • 慢开始(slow start)
  • 拥塞避免(congestion avoidance)
  • 快重传(fast retransmit)
  • 快恢复(fast recovery)

使用的字段限制

  • MSS(Maximum Segment Size)数据段的最大长度,建立连接的时候确定
  • cwnd(congestion window)拥塞窗口
  • rwind(receive window)接收窗口
  • swind(send window)发送窗口 =min(cwind, rwind)

慢开始(slow start)

拥塞避免(congestion avoidance)

快重传(fast retransmit)

快恢复(fast recovery)

详解TCP3次握手

其实如果认真阅读了上面内容,其实三次握手是很容易理解的。还是一句话,需要消息可靠到达,首先要建立可靠的信道,我能发送给你,你能发送给我。然后每次发消息要确认收到。
在这里插入图片描述

  1. 客户端主动向服务端发送连接请求(可理解为我要申请一条给你发送消息的通道)这个消息的标位:SYN=1,初始化序列号Seq=x(上面也讲了序列号的作用)【SYN-SEND】状态
  2. 服务端收到了SYN=1建立连接请求呀,服务端告诉客户端行。所以要发送一个确认信号ACK=1,但是一般服务端,客户端都是双向通信的,此时服务端也要想发数据给客户端本来也要发送一个SYN=1的信号,所以为了减少一次通信直接发送[SYN=1,ACK=1,Seq=y,Ack=x+1]【SYN-RCVD】状态
  3. 此时客户端知道了,我可以和你发数据了,然后服务端也要和我建立连接。我就同意呗发送[ACK=1,Syn=x+1,Ack=y+1]【ESTABLISHED状态】

抓包分析
在这里插入图片描述

问题1:既然可以直接连接对方,发送数据,为啥还要建立连接过程呢?

答:其实问这个问题,还是不了解TCP,其实不要连接是UDP的通信过程,因为TCP要保证可靠的连接,所以不仅仅是说建立这么简单,里面还有很多信息,例如数据段最大长度MSS,窗口大小,能否进行SACK确认,序列号,缩放窗口系数等好多信息,所以一般请求连接首部是32个字节,肯定要大于最小的20个字节。

问题2:建立连接为啥要三次,两次不行吗

答:其实这个还是为了确定性,如果省略了第三步,服务端也不知道我发送的连接客户端有没有收到,而且是否让我连接。如果认为收到了,并且可以连接,那么真实情况是不让连接,或者没收到,服务器一直会维护这个连接,占用系统内存资源。所以TCP中好多定时器,如果不能给我确认,在定时器时间内重发。 多次失败之后发送RST信号。重新建立连接

问题3:多次重试之后会进入死循环

答:回答第二个问题基础上,如果有失败步骤,会定时重发,为了不陷入死循环,在一定次数之后还是失败,则发送RST信号,断开连接重连。

详解TCP4次挥手

当双方通信完成之后,就需要断开连接。这里断开连接说的是,双方都要自己断开,客户端觉得自己自己不需要发送消息了,就断开发送给服务端的信道。服务端如果也发送完了,则也要发送断开请求。断开请求可以随便哪方主动发起,没有先后顺序。
在这里插入图片描述
流程

  1. 客户端没数据发送,发送断开连接请求【FIN=1,ACK=1,Seq=u,Ack=v】然后自己进入了FIN-WAIT-1状态。还能收数据
  2. 服务端同意你断开连接【ACK=1,Seq=v,Ack=u+1】
  3. 客户端此时收到了服务端确认,本来是直接进入关闭状态【close】但是它也要等待服务端的关闭连接,所以进入了FIN-WAIT-2状态
  4. 服务端发现自己也没有消息发送给客户端,也发送一个【FIN】信号给客户端,进入last-ack阶段
  5. 此时客户端收到了服务端的FIN信号,好了,我就可以等待关闭了进入【TIME-WAIT】,此时这个状态会等待2倍MSL才会关闭。后面会将为啥2倍MSL
  6. 客户端发送ACK确认服务端可以断开了,服务端进入关闭。
  7. 客户端在TIME-WAIT时间到了之后, 也关闭。

问题1:为啥需要4次挥手

答:因为TCP是双向通信的,两个信道,客户端要关闭服务端,服务端也要关闭客户端。所以一问一答就是4次。但是有的抓包情况是3次,原因是主动断开连接的一方发送了断开连接请求,此时接收方发现自己也没有数据发送给它,也要发送断开,此时两步可以合成一步来完成。

问题2:为啥主动断开连接有个2MSL

  1. MSL(Maximun Segment LifeTime)数据段的最大生命周期,具体看操作系统如何实现,定义这个时间,一般2分钟。
  2. 因为服务端(被动方)也发送了关闭连接请求,所以我要不断的去ACK确认,每次ACK都会刷新这个时间。一旦在这个周期内没有发现服务端再有FIN消息,则表明它收到了ACK,关闭了连接。在等待完2MSL之后也会主动关闭。
  3. 因为一个数据段的生命周期是MSL,我要确保服务端发送的消息一定是被我接受到,所以我要等待,否则如果我立即断开连接,服务端没收到我的ACK,他会继续发送FIN信号,如果此时又有一个程序正好建立了同样客户端的连接。那是不是刚上线就断开了。所以取最坏情况的时间【ACK信息和FIN信息】2MSL
tcp
本作品采用《CC 协议》,转载必须注明作者和本文链接
《L05 电商实战》
从零开发一个电商项目,功能包括电商后台、商品 & SKU 管理、购物车、订单管理、支付宝支付、微信支付、订单退款流程、优惠券等
《L03 构架 API 服务器》
你将学到如 RESTFul 设计风格、PostMan 的使用、OAuth 流程,JWT 概念及使用 和 API 开发相关的进阶知识。
讨论数量: 2

没看懂,但是感觉好厉害 :joy:

2年前 评论

这些大厂面试很大的概率会问

2年前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!