1. TCP格式
TCP特性:有连接,全双关,面向字节流,可靠传输。(TCP安身立命的本钱,初心就是解决“可靠传输”问题)
其实TCP的特征有很多这里我就简单的介绍几个。
2. 确认应答
其实用来确保可靠性,最核心机制。就用我女神来简单举个例子。
其实我发出短信之后,我也不知道是否发送成功,此时我们也不知道对方是否有收到,是不是对方收到一个消息回一个消息,是不是也可能出现“后发先至”的情况。为了解决上述问题,引入了序号和确认序号,对于数据进行编号,应答报文里就告诉发送方说,我这次应答的是哪个数据。
下图就是确认应答的伪图:
3. 超时重传(是确认应答的补充)
定义:如果一切顺利,通过应答报文就可以告诉发送方,当前数据是不是成功收到。但是网络上可能存在“丢包”情况,如果数据包丢了,没有到达对方,对方自然也没有ack报文了,这个情况下,就需要超时重传了。TCP可靠性就是在对抗丢包,期望在丢包客观存在的背景下,也可能尽可能的把包给传过去,发送方发了个数据之后,要等,等的时间里,收到了ack(数据报在网络上传输,需要时间的)如果等了好久,ack还没等到,此时发送方就认为数据的传输出现丢包了。当认为丢包之后,就会把刚才的数据包再传输一次(重传),等待的过程有一个时间的阈值(上线),就是(超时)。
此时就会出现了几个典型的ack问题(这里讲俩个):
第一个情况:接收方本身就没收到数据,此时你重传理所应当,没有任何问题。
第二个情况:数据已经被B收到了,再传输一次,同一份数据,B就会收到俩次。
这时TCP socket在内核中存在接收缓冲区(一块内存空间)发送方发来的数据,是要先放到接收缓冲区中的。然后应用程序调用read/scanner.next 才能读到数据,这里的读操作其实是读接收缓冲区。接收缓冲区,除了能够帮助我们去重之外,还能够进行排序。
注意点:超时是会重传,重传也不是无线的重传,也要有一定的策略。
1. 重传次数是有上限的,重传到一定程度,还没有ack,就尝试连接,如果重置也失败,就直接放弃连接。
2. 重传时间超过阈值也不是固定不变的,随着重传次数的增加,而增大(而重传频率越来越低)
4. 连接管理
4.1 建立连接(三次握手)
这个处于accpet之前的阶段。此处谈到等等连接“虚拟的,抽象的”连接,目的是让通信双方都能保存对方的相关的学习。
所谓的建立连接过程,本质上是通过双方各自给对方发起一个syn,各自给对方一个ack。(这里客户端的信息告知服务器,这个操作确实在第一次握手的时候就完成了,但是最终强力出这个连接要建立,确立出后续要进行通信,还是得所有流程都走完)
这个是详细图:
那我们为什么要握手呢???意义何在?
1. 三次握手,可以先针对通信路径,进行投石文路,初步的确认一下通信的链路是否畅通(可靠性的前提条件)。
2. 三次握手,也是在验证通信双方,发送能力和接受能力正常。
注意:三次握手对于“可靠传输”这件事情,是有意义,起到了一些作用,但是他的作用有限,关键的可靠传输还是通过确认应答以及超时重传来保证的(毕竟三次握手,只是通信最开始的时候,握了一下,后面数据开始传输了,就和三次握手无关了)。
4.2 断开连接(四次挥手)
是不是有一个问题四次挥手能不能跟三次握三一样合并一个??它是有点时候能合并,有的时候不能合并,在实际通信过程中,ack和第二个fin时间间隔比较长,此时就无法进行合并了,就分俩次来传输。
四次挥手和三次握手之间的相似之处和不同之处:
相似之处:
都是通信双反各自给对方发起一个syn/fin,各自给对方返回ack,数据传输的顺序,syn/ack/syn /ac k, fin/ack/fin/ack。
不同之处:
三次握手中间俩次一定能合并,四次挥手则不一定。三次握手必须客户端主动,四次挥手,客户端/服务器都可以主动。
详图:
这里TIME_WAIT存在的意义,主要是防止,最后一个ACK丢包。服务器如果没有收到最后一个ACK,就会重传FIN。一般最多等待2MSL。
5. 滑动窗口
为什么有滑动窗口??一为了批量传输,之前发一个数据等待ack,再发一条数据,现在是连续发了一定数据之后,统一等一波ack,把多次请求的等待时间,使用同一份时间来等了,减少了总的等待时间。
虽然减少了时间也出现了问题;
1. ack丢了
这里要了解ack的用处了,ack是放回1001之前的数据传输成功而,这里1001没有穿过去,但是2001的穿过去了,表示2001之前的传输成功,是不是包含了1001,所以这里ack掉了不用任何操作。
2.数据丢了
在上述重传过程中,整体的效率是非常高的。这里的重传做到了“针对性”的重传,哪个丢了就重传哪个,已经收到的数据,是不必重复发送的(因为TCP有个接受缓冲区,这边发的数据到了接收方都是先放到接收缓冲区里的排队)。整体的效率没有额外的损失,就把这种重传成为“快速重传”。
6.流量控制
滑动窗口越大,此时传输数据的速度越快,但也不能无限快,但是接收方的接收能力存在上限,取决于接收方应用程序,读取接收缓冲区的熟读,这里需要根据接收方的处理能力(使用接受缓冲区剩余空间大小)反向影响发送方的发送速率。
当窗口大小为0,意味着接受缓冲区满了,此时发送方就应该暂停发送,发送方会周期的触发“窗口探测包”并不携带载荷,这样的包对于业务不产生影响,只是为了触发ack。一旦查询出来的结果,是已经非0 了,缓冲区又行了,发送方继续发送。
7. 拥塞控制
都是要限制发送方发送数据的速率,流量控制是站在接收方的角度来制约发送方速率。也符合木桶效应,能装多少水,取决你“最短的板”。
这里就举了方法:
1. 慢启动: 刚开始传输的数据,速率是比较小的,采用的窗口大小也就比较小,此时,网络的拥堵情况未知,如果一上来就搞很大,可能就让本来不富裕的网络带宽,雪上加霜。
2. 如果上述传输的数据,没有出现丢包,说明网络还是流畅的,就要增大窗口大小,此时,增大方式是按照指数来增长(*2)
3. 指数增长,不会一直持续保持的,可能会增大太快,一下导致网络拥堵,这里引入了一个“阈值”,当拥塞窗口达到阈值之后,指数增长就成了线性增长。
4. 线性增长也是一直在增长,积累一段时间之后,传输的速度可能太快,此时还是会引起丢包。一旦出现了丢包,就把拥塞窗口重置成比较下的值,回到最初的 慢启动过程,并且这里也会根据刚才丢包的窗口大小,重新设计阈值。、
8. 延时应答
也是基于滑动窗口,是要尽可能的再提高一点效率,结合滑动窗口以及流量控制,能够通过延时应答ack的方式,把反馈的窗口大小,弄大点。(核心让窗口尽可能大)
如我们前面讨论的:滑动窗口下如果ack丢了,没啥影响,延时应答具体怎么延时,也不是简单的按照时间,而是可以按照ack丢了的方式来处理,正常每个数据都会有ack,此时就可以每隔几个数据再放回ack了(每隔几个数九,就能起到了延时应答的效果)。另外也能减少ack传输的数量,也能起到节省开销的效果。
9. 捎带应答
基于延时应答引入的机制,能够提升传输效率,修改窗口大小,确实是提升效率的有效途径,捎带应答,就是走另一条路,尽可能的把能合并的数据包进行合并,从而起到提高效率的效果。
10. 面向字节流
主要的是一个粘包问题的处理。此处的包是“TCP的载荷中的应用数据包”。
粘包问题不是TCP独有的问题,只要你是面向字节流的,都是同样的问题的,解决问题的关键,就是明确包之间的边界:
1. 通过特殊符号,作为分隔符。
2. 指定出包的长度,比如在包开始的位置,加上了一个特殊的空间来表示整个数据的长度。
11. 异常情况
11.1 其中有一方出现了进程崩溃
进程无论是正常结束,还是异常崩溃,都会触发回收文件资源,关闭文件这样的效果(系统自动完成的)就会触发四次挥手,TCP连接的生命周期,可以比进程更长一些,虽然进程已近退出了,但是TCP连接还在,仍然可以继续进行四次挥手。
11.2 其中一方出现了 关机
点了关机之后,此时四次挥手不一定能挥完,系统马上要关闭了。如果挥的快,就能顺利挥完。但是如果挥的慢,至少也把第一个fin发给对端,至少告诉对方,我这边要结束了,对端收到fin之后,对端进入释放连接的流程了,放回ack并且也发fin,这里的fin不会有ack了。就会直接单方面删除连接信息。
11.3 其中一方出现了断电
对端是发送方:与主机关机执行机制顺序相同:接收不到ack->超时重传->重置连接->释放连接。
对端是接收方:对端无法立即知道此电脑是未发送新的数据还是直接关机。因此TCP内置了心跳包(保活机制),对端会定期给此电脑发送一个心跳包,此电脑会返回一个回应。如果每个心跳包都有及时的回应,就说明当前的状态良好。如果心跳包发送过去后没有回应,就说明此电脑关闭。
11.4 网线断开
执行的顺序与断电的一致。