TCP协议的特点 : 有连接 , 可靠传输 , 面向字节流, 全双工
其中最重要的是可靠传输.
可靠传输 : 发送方能够知道 所发送的信息对方是否收到了
那如何实现可靠传输 ?
TCP的报文分为 普通报文 和应答报文 .
应答报文的 中的 ACK 标记位 为1 , 普通报文 为 0.
当为 普通报文时 32 位确认序号不生效 , 如果当前报文时 应答报文,确认序号就表示应答的是哪个报文
每一个ACK都带有对应的确认序列号,意思是告诉发送者,我已经收到了哪些数据;下一次你从哪里开 始发。
进一步理解区分面向字节流和面向数据报
面向字节流 和 面向数据报 都是在应用层中体现的 , 在传输层中, UDP 和 TCP 协议都是通过报文进行传输的.
当每一条 报文 传输过来后 , 在操作系统内核中 有一块 接收缓冲区 (内核中的一块内存空间, 每个socket都有自己的缓冲区) , 用来接收 传输过来的报文 ,
而 面向数据报时, 在接收缓冲区中 存储报文 是以类似链表的结构来存储的,每个报文都是一个节点,读取的时候也是 以节点为单位读取.
面向字节流时 , 在接收缓冲区中 存储报文 是以类似数组的结构来存储的 , 每个报文都是数组中的一个元素, 你要读取多少的元素 , 就读取多少个元素,体现的流的状态.
在确认应答机制下, 能知道 告诉发送方 接收端收到了 , 但是如果没收到, 如何确认? 就需要用过超时重传机制
发送过程中 会发生 丢包 的现象, 而 丢包可能会丢普通报文, 也可能会丢 ACK ,两种情况下,发送方都不知道数据对方是否收到.
这时就会启动超时重传机制, 在一段时间内 没收到应答 ,发送方就会再发送一次数据.
如果丢的 是 ACK ,重新发送后 接收端就有两份一样的数据了,那么 就会把相同的数据丢掉一份 ,只保留一份有效数据
传输过程中 , 会经过 交换机 路由器等中间过程, 每到达一个中转位置, 都会 检查 报文的 校验和,如果发生变化,就说明发生了丢包 ,会再重新进行发送
超时重传机制的, 多久没收到应答进行重新发送的时间间隔, 会越来越长. 如 : 第一次发送没收到, 间隔1秒再发送 , 第二次发送又没收到 ,就会间隔 2 秒再发送, 以此类推. 也不会一直重新发送, 尝试几次后, 如果仍然不成功,就会断开连接,重新尝试连接. 重连也连不上,就放弃了.
确认应答机制 和 超时重传机制 是TCP可靠性的最核心机制
如何建立连接 ? 通过 TCP协议的三次握手机制
三次握手
- 由客户端先发送建立连接的请求( syn )
- 服务器收到后, 也给客户端发送确认收到的应答 (ACK)建立连接的请求(syn)
- 客户端 收到 服务的的 ACK 和 syn 后, 给服务器 发送一个 确认收到的 ACK , 连接建立完成
为什么要三次握手 (三次握手的意义) :
- 相当于"投石问路 " , 要进行发送数据前, 先测试下发送数据的线路有没有问题 . 保证没有问题再进行发送, 同时也是在验证通信双方的 发送能力 和 接收能力 是否正常. ( 两个人 语音开黑 , 测试两人的 耳机和 麦克风)
- 用于通信双方进行协商 一些重要的参数.( 比如序号要从几开始)
两次行不行 ? 不行 , 因为次数太少无法确认线路是否畅通
四次行不行? 可以, 但是没必要因为降低了效率
四次挥手用于 断开连接 ,
四次挥手流程: ( 客户端A 或者 服务器B 都可以主动断开连接)
- 客户端 向 服务器 发送 断开连接的请求( FIN)
- 服务器 接收到 FIN, 立即给 客户端返回ACK
- 服务器 中用户调用 socket.close 方法时,才会给客户端发送 FIN ,与第二步有不可忽视的时间间隔,所以 2 3 步不可以合并.
- 客户端收到 syn 后, 给服务器返回ACK. 连接断开
TCP建立连接时,是没有历史包袱的,立即就能完成,而 断开连接时, A发送FIN给B时, B中可能还有数据在缓冲区中没读完, B要把数据处理完了再发FIN , B什么时候发FIN就是代码层次的问题了.
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UZfZROAl-1667974243144)(2022-8-9 2022-8-9 javaEE笔记 081053 102105.assets/image-20220810155029015.png]
CLOSED状态 : 连接断开( 释放)的状态
服务器 LISTEN 状态 : 服务器已经启动就绪等待连接的状态
ESTABUSHED 状态: 连接已经建立好了 ,可以进行后续通信
服务器 CLOSE WAIT 状态 : 收到了客户端 的 FIN ,返回ACK后 , 在自己调用close方法 , 发送FIN 之前的状态.
LAST_ACK 状态 : 发送 FIN后 ,未收到ACK前的状态
TIME_WAIT 状态 :
主动发起FIN的一方在 收到对方的FIN后进入的状态, 在收到对方的FIN后,要等待一段时间再进入CLOSED状态释放连接.
目的是为了防止最后一个ACK丢失 , 如果丢失 , 服务器端会认为 发起方 未收到FIN ,会进行重发 . 对于重发的FIN如果 此时A 已经释放连接, 进入CLOSED状态,就没有人处理 FIN 了 . 而 TIME_WAIT状态就是为了处理这种情况, 在TIME_WAIT状态 , 如果一段时间后没有收到 服务器 重发的FIN ,就说明此时 最后一个ACK对方已经收到了, 发起方 也进入 CLOSED状态.
TIME_WAIT 等待的时间是 2 MSL . MSL是操作系统中的一个配置参数, 表示两个主机之间,数据从一边到另一边花费的最大时间 .
为什么是 2MSL ? 因为要确保 最后一个ACK 从 A- > B , 重发的FIN 从 B->A ;
在保证可靠性的前提下, 因为为了保证可靠性, 降低了数据传输的效率 . 为了补救传输数据的效率. 使用了滑动窗口机制 . 因此 滑动窗口是一种效率补救机制
滑动窗口机制 : 在发送数据时 , 一次性发送多条 ,相当于一个窗口, 将窗口内的数据一次性发出去, 在发送的第一条的ACK返回后, 窗口就往后滑动.继续发送后续数据.
具体工作过程如图 :
上图中的 窗口大小是 4等分段 , 相当于一次 发送 4 条数据(1001 - 5000) , 在窗口中的第一条数据 的ACK 返回后, 就继续发送(5001 - 6000)的数据 ,看起来就像窗口在滑动.
发送数据过程中 必然会产生 丢包的问题: 在丢包的情况下, 能否保证可靠性呢. 下面分为两种情况 分析 :
丢失的是ACK
如果丢失的是其中某条数据的ACK ,并不会影响可靠性 ,因为只要 后续数据的ACK收到 ,就说明前面的的数据都已经发送到接收端了. 因为ACK设计的特点 , 后续的ACK会涵盖前面的ACK.
丢失的是数据包
如果丢失的是其中某个数据包( 如 1001-2000)的数据包丢失), 接收方就会一直向发送方返回 1001的ACK ,在重复几次后, 发送方就明白了 是 1001-2000的数据包丢失 , 启动重发机制 ,重新发送 1001-2000的数据包. ( 具体过程如下图)
这种机制被称为 “高速重发控制”(也叫 “快重传”)。
流量控制是用来控制滑动窗口的大小的, 因为滑动窗口机制提高了数据发送的效率, 如果滑动窗口越大, 发送速率越大. 但是如果发送速率过大, 而接收方的接收速率 ,是有限的, 这样就会导致接收方处理不过来, 导致丢包问题, 这样就要频繁的进行重发 , 效率反而更低. 流量控制机制是一种保证可靠性的机制
为了控制 滑动窗口的大小 ,保证效率, 引入流量控制机制.
流量控制机制:
根据接收方接收速率的大小 ,确定 滑动窗口的大小.
那么如何确定 接收方的接收速率多大呢 ?
在接收方接收数据时, 接收方的操作系统内核中 会有一块 " 接收缓冲区",先存放当前接收到的数据, 而读取接收缓冲区中的存放的数据 的速度,就是接收速率. 如图
接受速率的多快是根据我们的 所写的代码的实现方式来确定的 , 代码的实现方式 五花八门 ,就是导致接收的速率也各不相同. 直接表示并不好表示
这时可以根据根据接收缓冲区 剩余空间的大小 表示 接受速率的大小.
可以想象成一个蓄水池( 如下图)
那 接收方 如何接收缓冲区的大小告诉 发送方呢?
可以在ACK的报文中 带上这个信息
通信的双方 A 和 B 要完成通信 , 不单只有这两个 , 还有很多的中转站 ( 交换机 \ 路由器) . 此时用 接收缓冲区剩余空间的大小来衡量 接收方B 的接收速率 , 那中转站的中转能力又要如何进行衡量呢 ? 这就是拥塞控制
拥塞控制 :
做实验 ( 定性分析)
- 先以最小的 滑动窗口 来试探,
- 如果不丢包 , 说明网络顺畅, 逐步增大滑动窗口
- 放大到一定程度后,速率已经比较快了, 网络出现拥堵, 进一步出现丢包的情况 ,当发现丢包后就要减小滑动窗口
反复在 2 -3 步进行循环, 这就构成了一个 " 动态平衡"
通过 拥塞控制机制 , 使得网络发送速度 接近能够承载的最大极限, 既保证传输的速度, 同时能能够减少丢包,还能够适应网络的动态变化.~~
定量分析 :
注意 : 通过流量控制机制 和 拥塞控制机制 ,都能够控制滑动窗口的大小 , 那到底应该取哪一个机制决定的大小呢 ?
应该取两者中较小的那一个 , 取较小值生效
延时应答也是用来提高效率的机制.
延时应答,则是让滑动窗口能够大一些!
在流量控制机制中 , 通过ACK告知发送方滑动窗口大小是多少适合 , 为了防止流量控制将滑动窗口限制的更狠, 相对的提升一点效率, 引入了延时应答机制.
延时应答机制:
接收方在收到 发送方的数据后, 不立即返回收到的ACK , 而是延时一段时间再发送,
在延时的这段时间内, 应用程序是在不断的从接收缓冲区读取数据的, 接收缓冲区中的数据被读取后, 其剩余空间的大小就变大了,
这时再返回ACK附带的此时接收缓冲区剩余空间的大小给发送方 , 就会使下次发送数据时的 滑动窗口相对未进行延迟应答的窗口更大, 提升一定的发送效率.
上图中 在1-1000 的数据收到后, 进行了延时应答 , 没有立即返回1001的ACK , 而是在1001- 2000的数据到达之后,再进行了应答,应答了2001, ( ACK会涵盖前面的报文, 所以1001相当于也已经应答了)
捎带应答也是基于延时应答的一种提升效率的机制.
捎带应答机制
在客户端 和 服务器 交互过程中 , 如果是 一应一答的模式 , 每次客户端的请求都会 得到 服务器的回应 . 而在TCP中, 每次的请求和回应都会给对方返回一个ACK. 这时在延迟应答的基础上 , 如果请求的ACK 在延时后刚好和回应在同一时刻返回 , 二者就可以合成一个数据报进行发送, 提升了效率.
利用这个机制 在四次挥手过程中, 对于主动发送的FIN , 返回的ACK 若是延时应答后 和 接收方发送FIN的时机一样, 也是可以合并的 , 所以四次挥手 此时 可以 变成3次挥手.
TCP协议是面向字节流的 , 只要是面向字节流的传输方式 , 就会产生粘包问题.
粘包问题:
在面向字节流的传输过程中 , 接收方使用一个接收缓冲区,暂时存放接收到的数据, 应用程序再进行读取.
但是因为是 面向字节流传输 , 在接收缓冲区中如果有 多个数据包的数据同时存放 , 这时几个包的数就会粘在一起, 应用程序就无法区分 哪些数据是哪个数据包传输过来的了 , 这就是粘包问题;
解决粘包问题的方案:
在应用层序代码中,明确包之间的边界 :
主机关机( 正常按程序关机)
按程序关机, 会直接杀死所有的用户进程 => 释放进程的PCB => 释放文件描述符表对应的文件资源( 调用close 关闭文件) => 触发FIN => 开启四次挥手
如果在四次挥手过程中 主机已经关机了 , 对端会重传FIN 若干次, 没回应就放弃了
程序崩溃
同 主机关机 相同. 因为程序正常关闭 或者 崩溃 都会释放 文件描述符表 相当于调用close关闭文件.
虽然进程不在了, 但是四次挥手的过程是系统内核负责的, 所以并不影响四次挥手
主机突然断电
突然断电, 电脑直接关机 , 肯定来不及进行四次挥手 .
a) 接收方断电 : 这时候 发送方 ,在发出数据后得不到相应 , 就会触发超时重传机制, 多次重传后还是失败 , 就会尝试重新建立连接 , 仍然失败, 就会认为当前网络出现的问题, 就放弃了
b) 发送方掉电 : 接收方迟迟等不到要接收的数据 ,这时接收方无法区分,是发送方没发还是发送方出问题了,
接收方如果一段时间未收到数据 , 就会给 发送方 发送一个**“心跳包”**;
心跳包 : 接收方发给发送方的 一个特殊报文( ping ) ,如果发送方没问题 就会回应一个特殊报文 ( pong), 说明发送方还没发, 没回复就是 发送方出问题了.
心跳包是周期性的 ,用于判断对方是否存活.
网线断开
同 主机突然断电