TCP协议
延迟应答
它也是基于滑动窗口,提高效率的一种机制,结合滑动窗口以及流量控制,能够以延迟应答ACK的方式,把反馈的窗口,搞大.核心在于允许范围内,让窗口尽可能大.
如果接收数据的主机立刻返回ACK应答,这时候返回的窗口可能比较小.
1.假设接收端缓冲区为1M.一次收到了500K的数据;如果立刻应答,返回的窗口就是500K;
2.但实际上可能处理端处理的速度很快1,10ms之内就把500K数据从缓冲区消费掉了;
3.在这种情况下,接收端处理还远没有到达自己的极限,即使窗口再放大一些,也能处理的过来;
4.如果接收端稍微等一会再应答,比如等待200ms再应答,那么这个时候返回的窗口就是1M;
简而言之:接受方收到数据之后,不会立即返回ACK.而是稍等一下,等一会再返回ACK.等的这一会,相当于给接收方的应用程序这里,腾出更多的时间,来消费这里的数据.
典型场景:发送方不停发,接收方不停取
新收到的数据也占一部分空间.如果不是立即返回,比如延时100ms,在100ms之内,接收方应用程序就能再多消费一些数据,剩余的空间就更大,返回的窗口就是一个比较大的值.
一定要记得,窗口越大,网络吞吐量就越大,传输效率就越高.我们的目标就是在保证网络不拥塞的情况下尽可能提高传输效率;
那么所有包都可以延时应答吗?肯定也不是;
数量限制:每隔N个包就应答一次;
时间限制:超过最大延迟时间就应答一次;
具体的数量和超时时间,依操作系统不同也有差异;一般N取2,超时时间取200ms;
捎带应答
尽可能把能合并的数据包合并,从而提高效率的效果.
在延迟应答的基础上,我们发现,很多情况下,客户端服务器在应用层也是"一发一收"的.意味着客户端给服务器说了"How are you",服务器也会给客户端回一个"Fine, thank you";
那么这个时候ACK就可以搭顺风车,和服务器回应的"Fine, thank you"一起给回客户端.
正常情况下,2和3之间有一定时间间隔,此时就分两个包发送.但是由于延迟应答,ack应答时间有所推迟,ack就可以和response合并.
ack在延时的这段时间里,响应数据刚好准备好了.此时就可以把ack和应答的响应数据合并成一个TCP数据报.本身ack也不携带任何载荷,只是把ACK载荷设置为1,并设置确认序号以及窗口大小.
注意!很多时候客户端和服务端之间是长连接,要进行若干次请求的.在捎带应答的加持下,在捎带应答的加持下,后续每次传输请求响应,都可能触发捎带应答(也不是一定触发,具体是否能触发,取决于代码怎么写,取决于下一个数据来的快不快),都可能把接下来的数据和ack合二为一.
面向字节流
创建一个TCP的socket,同时在内核中创建一个发送缓冲区和一个接收缓冲区.
调用write时,数据会先写入发送缓冲区中;
如果发送的字节数太长,会被拆分成多个TCP的数据包发出;
如果发送的字节太短,就会先在缓冲区中等待,等到缓冲区长度差不多了,或者其他合适的时机发送出去;
接收数据的时候,数据也是从网卡的驱动程序到达内核的接收缓冲区;
然后应用程序可以调用read从接收缓冲区拿数据;
另一方面,TCP的一个连接,既有发送缓冲区,也有接收缓冲区,那么对于一个连接,既可以读数据,也可以写数据,这个概念叫全双工;
由于缓冲区的存在,TCP程序的读和写不需要一一匹配,例如:
写100个字节数据时,可以调用一次write写100个字节,也可以调用100个write,每次写一个字节;
读100个字节数据时,也完全不需要考虑写的时候是怎么写的,既可以一次read100个字节,也可以一次read一个字节,重复100次;
粘包问题
在tcp传输的数据到了接收方之后,接收方要根据socket api来read出来.read出来的结果就是应用层数据包.由于整个read过程非常灵活,可能使代码中无法区分出当前的数据从哪到哪是一个完整的数据包.
首先要明确,粘包问题中的"包",是指应用层的数据包;
在TCP协议头中,没有如同UDP一样的"报文长度"这样的字段,但是有一个序号这样的字段;
站在传输层的角度上,TCP是一个一个报文过来的,按照序号排好放在缓冲区中;
站在应用层的角度,看到的只是一串连续的字节数据;
那么应用程序看到了这么一连串的字节数据,就不知道从哪个部分到哪个部分,是一个完整的应用层数据包;
那么如何避免粘包问题呢?归根结底就是一句话,明确两个包之间的边界.
对于定长的包,保证每次都按固定的大小读取即可;例如上面的Request结构,是固定大小,那么就从缓冲区从头开始按sizeof(Request)依次读取即可;
对于变长的包,可以在包头的位置,约定一个包总长度的字段,从而就知道了包结束的位置;
对于变长的包,还可以在包和包之间使用明确的分隔符(应用层协议,是程序员自己来定的,只要保证分隔符不和正文冲突即可);
思考:对于UDP协议来说,是否也存在"粘包问题"呢?
对于UDP,如果还没有上层交付数据,UDP报文长度仍然存在.同时,UDP是一个一个把数据交付给应用层.就有很明确的数据边界.
站在应用层的角度,使用UDP的时候,要么收到完整的UDP报文,要么不收.不会出现"半个"的情况.
UDP的接收缓冲区不是队列结构,而是链表,每一个结点都是一个UDP数据报.
粘包问题,是TCP引起的,但TCP本身并不解决,而是由程序员写代码自行处理(应用层逻辑),xml,json, protobuffer都能处理粘包问题.
异常情况
进程终止:进程终止会导致释放文件操作符,仍然可以发送FIN,和正常关闭没有什么区别.(进程无论是正常结束,还是异常崩溃,都会触发到回收文件资源,关闭文件这样的效果(系统自动完成的),就会触发四次挥手)
TCP连接的生命周期,可以比进程更长一些.虽然进程已经退出了,但是TCP连接还在,仍然可以继续进行四次挥手.
其中一方机器关机(按照正常流程关机):当有个主机,触发关机操作,就会先强制终止所有的进程(类似于上述的强杀进程),终止进程自然会触发四次挥手~
点了关机之后,此时,四次挥手不一定能挥完,因为系统马上就关闭了.如果挥的快,就能够顺利挥完,此时,本端和对端都能正确删除保存的连接信息.(四次挥手的核心流程)
如果挥的不快,至少也能把第一个FIN发给对端,至少能告诉对方,我这边要结束了.
对端收到FIN之后,对端也要进入释放连接的流程了,返回ACK,并且也发FIN.这里的FIN不会有ACK了,FIN没收到ACK时,势必要进行重传(超时重传的流程中了).
当重传几次后,发现还是不行,还是没有ACK,这个时候就会单方面释放连接信息.
其中一方出现了断电(也算关机,更突然的关机):
(a):断电的是接收方:发送方就会突然发现,没有ACK了,就要重传.重传了几次后,还是不行.
TCP就会尝试复位连接.相当于清除原来的TCP中的各种临时数据重新开始.
需要利用到TCP的"复位报文段"(RST). 但此时的RST也不会有ACK.重置了还不行,单方面放弃连接.
(b):断电的是发送方:这个情况下,接收方需要区分出,发送方是挂了,还是好着暂时没发.
TCP也是如此,接收方一段时间之后,没有收到对方的消息,就会触发"心跳包"来询问对方情况
如果对端没心跳了,此时本端也就会尝试复位并且单方面释放连接了.
TCP/UDP对比
我们说TCP是可靠连接,那么是不是TCP一定优于UDP呢?TCP和UDP之间的优点和缺点,不能简单,绝对的进行比较.
TCP用于可靠传输的场景,应用于文件传输,重要状态更新,数据包很大的传输;(绝大部分场景)
UDP用于高速传输和实时性要求较高的通信领域,例如,早期的QQ,视频传输等.另外UDP可以用于广播;(对于效率要求很高,但对于可靠性不高).
归根结底,TCP和UDP都是程序员的工具,什么时候用,具体怎么用,还是根据具体场景判定.
如何用UDP实现可靠传输?
参考TCP可靠性机制,在应用层实现类似逻辑.
例如
引入序列号,保证数据顺序;
引入确认应答,确保对端收到了数据;
引入超时重传,如果隔一段时间没有应答,就重发数据.