一、网络中的标准封层
1、TCP/IP
- 应用层 : 进程之间进行具体沟通 (具体的信息)
- 传输层 : 端对端的数据交换; 端: 进程 (进程之间可以通信)
- 网络层 : WAN 设备和设备之间实现数据交换,跨 LAN 工作 (跨 LAN ,主机间通信)
- 数据链路层 : 在一个局域网 LAN 内部的相关事宜 (LAN 内部主机之间的通信)
- 物理层 : 例如两台设备可以直接连接 (直接连通)
补充
- 发送过程中:应用层–》传输层–〉网络层–》链路层, 每一层都会在数据包中添加自己必要的信息进去(封装)
- 接受过程中,链路层–〉网络层–》传输层–〉应用层, 每一层都需要负责把这一层的相关信息删除掉(解包)
- 数据包中每层都应该携带两个重要信息:
- 如何分割每层添加的数据(长度、边界) 为了解包
- 具体交给上层的那个协议进行处理,为了分用
- 同一台主机上,端口只能被一个进程所占用,即使多个进程属于同一个程序.
- 常用端口
http :80 https: 443 ssh: 22 mysql: 3306 redis:6379
只要有端口,一定是传输层以上级别。传输层上又有两个重要的协议:TCP 、UDP; 四元组的基础上 + 协议号 =》五元组
- 网络编程 : 利用 os 提供的套接字(socket) ,进行网络通信内通的编程;
- 套接字(socket) : 插口 ,相当于通过 socket 才能真正使用 os 提供的 TCP、UDP 其他网络服务。
2、传输层重要协议: 进程 to 进程
2.1 UDP User Datagram Protocol 用户报文协议
- 没有任何特点, 和 TCP 对比时: 不可靠、无连接、面向报文
- UDP 作为一最简单的传输层协议,没有帮助用户处理复杂的网络环境,所以 保留下来了网络不可靠的特性。
- UDP 报文的头信息(header)
- 封装的数据(header)
- 8 字节,定长; 16位 源端口号(发件人), 16位 目的端口号(收件人); 16位 UDP 长度(报文长度), 16位 UDP 检验和 (checksum )
- checksum 作用: 判断收到的报文(数据)是否出现差错的; 如果校验和出错,就会直接丢弃;没有出错,正常接收数据;
- checksum 简单工作机制: 利用 hash 函数原理:通过设计一种 hash 函数,达到冲突率很低的一种情况。
- 数据(有效负荷 payload)
- UDP 有接收缓冲区(满了以后再到达的UDP数据就会被丢弃),没有发送缓冲区; 造成用户层发送了多少数据,UDP 也直接发送了多少数据;
- UDP 发送数据无需任何准备,随时随地发送(寄信)。 ,所以也是无连接的 ;
- 不可靠性: 报文不保证发送给对方;出现丢包之后,发送方也不知道; 报文无法保证有序;
- 影响结果: 由于底层(物理层+网络层) 都对一次发送的数据有大小限制;如果强行发送大于限制的数据,就会出现数据截断。
- UDP 的 socket 即能读也能写,叫全双工 (即能发送,也能接收)。
- UDP 协议 最适用的场景 : 对实时性要求较高,对可靠性要求较低的场景-----实时聊天(语音、视频)。支持广播。
2.2 TCP 协议:Transmission Control Protocol 传输控制协议
-
工作在传输层。其上是应用层(为应用层提供服务);下面是网络层(依赖网络层来进行工作);TCP 协议栈的外部事件源有两个:来自应用层上的方法调用;来自网络层的数据接收。 -
核心目标:提供进程间的通信;追求可靠性; -
以进程为单位传递数据 ; 追求可靠性; -
可靠性 : TCP 只能保证尽自己最大的可能,把数据有序的发送给对方(TCP 保证应用层收到的数据有序);但是不能保证一定能发送给对方。即使没有发送成功,也有反馈。 -
保证对方接受是有序的。保证对方不会受到差错数据。 TCP 会设计一些机制,来尽可能的优化网络,提高对方收到的可能性。 -
**如何做到可靠性?**机制 : 确认应答机制
- 发送的数据一般被称为:segment(数据段);应答:acknowledge
- 发送方同时发送多条 segment ,接收方进行了多次应答。发送方如何得知,接收方收到的是哪一次 segment ? 编号机制:发送方为发送的数据做编号,应答的时候带上对应编号即可。
- 发送方没有收到对应的应答,就认为对方没有收到数据。 ----超时重传机制
-
TCP 协议的 header 格式:
- 不是定长;
- 16 位 源端口号 , 16位 目的端口号。
- 32 位序列号 Sequence Number SN: 携带数据的 segment。
- 32 位确认序列号 Acknoeledge Sequence Number ASN : 应答的 segment。
- 4 位首部长度 ,可以保证接收方的 TCP 协议栈进行解包工作。
- segment 有两种 :携带数据、 数据 + 应答 segment ; 但是进行了统一的设置。如何区分本次 segment 是否有应答的作用 ? ack = 1 有应答作用, ack = 0 没有应答功能。
-
TCP 为发送的每个字节都进行编号(只是 payload)。 发送的时候, header 中的 SN 填写的是 第一个 payload 中的第一个字节的序列号。接收方知道长度,如果收到收据,就全都知道。 -
TCP 协议栈在建立连接时,会随机一个初识序列号(Initial Sequence Number ISN) -
ASN 中填写的是接收方收到的下一个字节的数据, (发来最后一个是112 ,则 ASN 填写113,意思:113之前的所有数据全被收到)。 -
如果发送方超过一定时间,都没有收到应答, 原一:对方没有收到; 原二: 对方收到了但是应答丢了。(前提:接收方可以根据 SN ,来确认数据是否已经接受过。)。发送方的处理逻辑是一致的,超时之后,直接重传(重传数据不会丢失)。 应答方 判断是否重复接受,不是 正常应答; 是 丢弃,正常应答。 -
如果乱序到达怎么解决?
- 发送方先发送的数据,后被接受(发送方可以不等应答,直接多次发送) 第一次收到的 不应答 ,(如果应答,对方以为之前全部都收到,若第一个包丢掉,也不会重复发)。第二次收到的应答 , ASN = 2001. (保证对方接受是有序的 ,接受过的数据不再接受。虽然接受方主机的TCP协议栈还不是有序的, 但是保证接收方主机的应用层收到的数据是有序的)
-
如果超时之后,重传对方还没有收到?
- 继续重传,直到达到一个阈值,如果还没有应答,就放弃; 步骤一:试图通知对方,连接异常关闭了, 发送一个 reset segment; rst = 1 是reset segment ; rst = 0 不是。对方能不能收到不保证。步骤二:通知应用层,数据发送失败,根据不同的语言有不同的通知方法,Java 中是通过异常的方式的通知,会受到 IOException,描述 reset connection。
-
如果超时,时间如何确定?
- 最理想的情况下,找到最小时间,保证“确认应答一定能在这个时间内返回”。超时时间设置太长,会影响整体的重传效率,如果超时时间设置太短,有可能会频繁发送重复的包。
- 处理办法:RTT:Round Trip Time 正常情况下,数据一次来回的时间。 一般要大于这个时间,一般有一个经验值。每次重传之后,会把这个数放大。Linux 中超时以 500ms 为一个单位进行控制,每次判定超时重发的超时时间都是 500ms 的整数倍。如果重发一次之后,任然得不到应答,等待2500ms后再进行重传,下一次 4500ms ,累计到一定重传次数,TCP 认为网络或者对端主机出现异常,强制关闭连接。
-
TCP 发送的时候,并不需要等到应答之后,才能发送新的数据;应答只给一个也是可以的。 -
TCP 即使在没有收到数据的情况下,偶尔也会定期发送应答回去。(最起码更新窗口)。 -
TCP 并不是收到数据之后会理解发送应答,有延时应答机制;等待是否可以捎带数据。 -
捎带应答;应答时可以捎数据。 -
快重传;理论上重传要等到超时才重传,但是接收方通过下发12道金牌方式,让发送立即重传。
2.1.1 TCP 的三个特点
- 可靠性(根源)
- 有连接
- 面向字节流,发送缓冲区的数据,由于发送窗口大小限制,不一定严格按照应用层写入的情况进行发送。接收方的应用层完全没有办法按照发送方的应用层的分割方式读取数据。(有些人叫粘包问题);通过应用层协议设计:1.定长的方式;2. 不定长,携带长度/制定特殊字符作为分隔符,比如\n。
- HTTP 协议是在 TCP 之上的一种协议,所以,也需要在协议中处理面向字节流的问题。所以HTTP 的协议设计,通过\r\n来进行请求行、请求头分割;针对正文部分,使用 Content-Length指定长度来分割
3、连接管理
3.1 为什么需要连接,连接是什么的抽象(TCP有连接,UDP没有连接)
- 作为 TCP 协议栈,是需要维护一个接收缓冲区的;
- 保证整理乱序到达的数据;
- 在数据暂时未被应用层读走之前,临时保存数据。
- 作为 TCP 协议栈,是需要维护一个发送缓冲区的;
- 由于可能会重发,所以未应答的数据不能直接扔掉,所以需要一个空间暂存。
- 作为 TCP 协议栈,发送方,需要维护已经发送的 SN;接收方时,需要维护应该应答的 SN。
TCP 协议栈,为了保证之前的那些机制可用,必须为每个信道,维护一组相关的数据。 一组有关联的数据----》抽象成对象,这个对象就被称为连接。
- 连接对象:双方通信的信道的代表。对于 TCP 协议栈,维护信道信息相关的一组数据。
3.2 建立连接的必要性
- 主机A上的进程要和主机B上的进程通过TCP进行通信。
- 由于TCP 是追求可靠性的,所以在正式发送数据之前,想验证对方是否可以收到数据(互相确认对方在线)。
- 连接对象,有一部分信息是无法独立知道的,需要双方进行有效信息的同步(双方同步必要的初始信息)。
- 使得通信阶段分成三部分:建立连接阶段(握手阶段 handshake)、信道可用阶段、释放断开连接阶段(挥手阶段)。
3.3 引入状态机制,不同状态----当前连接情况
3.3.1 三次握手
- TCP 的建立连接,需要双方交换3次信息;为什么是3次?A把信息同步给B,B把信息同步给A,至少需要两次,TCP 是需要保证可靠性的,所以除了应答、reset)都需要进行应答,同时需要2次应答,逻辑上是需要四次,但是为了提升网络利用率,合并(2)(3)变的必然(B应答A信息,并把信息同步给A)。两次是同步信息用的。同步(synchronize sync)。
- 四种 segment:数据 segment、应答 segment(ack = 1)、reset segment(rst = 1)、同步 segment(syn = 1)。
- 三次握手中的 SN 变化:
3.3.2 状态转移图(仅限于三次握手阶段)
- 在 TCP 层面,标准术语不提客户端、服务端,而是主动连接方和被动连接方;在实践中通常服务器是被动连接方,客户端是主动连接方。
- 连接阶段的状态:
- ---------- 建立连接前
- closed:无连接/连接刚建立未初始化时;
- listen:监听状态;被动连接方:处于等待别人建立连接的情况。实际上只有一方存在,还没有信道存在。
- ---------- 建立连接中
- syn_rcvd:被动连接方:刚收到对方(主动连接方)发过来的 syn segmnet。
- syn_sent:主动连接方:发出了syn segment ,等待对方回应阶段。
- ---------- 建立连接后
- established:连接已建立,可以正常通信的状态。
- 在正式建立三次握手之前,被动连接方有两个状态:closed、listen。
- 双方连接一旦建立,通信的双方地位就平等了:谁都可以主动发送数据,甚至可以同时发数据。·
3.3.3 四次挥手
- TCP 连接的关闭是独立的,不影响对方。
- 为什么挥手阶段是四次?
- 通信双方A、B (主动发起关闭的一方,并不一定是主动建立连接的一方。
- A 主动发起关闭----》B 收到关闭消息时,同时也关闭;
- A 主动发起关闭----》B 收到关闭消息后继续----》过一段时间----》B 再次发起关闭;
- A 发起关闭,几乎同时 B 也发起关闭。
- TIME_WAIT 只可能出现在主动关闭方一侧;CLOSE_WAIT只可能出现在被动关闭方一侧。
- 对于服务器上出现大量CLOSE_WAIT状态,是不是正常现象?如果不是,应该排查哪些问题?
- 不是特别正常,但是不是就一定有错。排查:服务器忘记关闭连接了,连接至少需要占用内存,大量废连接,无效浪费资源。
- TIME_WAIT状态的必要性:是经过一段时间变成了CLOSED,而不是直接变成CLOSED?一个连接关闭,现象是正常死亡了,但是实际上是不是真的死亡了还需要一段时间确认。可能出现的两种意外情况:情况一:B没有收到A的应答,如果A关闭,那么B请求应答机制,无法再次发送;情况二:通信阶段由于某些原因丢了,后面又传过来,如果closed,就无法收到。
- TIME_WAIT持续时间:2* MSL(Maximum Segment :TCP Segment 能在网络中存活的最大时间。
3.3.4 TCP 异常情况
- 进程终止:会释放文件描述符,任然可以发送FIN,和正常关闭没有什么区别。
- 机器重启:和进程终止的情况相同。
- 机器掉网、网线断开:接收端认为连接不在,一旦接受端有写入操作,接受端发现连接已经不在,就会进行reset。即使没有写入操作,TCP 自己也内置一个保活定时器,会定期询问对方是否还在,如果不在,也会把连接释放掉。
- 进程终止、机器重启、机器正常关机;OS 是有介入的时机的。所有连接都可以正常的关闭(走四次挥手流程)。
- 机器直接掉电(猝死);来不及;A电脑还没有重启;B电脑是不知道的,所以还是正常连接;
- B什么时候知道?如果B上的乙进程,尝试向连接中写入数据时,永远收不到应答,会尝试在多次重传后,放弃。作为乙进程(用户层),可以收到来自 TCP 协议栈的错误(写入失败)。(乙进程只知道出现问题,但是什么时间出问题,出了什么问题都不知道)。
- 如果B上的乙进程,正在等待对方发来的消息,处于读的状态。并且没有加超时机制(无限制的等待)----》乙进程永远不知道连接出现问题,原因:无法区分对方没发消息,还是连接出现问题。
- 只有写数据,才能暴露问题;
- 作为用户进程,应该:1、为所有的读添加超时;2、添加心跳机制(heartbeat)。定时(固定间隔、非固定间隔)给对方发消息,要求对方回应。
- 如果A电脑重启:乙进程如果处于读的情况,现象不一样;乙进程如果写数据,那么A进程由于重启,导致内存中的所有数据都丢失,所以连接都没有了,在收到一条网络数据之后,根本不知道是谁发过来的(类似失忆),回一条reset segment(异常情况),对于B上的乙进程,收到reset ,知道了对方异常关闭连接了,也可以关闭连接并且通知乙进程。
- 只要是写数据,就有机会知道对方出现问题。
- 作为应用层进程的开发者,最好添加:1、read timeout 机制;2、heartbeat机制;
- TCP 也提供了心跳的机制(keepalive),但是基本没人用,原因:以小时为单位;全局配置。
- 通信双方的机器都正常,网络上面某个必经之处断掉;来不及正常关闭;不知道这种情况的发生。
4、智能流量控制
- TCP 基于可靠性,对方接收缓存区可以容纳发送的数据;
4.1 基于对方的接收能力----流量控制(狭义) flow control
4.1.1 作为发送方,如何知道目前对方的接受能力
- 来自与对方的反馈,由接收方告诉我们。通过什么途径呢?-----发送数据一定有应答,可以把接收能力随着应答段(segment)发送回来。在 TCP 发送的应答中,把接收能力放在哪块?------16位窗口大小(窗口:接收方的当前接收能力(接收方的接收缓存区中剩余空间的大小),为了避免和其他窗口混淆,我们使用接收窗口来指代)。接收能力(当前)>= 接收能力(应答瞬间,数据可能被网络读走 )。
4.1.2 作为发送方,如何具体进行发送量的控制
- 保证本次发送的数据 <= 接收能力;
- 通过给发送缓冲区进行分段管理的方式(为什么 TCP 必须有发送缓冲区的第二个原因)
| 已应答 | 未应答 | 待发送 | 空闲 | 列举一种可能,网络上有非常多的可能,发送缓冲区(循坏队列) - 增加发送量控制机制
- 根据对方的接受能力计算出来最大发送量(最大发送量 = 对方接受能力)
- 应用层写入数据
- 滑动窗口机制:左边随着收到的应答朝右滑动;右边随着收到应答 + 接收窗口,朝右滑动。
4.2 基于当前的网络质量----拥塞控制 congestion control
4.2.1 如何知道当前的网络情况
- 接收能力是接收方明确告诉我们的,是一个精确值。网络承载能力,是无人告知的。所以采用一些算法来推测这个值。利用丢包情况来推断网络承载能力。
- 利用丢包情况来推断网络承载能力,使用应答的情况,来推测丢到的情况。如果每次发送的数据,都有应答,说明网络承载能力很高。如果每次发送的数据,极少应答,说明质量很低。
- 具体的计算情况,不会以几次丢包就来推断,防止对网络质量的评估造成抖动。一般采用单位时间内的丢包数,例如30s如果丢包超过10次,
- 拥塞窗口,根据网络承载额能力推断出的最大发送量大小。慢开始、快启动;网络质量良好情况下,一开始拥塞窗口是非常低的,被称为慢启动;指数增长,当到达一个阈值之后,变成线性增长。
- 发送窗口大小 = f(接收窗口大小,拥塞窗口大小) ; 发送窗口大小 = min(接收窗口大小,拥塞窗口大小);
- 当遇到网络质量比较差时,拥塞窗口 = 初始值;指数增长 --》线性增长的阈值 = 拥塞窗口cwnd/2(快启动的速度需要根据当前网络情况评估;cwnd 比较大的话,说明网络质量不错,说明遇到一次网络质量较差,但是增长起来还是很快)
4.2.2 如何具体进行发送量控制
总结
TCP | UDP |
---|
实现进程对进程 | 实现进程对进程 | 实现可靠性 机制(硬性、提升) | 不可靠 | 有连接 | 无连接 | 面向字节流 | 面向数据报文 | 一对一 | 可以一对一、可以广播(一对多) | 数据传输时的实时性要差 | 实时性略好 |
- 确定 UDP 的情况下,还想追求可靠性?-----在应用层自己做可靠性机制;学习 TCP 优点,避免它的缺点。HTTP3 尝试使用 UDP (背景:网络质量良好,HTTP3 自己做了一些可靠性)。
|