TCP报文的结构:
TCP的报头前20字节是固定长度,也可以通过“选项”来增加。
一、用来确保可靠性,最核心的机制,称为“确认应答”
引入一个情景:
A向B询问cat和dog的意思:
这种情况是理想情况,但是在互联网中经常存在后发先至的情况,例如:
这时A就会误认为cat是小狗,dog是小猫。
为了解决这种问题,TCP引入了序号和确认序号,对数据进行编号,在应答报文里告诉发送方说,我这次应答的是哪个数据。
接受方将接收到的数据先放入到接受缓存区中,而接收缓冲区又可以认为成一个优先级队列(以序号作为优先级的参考依据)。
但是应答报文的确认序号,是按照发送过去的最后的一个字节的序号加1来进行设定的,而不是接受到的最后的一个数据序号。
TCP的确认应答机制是确保TCP可靠性的最核心机制。当报文为应答报文时,报头中的ACK为1,其他时候为0。
二、超时重传,是确认应答的补充
如果一切顺利,通过应答报文可以告诉发送方当前的数据是不是成功收到,但是网络上会存在“丢包”的情况,如果数据包丢了,对面没接收到,也就不会发送ack报文。
这个情况下,就需要超时重传了。
TCP的可靠性就是对抗丢包,在发送方发送一个数据包后,如果在一定时间里没有接收到ack报文,就会认为丢包了,就会把刚才的数据包再重写传输一次。这个等待有一个时间的阈值。
丢包也分为两种情况,如果丢的是数据包,再次重传一次理所当然;但是如果丢掉的是ack报文,接收方就会重复接受,这时就需要丢弃重复的数据。
关于TCP是如何判断重复的:TCP socket在内核中存在接收缓冲区,发送来的数据是要先放到缓冲区里的,然后应用程序调用read/scannner.next才可以读到数据。
当数据到达接收缓冲区里后,接收方会先判断这个数据双是否已经有了或者曾经存在过。如果存在了或者曾经存在过了,就会丢弃这个数据包。接收方判断方法就是通过数据的序号:
1.数据还在缓冲区里,拿着新收到数据的序号,和缓冲区里所以的数据序号对比一下,看看有没有一样的,有的话就丢弃。
2.数据已经被read了,因为应用程序在读取数据包时,是按照序号的先后顺序来读取的,例如:1000~2000,2001~3000,一定是先读序号小的再读序号大的,而socket会记录上次读到的最后一个字节的序号是多少。如果发现这个序号小于最后一个字节的序号,就会把这个包判断为重复数据包,并丢弃。
重传的策略:
1.重传次数是上限的,重传到一定程度还没有ack,就尝试重置连接,如果重置也失败,就直接放弃连接。
2.重传的超时阈值也不是一成不变的,随着重传的次数增加而增加。
三、连接管理
1.建立连接
socket = new Sockket(serverIp,serverPort);这个代码就是在建立连接,但真正建立连接的过程是在操作系统内核中完成的。
内核建立连接的过程称为“三次握手”。过程如下:
syn:一个特殊的报文段,1.没有载荷,不会携带应用层数据。2.六个标志位的第五个,为1。
在实际情况中,有两次交互可以合二为一,最终就形成了“三次握手”。
所谓建立连接的过程,本质上就是通信双方各自给对方发一个syn,各自给对方发一个ack。由于ack是六个标志位的第二位,syn是第五位,所以完全可以用一个数据包同时将ack和syn置为1。这样就可以合并为一个数据包。就将四次交互合为三次握手。
三次握手的意义:
1.三次握手,可以先针对通信路径进行投石问路,初步确认一下通信链路是否通畅。
2.也是在验证通信双方的发送能力和接收能力是否正常。
3.三次握手也会协商一些必要的参数。TCP中也是有很多参数需要协商的,往往是以“选项”来体现的。最少0字节,最多40字节。其中有一个关键信息是TCP通信序号的起始值。TCP每次通信序号的起始值都是一个比较大数字,即使是同一个客户端和服务器,每次连接开始的序号也不同。这样上次传输的数据包即使因为某些原因到达了接收端,也会因为序号差别太大而被丢弃。
2.释放连接
释放连接称为四次挥手,和三次握手是由客户端发起不同的是,四次挥手可以是双方的任意一方发起的。它是通过发送fin(结束报文段)来结束连接。调用socket.close()就会触发fin,如果进程直接结束,也会触发fin。
面试经典题:四次挥手是否可以像三次握手一样合二为一呢:
看情况:如上图,如果服务器发送FIN和ACK时机时间间隔很小,是有可能合二为一的,如果时间间隔过大,就需要分两次发送了。
连接管理过程中涉及到的TCP状态转换:
LISTED状态表示服务器已经创建和 severSocket了,并且绑定了端口。
ESTABLISHED 已确立的,表示客户机与服务器已经建立了连接。三次握手完了。
四次挥手中涉及到的状态转换
CLOSED_WAIT表示接下来的代码需要调用close来主动发起fin,一般是谁被动断开连接,谁进入CLOSED_WAIT。
TIME_WAIT表示本端给对方发起fin后,对端也给我发fin,此时本端进入TIME_WAIT,给最后一个ACK的传送留有一定的时间。一般是谁主动断开连接,谁进入TIME_WAIT。
TIME_WAIT存在的意义是:防止最后一个ACK丢包。如果最后一个ACK没有传到,那么接受ACK的一方就会重传fin,如果在断开连接发起方发送ACK后很长时间(2 MSL)都没有收到重传的fin,就认为已经ACK发送成功,连接断开。
四、滑动窗口,减小传输效率的折损
因为TCP为了实现可靠传输付出了很多代价,比如传输效率就大大减小了。例如在确认应答的机制下,每次发送方接收到一个ack后才会发送下一个数据,导致大量时间浪费在了等待ack上。
而滑动窗口的出现就是为了解决上述问题的。
它采用批量传输的方式:发送了一个数据后,不必等待ack,而是继续往下发,连续发送了一定的数据后,统一等待一波ack。
滑动窗口处理丢包问题:
1.ack丢了
如果是最后到达的ack报文前的ack丢了,因为数据发送是按照序号从小到大发送的,所以这并没有什么问题。后面的ack都到了,前面的到不到无所谓了。如果丢掉的是最后一个,接收方的接受缓存也会将发送方重复发送的数据丢弃。
2.数据丢了
这时接收方会重复向发送方发送丢失数据的ack报文,当重复发送三个一样的ack报文后,发送方会将丢失的报文重新发送。这种重传是针对性的重传,整体效率很高。被称为“快速重传”。
注意:确认应答中的超时重传和滑动窗口的快速重传是并不冲突的,当数据量很少时,采用确认应答的超时重传,当短时间发送大量数据时,采用滑动窗口的快速重传。
五、流量控制,使接收双方速率一致
通过滑动窗口可以提高传输效率,理论上窗口越大,效率越高。但是由于接收方的接收缓存是有限的,所以窗口不可以无限大。因为如果发送过快,接收缓存满了后,继续发送会导致丢包问题,数据传输的可靠性就会下降。
为了解决这个问题,TCP报头就增加了窗口大小的字节段。
接收方通过这个字段来反馈给发送方发送速度,这个字段在普通报文中无效,只有在ack报文中才有用,发送方就会根据这个大小来调整发送速率。当接收缓存为0后,发送方会周期性的发送“窗口探测包”,并不携带载荷,只是用来检测接收缓存是否不为0。
六、拥塞控制
同流量控制类似,都是限制发送方发送数据的速率。拥塞控制将通信双方之间的路由器,交换机等视作一个整体,然后通过实验的方式,计算出一个较合适的速率。
在实际中,滑动窗口的大小是由流量控制和拥塞控制中较小的一方决定的。
拥塞控制具体计算窗口大小的方法:
1.慢启动。刚开始传输时,速率比较小,窗口大小也比较小。
2.如果传输过程中没有丢包,说明网络很通畅,就会增大窗口大小,而且是按照指数增长发送来增大。(*2)
3.指数增长,不会一直持续下去,当到达某一个阈值后,便会转化为线性增长。
4.线性增长是持续的,当增长到丢包后,就把拥塞窗口重置为较小的值,回到最初的慢启动,而且还要根据刚才丢包时的窗口大小来重新设置指数增长的阈值。(新版本的TCP在发送丢包后会直接跳过指数增长的过程,直接进入到线性增长的环节)。
七、延时应答
也是基于滑动窗口,目的是再提升一点传输效率。核心是在允许的范围内把滑动窗口的大小再扩大一些。
原理:接收方收到数据后,不会立即返回ack,而是稍等一下再返回ack,等了这一会,相当于给接收方的应用程序这里腾出来更多的时间来消费发送方发送的数据。这时接收方接收缓存的大小会更大一些了,返回给发送方的窗口值也会更大,滑动窗口也就会更大了。
八、捎带应答
基于延时应答引入的机制,可以提升传输效率。
原理就是尽可能将能合并的数据包进行合并,从而达到提升效率的效果。
在延时应答的加持下,原本①和②之间的时间间隔就会减小,此时便可以合二为一,类似于三次握手。
九、面向字节流
此处重点讨论,面向字节流中的一个非常重要的问题“粘包问题”。
由于TCP是面向字节流的,所以对于发送过来的数据包的界限便很难区分,比如将aaa和bbb读成aaab和bb。
解决方法:
1.通过特殊符号作为分隔符,
2.指定出包的长度,比如在包开始位置加上一个特殊空间来表示整个数据的长度。
十、异常情况
这里列举一些常见的异常情况:
1.其中一方出现了进程崩溃:
进程无论是正常结束还是异常崩溃,都会触发到回收文件资源,关闭文件这样的效果。就会触发 四次挥手。
TCP连接的生命周期会比进程更长一些,虽然进程已经退出了,但是TCP连接还在,仍然可以继续进行四次挥手。
2.其中有一方出现了关机(正常流程关机):
当有个主机触发关机操作时,就会先强制终止所以的进程,终止进程自然就会触发四次挥手。
3.其中一方出现了断电:
a.断电的是接收方,发送方就会突然发现没了ack,就要重传。重传了几次后,TCP就会尝试复位,即六个标志位中的“RST”,清除TCP中的各种临时数据,重新开始。如果重置了还不行,就会单方面放弃连接。
b.断电的是发送方,此时接收方一直处于阻塞状态,但是会发送“心跳包”来询问对方的情况。(不携带数据,周期性的),如果对面没回应,本段就会尝试复位并单方面释放连接了。
4.网线断开
这个情况本质上就是a和b的结合了。