作者:困了电视剧
专栏:《JavaEE初阶》
文章分布:这是一篇关于网络编程的文章,在这篇文章中我会着重介绍TCP保证效率,应对特殊情况等相关机制,希望对你有所帮助!
目录
效率
批量传输 滑动窗口
快速重传
流量控制
拥塞控制
延时应答
捎带应答
面向字节流——粘包问题
异常情况
进程关闭 / 进程崩溃
主机关机(正常流程关机)
主机掉电(拔电源)
网线断开
效率
在上一篇博客中,我简述了TCP是如何保证可靠性的。不过可靠性固然重要,传输的效率同样不可以忽视,那TCP在保证可靠性的同时又是怎样提高效率的呢?
批量传输 滑动窗口
TCP为了可靠性引入了应答机制,但是每次当主机1发送一个数据报的时候都需要等待主机2发送一个ack数据报才能进行下一步操作,这是不是有点太慢了?
于是,第一个提升效率的方法就出现了,想要提高效率,就需要缩短等待时间,,怎样缩短等待时间,答案:批量发送数据,一次发多条数据,一次等多个ack
这里是批量发送4条数据,发完之后统一等待ack,每次收到一个ack就立刻发下一条并不是收到4个ack再发下一组,,这样就使用一份时间,等待多个ack了,总的等待时间缩短了,整体的效率就提升了,上述批量传输数据的过程就称为滑动窗口。
注意:这里的效率提升仅仅是和没有进行批处理发送进行的对比,TCP的效率再快也快不过没有可靠性的UDP,可靠性和效率属于是鱼与熊掌不可兼得的东西。
快速重传
批量传送的过程中如果发生了丢包该怎么办?
当ack发生丢包的时候:
举个栗子1001这个ack丢包后,发送方就无法收到1001的这个ack报文,不过当2001这个ack报文传输过来的时候,发送方就可以明白1-1000的数据传输成功了,只是返回来的ack发生了丢包。
当数据发生丢包的时候:
举个栗子:1001-2000的数据发生了丢包,1-1000数据传输成功,那接收方就会返回下一个是1001的确认应答,由于丢包,1001的数据迟迟没有发来,所以接收方会一直返回 下一个是1001的ack报文,不过在这期间发送方仍然会将2001-3000,3001-4000...的数据发送出去,接收端也会接受这些发送来的数据,当发送方连续接受了许多个“下一个是1001”的ack报文后就会开始考虑是不是发生了丢包现象,然后会在重传一次1001-2000的数据,如果该数据没有发生丢包即被成功接收了以后,那接收端的下一次ack报文就会根据目前接收的情况进行传输,即如果现在接收端接收到了5001-6000的数据那接下来就会返回6001的ack。
流量控制
流量控制是一个保证可靠性的机制,上面说过,滑动窗口批量发送,窗口越大相当于批量的数据越多,整体的速度就越快,但是越快越好吗?
计算机在日常的工作中很大程度上手运用了缓冲区,这里也不例外,对于接受到的数据,是先放到接收缓冲区,接下来应用程序就可以通过socket里的InputStream来读了,代码中读出来的数据就从接收缓冲区删除了。
如果发的太快,那瞬间就可以把缓冲区给占满,接下来继续发送的数据就会造成丢包,这种情况下就会得不偿失,不如发的慢点。
那TCP 又是怎样对流量进行控制的呢?
这个16位窗口大小,当该报文的是ack报文的时候,这个窗口大小字段就会生效,这里的值就是建议发送方发送的窗口大小,注意这里是建议,接收方是如何计算窗口大小的?简单粗暴,直接那接收缓冲区的剩余空间的作为窗口大小。
接收缓冲区还有剩余空间的时候,发送发会按照之前介绍的规则向接收方发送数据,当接收缓冲区满了之后发送方就会暂停发送,但是仍然会每隔一段时间触发一个窗口探测报文,如果探测一会发现对方这里腾出空间了,就会继续发送。
拥塞控制
介绍完流量控制,现在要来介绍一下拥塞控制,滑动窗口的大小取决于流量控制和拥塞控制,流量控制衡量了接收方的处理能力,而拥塞控制衡量了传输路径的处理能力。
一段数据要完成从主机1到主机2的通信传输需要经过很多中间节点——一系列的交换机和路由器,很明显,在传输路径上任何一个设备的处理能力遇到瓶颈,都会对整体产生明显的影响,拥塞控制做的事就是衡量中间节点的传输能力。
拥塞控制的做法就是通过实验的方式,找到一个合适的发送速率,开始的时候按照一个小的速率发送,如果没有发生丢包的现象就可以提高一下速率——扩大窗口大小,如果出现丢包就立即在把速率调小,这是暂时不考虑流量控制的情况:
这张图详细介绍了相关的过程,那么现在问题来了,目前我们有拥塞控制和流量控制两种手段来控制窗口的大小,那实际发送方的窗口大小应该怎么计算?答案就是取当前拥塞窗口和流量控制窗口中较小的那一个。
延时应答
TCP可靠性的核心,是确认应答,ack要发,但是不是立即发,而是过一会再发,TCP中决定传输效率的关键元素就是窗口大小,这种延时应答影响的就是窗口大小。
发送方将数据发给接收方后,应用程序从缓冲区读数据进行不停的消费,如果此时立即返回一个ack,此时ack里带有一个窗口大小,如果稍等片刻再返回ack,此时ack里的窗口大小一定比立即返回的要大,因为在这片刻中,应用程序就会又从接收缓冲区中消费一批数据了。
说白了延时应答的效果,就是通过这个延时,让接收方应用程序,趁机多消费点数据,此时反馈的窗口大小就会更大一点,发送方的发送速率也就能更快一点。
捎带应答
捎带应答是基于延时应答的,客户端服务器之间的通信模型,通常是“一问一答”这种模式的,比如现在A向B发送一个请求,B收到数据后会回一个ack报文表示自己已经收到,然后根据相关的业务逻辑,通过一系列的代码执行完返回一个响应,这个响应和ack的时机本来是不同的,但是,由于延时应答,他们俩有几率进行合并,合并成一个数据报进行发送比分两次发效率要高很多。为什么四次挥手有可能三次挥完,也是捎带应答起的效果。
面向字节流——粘包问题
比如现在A给B发两条消息,第一条“今晚去哪吃饭”,第二条“吃饭的时候喝点不”。当A给B连续发了多个应用层数据报之后,这些数据就都积累到B的接收缓冲区中,紧紧挨在一起,此时B的应用程序在读数据的时候,就难以区分从哪到哪是一个完整的应用层数据报,就很容易读出半个包或者一个半包,那么该如何解决呢?
第一个方案是定义分隔符,比如约定每说完一句话的时候都已“。”结尾,这样就知道到哪截止,还有一个方案就是约定长度,约定数据的前4个字节,表示整个数据报的长度。
这俩都是自定义应用层协议的注意事项,xml或者json,本质上都是通过分隔符的方式来实现的,HTTP协议,既会使用分隔符,也会使用长度。
异常情况
断开连接需要进行四次挥手,但现实情况是十分复杂的,会有各种不同断开连接的方式,此时TCP又是如何进行处理的呢?
进程关闭 / 进程崩溃
进程没有了,但是socket是文件,不会突然消失会随之关闭,仍然可以进行四次挥手。
主机关机(正常流程关机)
主机关机会先杀死所有的用户进程,也会触发四次挥手,如果挥完最好,如果没有挥完,比如对方发的fin过来了,还没来得及ack就关机了,此时对端就会重传fin,重传几次都没有ack的时候就会尝试重新连接,如果还不行,就会直接释放连接。
主机掉电(拔电源)
瞬间机器就关了来不及进行任何挥手操作。
如果对端是发送方,对端就会收不到ack,然后进行超时重传,重传也连接不上就会重置连接,最后释放连接。
如果对端是接收方,对端就无法立即知道,发送方是还没来得及发新的数据还是发生意外情况直接没了,所以TCP内置了心跳包保活机制,如果对端是接收方,对端会定期发送一个心跳包,发送方会返回一个,如果双方的回应都很及时则说明没有出现问题,考虑到丢包情况,这个心跳机制会周期性的进行很多次,如果没一次都没有回应,就知道对方挂了。
网线断开
网线断开和主机掉电同理。
以上就是本篇博客的全部内容,如有疏漏欢迎指正!