目录
- TCP的“可靠性”(上)
- 确认应答(可靠性传输的基础)
- 超时重传
- 连接管理(三次握手,四次挥手)
TCP的“可靠性”(上)
想必大家都或多或少的听说过TCP的特性:有连接,可靠传输,面向字节流,全双工
本文重点讲讲TCP的“可靠性”
网络通信过程是复杂的,无法确保发送方发送出去的数据,100%能够到达接收放。
此处可靠性,只能“退而求其次”,只要尽可能的去进行发送了,发送方能够指定对方是否收到,就认为是可靠传输了。
网上很多帖子说:”TCP的可靠性是因为三次握手四次挥手“
这个说法是很不准确的,因为三次握手四次挥手只有初次建立连接的时候才会,但是可靠性是整个过程都可靠,那靠的什么呢?
用来确保可靠性,最核心的机制,称为“确认应答”
确认应答(可靠性传输的基础)
确认应答就是:句句有回应!
比如:银角大王每次像金角大王发出请求,金角大王都有响应
但是上述的时序有些过于理想了,实际上网络传输过程中,经常会出现“后发先至”情况
为什么网络中会出现“后发先至”情况呢?
一个数据包从发送方到接收方过程中走的路线可能不一样
第一个数据包,走路线一,第二个数据包走路线二
有可能路线二非常通畅,路线一堵车了,第二个数据包虽然发的迟,但是能先到!!
如果出现后发先至的情况,再去理解这里的含义就会出现问题了!
为了解决上述问题,引入了序号和确认序号,对于数据进行编号,应答报文里就告诉发送方说,我这次应答的是哪个数据
这只是简化版本的模型,真实的TCP的情况要更复杂一些。
TCP是面向字节流的,以字节为单位进行传输的,没有“一条两条”的概念
实际上,TCP的序号和确认序号都是以字节来进行编号的
应答报文中的确认序号,是按照发送过去的最后一个字节的序号再加上1来进行设定的
超时重传
超时重传是确认应答的补充
如果一切顺利,通过应答报文就可以告诉发送方,当前数据是不是成功收到
但是,网络上可能存在“丢包”情况。如果数据包丢了,没有到达对方,对方自然也没有ack报文了。
这个情况下,就需要超时重传了
TCP可靠性就是在对抗丢包(期望在丢包客观存在的背景下,也能够尽可能的把包传过去)
发送方发了个数据之后,要等
等的时间里,收到了ack(数据报在网络上传输,需要时间)
如果等了好久,ack还没等到,此时发送方就认为数据的传输出现丢包了
当认为丢包之后,就会把刚才的数据包再传输一次(重传)
等待的过程有一个时间的阈值(上线),就是(超时)
为啥会存在“丢包”?
网络中的路由器/交换机,不仅仅是给你这一次通信提供服务,还要能支持千千万万的主机之间的通信
整个网络中,就可能存在,某个路由器/交换机,某个时刻,突然负载量很高,短时间内可能有大量的数据包要几个这个设备转发。这个时候,如果瞬间的高负载超出了这个设备能转发的数据量的极限,多出来的部分,就无了,就被设备丢包了。
当然,没收到ack不一定就是丢包了,也可能是数据到达了,ack丢了
所以,这里要分情况讨论:
1.丢包了(数据包丢了)
这种情况接收方本身没有收到数据,此时你重传理所应当,没有任何问题
2.ack丢了
数据已经被接收方B接收了,但是B返回的ack丢了
此时发送到再传输一次,同一份数据,B就会收到两次
试想一下,如果发的请求是扣款请求呢??这是肯定不行的
TCP socket再内存中存在接收缓冲区(一块内存空间)
发送方发来的数据,是要首先放到接收方缓冲区中,然后应用程序调用read/sanner.next才能读到数据,这里的读操作其实是读接收缓冲区。
【缓冲区的应对方案】
- 1)去重
当数据到达接收缓冲区的时候,接收方首先会判断一下看当前缓冲区是否已经有这个数据了(或者这个数据曾经在接收缓冲区中存在过)
如果已经存在或者存在过,就直接把重复发来的数据就丢弃了
就能确保不会出现重复数据了
接收方如何判定这个数据是否是 “重复数据”
核心判定依据:【数据的序号】
- 数据还在接受缓冲区里,还没被 read 走。 此时,就拿着新收到的数据的序号,和缓冲区中的所有数据的序号对一下,看看有没有一样的。有一样的就是重复了,就可以把新收到的数据丢弃了。
- 数据在接受缓冲区中,已经被应用程序给 read 走了,此时新来的数据序号直接无法再接受缓冲区查到
注意!!应用程序读取数据的时候,是按照序号的先后顺序,连续读取的!! 先读 1 - 1000 1001 - 2000
2001 - 3000 一定是先读序号小的数据后读序号大的数据的(可以把接收缓冲区这个队列想象成带有优先级的阻塞队列)
此时socket api 中就可以记录上次读的最后一个字节的序号是多少
比如上次读的最后一个字节的序号是 3000
新收到一个数据包的序号是 1001,这个 1001 一定是之前已经读过的了,这个时候同样可以把这个新的数据包判定为 “重复的包”
直接丢弃了。
上述谈到的,ack,重传,保证顺序,自动去重,都是 TCP 内置的。使用 TCP 的 api 的时候outputStream.write ()
只需要调用一个这样的简单代码,上述功能就都自动生效了~~程序员需要操的心就少多了。 如果使用 UDP,上述这些问题就都得好好考虑考虑。
超时是会重传,但不是无限的重传,有一定的策略的
- 重传次数是有上限的。重传到一定程度,还没有 ack,就尝试重置连接,如果重置也失败,就直接放弃连接。
- 重传的超时时间阈值也不是固定不变的,随着重传次数的增加,而增大(重传频率越来越低)
连接管理(三次握手,四次挥手)
后续内容,在我的下一篇文章中有讲到:【TCP的“可靠性”(下)——三次握手四次挥手】
建立连接: 客户端执行 :socket=new Socket(serberIp,serverPort)这个操作就是在建立连接
上述只是调用 socket API ,真正连接建立的过程,是在操作系统内核完成的
建立连接(三次握手)
此处的连接是“虚拟的,抽象的”连接,目的是让通信双方都能保存对方的相关信息
断开连接(四次挥手)
断开连接的本质目的,就是为了把对端的信息从数据结构中给删除掉 / 释放掉。