目录
滑动窗口
流量控制
拥塞控制
延时应答
捎带应答
面向字节流
异常情况
UDP和TCP对比
由于TCP是可靠传输,有确认应答,超时重传,连接管理等机制,发送消息时需要等待接收方返回的ack.因此会消耗大量等待ack的时间,我们引入滑动窗口的机制来竭尽可能提高TCP的传输效率
当批量发送一批数据后,发送方就需要等待ACK了,发送方不是等待所有的ACK到达才继续往下发,而是得到一个ACK ,就需要继续往下发下一条或者多条,这样此处等待的ACK始终都是四条.
要想弄清楚滑动窗口机制,我们还需要明白确认序号的含义,确认序号表示:从该序号往前的所有的数据都已经确认到达了,所以如上图,如果只返回了一个4001的确认序号,说明4001之前的数据都确认到达了,此时我们无需再等待4001之前的ACK,直接再继续往下发数条数据.
如果丢包该如何处理?
此处丢包也分俩种情况,一种是发送方发送的数据丢了,一种是接收方返回的ACK丢了
ACK丢了
因为确认序号表示的含义是:该序号之前的所有数据都已经确认到达了,所以我们偶尔几个ACK丢包了也没关系,因为后面的确认序号实际上就包含了之前的ACK的信息.而事实上,操作系统内核再返回这些ACK的时候也并不是全部返回,可能会少返回一部分,这样在不影响可靠性的前提下,也节省了系统资源
数据丢了
由于1001-2000丢包了,接下来2001-3000到达主机B后,B给A返回的ACK确认序号仍然是1001(此时和发来的这个数据序号关系不大),意思是告诉A: 1001-2000数据没有收到,重发一下,所以接下来的几个ACK的确认序号都会是1001,直到B收到了1001-2000的数据,而A在连续收到3次1001-2000的时候,就会重传1001-2000
上述重传方式,也称为"快速重传"(重传操作只重传了丢失的数据),这种重传方式可以视为 超时重传机制在滑动窗口下的变形,如果当前传输数据密集,按照滑动窗口的方式来传输,此时按照快速重传来处理丢包,如果传输数据数据稀疏,不按照滑动窗口的方式,此时按照超时重传处理丢包
当然我们可能会收到不止3个确认序号是1001-2000的ACK,所以A可能会重传1001-2000不止一次,但问题不大,B的接收缓冲区会进行去重
由上述滑动窗口机制我们知道,滑动窗口的窗口越大,传输效率就越高,但是窗口不能无限放大,因为窗口太大几乎不等待ACK,TCP的可靠性不能保证,窗口太大也会消耗大量的系统资源,发送方发送太快,接收方处理不过来,再发数据意义不大.
因此接收方的处理能力是滑动窗口的窗口大小的一个重要约束,发送方发的速度,不能超出接收方的处理能力,流量控制做的工作就是,根据接收方的处理能力,协调发送方的发送速率
如何衡量接收方的处理能力?
直接查看接收方接收缓冲区的剩余大小,比如A给B发送数据,每次A给B发送数据的时候,B就要计算一下自己的接收缓冲区的剩余空间大小,然后把这个值通过ACK报文返回给A,A就根据这个值来决定接下来发送的数据的速率是多少(窗口大小是多少),也就是TCP报文中16位窗口大小这部分的值,当然16位窗口大小里的值必须是ACK报文才有效,16位也就是0-65535,并不意味着窗口大小最多只能是64KB,TCP为了能让窗口更大,再选项部分引入了扩展因子,比如此处窗口大小是64KB,宽展因子 是2,意味着64KB左移俩位,此时窗口大小就是256KB了
因此窗口大小不是固定的,是随着传输过程的进行,动态调整的,当窗口大小为0时,发送方暂停发送,在暂停发送等待的过程中,A会给B定期发送一个窗口探测报文,这个报文不携带任何具体的业务数据,只是为了触发ACK查询窗口大小
流量控制和拥塞控制共同决定发送方发送的窗口大小是多少,流量控制考虑的是接收方的处理能力,拥塞控制描述的是传输过程中,中间节点的处理能力
我们如何衡量中间节点的处理能力呢?
我们通过实验的方式,来测试出一个合适的值
第0轮,窗口大小是1,此处1并不是指1个字节,而是指1个单位, 如果传输顺利,没有丢包就继续扩大窗口,第1轮,窗口大小就是2,扩大1倍.如果传输顺利,没丢吧就继续扩大窗口,第二轮窗口大小就说4,又扩大了一倍,依次类推.初始阶段,由于窗口比较小,每一轮不丢包,都使窗口大小扩大一倍.
当增长速率达到阈值后(也就是图中的ssthresh),此处指数增长变为线性增长,注意增长的前提是传输顺利,不丢包,当传输过程一旦丢包,就说明此时发送的速率已经接近网络的极限.因此就把窗口再缩小成一个很小的值(重复刚才指数增长和线性增长的过程,另外新的ssthresh等于刚刚接近极限的那个值的一半).
流量控制和拥塞控制的窗口,共同决定了发送方实际的发送窗口.(取俩者中较小的值)
另外上述的过程是老版本的拥塞控制(之前的互联网中间设备不稳定,且带宽不高),在新版本中,我们在测到一个接近网络极限的值,就直接使用这个值作为拥塞控制的窗口大小了,不再把它变为一个极小的值重新测了
延时应答是在滑动窗口的基础上,进一步提高传输效率
滑动窗口的关键是:让窗口大一点,传输速率就快一点,因此就要在接收方能处理的情况下,尽可能增大窗口大小,延时应答就是在收到数据之后,不是立即返回ACK了,而是稍微等一会再返回,等待的时间里,接收方就能把接收缓冲区的数据处理一些,此时剩余的空间就更大一些.
而实际上,延时应答具体是怎么延时的呢?采取的方式是:在滑动窗口下,ack不再每一条数据都返回了,可能是隔几数据返回一个ACK
捎带应答是在延时应答的基础上的,把本来俩个不同时机发送的数据,因为有了延时应答,就可能变成同一时机打包一起发送了.
因为有了捎带应答和延时应答,四次挥手变成三次挥手也成为了可能,捎带应答让俩个数据变成一份发送成为了可能,而延时应答提高了这种合并的概率
面向字节流会引入粘包问题,在接收缓冲区会把很多数据放到一起,此时应用程序read读取的时候,就不知道读到哪算是一个完整的数据报.
由于TCP是面向字节流的,一次可以读1个字节,也可以读多个字节,这就导致一次读到的数据可能是半个应用层数据报,可能是一个应用层数据报,也有可能是多个应用层数据报.
解决粘包问题的俩种方案
我们可以在应用层约定好应用层协议,明确应用层数据报和应用层数据报之间的边界
1.约定好分隔符
1.约定好每个包的长度
1.进程崩溃了
进程结束了,对应的PCB也就没了,对应的文件描述符表就释放了,相当于 socket.close(),此时内核会继续完成四次挥手,仍然是一个正常的断开流程,当然崩溃是立即断开连接,如果进程崩溃发生再四次挥手的过程中,连接会保持半开状态,需要等待超时才能被关闭.
2.主机关机(按照正常流程关机)
主机关机,会先杀进程,然后才正式关机,所以会正常完成四次挥手的流程
3.主机掉电
主机掉电,显然是来不及挥手了.假设是接收方掉电,发送方仍然在继续发送数据,发完数据要等待ACK,ACK等不到再超时重传,重传几次还没有应答,会尝试重置TCP连接.显然这个重置也会失败,放弃连接.
如果是发送方掉电,接收方发现没数据了,接收方会周期性的给发送方发一条消息,确认一下对方是否还正常工作.这个消息我们形象的称为心跳包,心跳是周期性的,如果心跳没了,说明进程没了
4.网线断开
和主机掉电同理
TCP的优势在于,可靠传输,绝大部分场景都需要可靠传输,UDP的优势在于传输速率快,有些场景对性能要求更高(如同一机房内部服务器之间的通信,网络结构相对简单,网络带宽比较充裕,此时丢包的概率小),此外UDP天然支持广播,IP地址中有一种特殊的地址"广播IP",通过UDP往广播IP上发送数据,此时局域网内所有设备都能收到数据
当然还有另外的传输层协议,如KCP为代表的一系列协议,在可靠性和高效率做出权衡,可以很好的为游戏场景提供服务