作者:困了电视剧
专栏:《JavaEE初阶》
文章分布:这是一篇关于网络编程的文章,在这篇文章中我会具体介绍TCP是如何实现可靠性的并且分析建立断开连接的情况,希望对你有所帮助!
目录
可靠性
确认应答
超时重传机制
连接管理
三次握手
四次挥手
可靠性
TCP协议具有有连接,可靠传输,面向字节流,全双工的特性,其中可靠传输是TCP存在的初心,是其最核心的机制。TCP实现可靠性是确认应答+超时重传,TCP的可靠传输主要通过以下几个方面进行实现。
这是一个TCP协议段格式,接下来我将对这个格式中的每一个元素进行分析。
确认应答
确认应答是实现可靠性的最核心机制,现在有这样一个情景,我在微信上给朋友发消息,第一个消息是“晚上出不出去吃饭”,朋友回答“好的”,我接着又发了第二个消息“我最近手头有点紧,借点钱”,朋友回答“不行”,此时朋友给我的回答就是应答报文(ACK报文),但是信息在通信的时候会不会出现这样一种情况,就是朋友发给我的两次应答报文顺序弄错了,即当我出去吃饭朋友回答不行,当我借钱的时候朋友说好的,这显然是会造成巨大的误会的。
为了避免这种情况,TCP就进行了编号,注意:由于在TCP的严重并没有“一条消息”,“两条消息”的概念,所以TCP就对每一个字节进行编号,具体如图:
超时重传机制
在一段网络通信的过程中,信息的传输需要经过许多个节点,如果中间的任何一个节点出现问题都会导致这个数据报的丢失,进而无法传输,这就是所谓的丢包,如果包丢了,接收方就收不到相关的数据了,自然也不会返回ACK。
不会返回ACK就这样一直等待下去吗,当然不会,TCP引入了超时重传的机制,如果发送方迟迟拿不到应答报文,在等待一段时间后就视为刚才的数据丢包了就会在重新发送一遍。
发送方对于丢包的判定,是一定时间内,没有收到ACK,那么此时就有两种情况了:
第一:发送方的数据丢包了,接收方没有收到自然不会有ACK。
第二:发送发的数据没有丢包,但是接收方收到数据过后发送的ACK丢包了,这样也会导致发送方触发超时重传机制,这时有人就会问了,如果是这种方式触发的,那接收方不就会收到了两份数据吗?这样的话,不也就造成了错乱了吗?
确实,在这种情况下接收方确实会收到两份数据,不过TCP非常贴心的帮我们处理好了这个问题,它会在接收缓冲区中根据收到的数据的序号,自动去重,保证了应用程序读到的数据只有一份。
拓:重传的数据会不会也发生丢包呢?答案是当然会,那此时的TCP又是怎样进行处理的呢?如果连续发多个数据都丢失,那说明当前丢包的概率已经非常大了,可能是50%以上了,当丢包率达到这个数据的时候表明网络此时很大概率出现了故障,所以TCP的处理思路就是,继续超时重传,但是每丢包一次,超时等待的时间都会变长(重传的频率降低了),因为此时TCP可能会觉得是网络出现了问题,如果连续很多次重传都无法得到ACK,那么此时的TCP就会尝试重新连接,如果重连也失效,TCP就会关闭连接放弃网络通信了。
连接管理
TCP建立连接:三次握手。TCP断开连接:四次挥手。
三次握手
握手指的是通信双方,进行一次网络交互,相当于客户端和服务器之间,通过三次交互,建立了连接关系,即双方各自记录对方的信息。
syn称为同步报文段,意思是一方要向另一方,申请建立连接,首先客户端发送一个申请,服务端接受后回复一个应答报文,然后也发一个申请(可以理解为建立连接是双方的事情,所以需要共同同意),客户端收到这个申请后,再返回一个应答报文,完成连接的建立。
如何区分syn报文和ack等报文呢?观察TCP报头结构:
这时可能有人会问了,这不是四次交互过程吗,为什么说是三次握手,因为syn和ack报文是能进行合并的,如果一个TCP数据报,第二位和第五位都是1,则当前这个报文是syn+ack。
为什么要进行三次握手,或者说三次握手达成了什么目标呢?
三次握手这个过程,本质上就是投石问路,验证了客户端和服务端各自的发送能力和接受能力是否正常。
首先客户端向服务端发送了一个syn报文,证明了客户端的发送能力正常,服务端接受到以后,发送ack报文和syn报文则证明了服务端的接受能力正常和发送能力正常,如果客户端的接收能力正常的话当客户端收到服务端发来的syn报文会响应一个ack报文,这些过程全部完成后,证明了双方都ok。
上述过程内核自动完成,应用程序干预不了。
从这个角度看,三次握手和可靠性也是有关系的,但是肯定没有确认应答和超时重传更重要。
四次挥手
断开连接,四次挥手。通信双方各自给对方发送一个FIN(结束报文),再各自给对方返回ACK。
ack 和 fin 是有一定概率合并成一个的,但是通常情况下不能合并。那为什么三次握手能100%合并而四次挥手就不能合并呢?
三次握手,ack和syn是同一个时机触发的(都是由内核来完成的)
四次挥手,ack 和 fin 则是不同时机触发的,ack是内核完成的,会在收到fin的时候第一时间返回,fin则是应用程序代码控制的,在调用到socket的close方法的时候才会触发fin。
参考我之前建立TCP的博客,这里调用close方法可能是立即,也可能是隔很久,取决于我们具体的代码,如果立即close,趁着刚才ack还没发现,这里就可以合并,如果是隔很久再close,此时fin只能单独进行发送了。