- 博主简介:想进大厂的打工人
- 博主主页:@xyk:
- 所属专栏: JavaEE初阶
上一篇文章讲了UDP协议,那么这篇文章我来讲讲TCP协议,TCP协议相对UDP协议难一些,内容相对更多。
TCP,即Transmission Control Protocol,传输控制协议,人如其名,要对数据的传输进行一个详细的控制。
分别介绍TCP协议段格式,以及TCP的十大原理(确认应答、超时重传、连接管理、滑动窗口、流量控制、拥塞控制、延迟应答、捎带应答、面向字节流、异常情况)
TCP对数据传输提供的管控机制,主要体现在两个方面:安全和效率。这些机制和多线程的设计原则类似:保证数据传输安全的前提下,尽可能的提高传输效率。
目录
文章目录
一、TCP协议
1.1 TCP协议段格式:
1.2 TCP实现可靠性传输——确认应答机制
1.3 丢包现象——超时重传
1.4 连接管理(三次握手,四级挥手)
1.5 批量发送——滑动窗口
1.6 流量控制
1.7 拥塞控制
1.8 延时应答
1.9 捎带应答
1.10 面向字节流
1.11 异常情况
1.进程关闭 / 进程崩溃~
2.主机关机(正常流程关机)
3.主机掉电(拔电源,啪的一下,很快的)
4.网线断开
二、TCP十大核心机制小节
三、TCP 与 UDP的差别
一、TCP协议
TCP特性:
- 有连接
- 可靠传输
- 面向字节流
- 全双工
其中可靠传输是TCP存在的初心!!最核心的机制
那么TCP如何实现的可靠传输??
不是说,100%就能传过去(要求太高了),而是尽可能的传过去~~如果传不过去,发送发至少能知道自己没传过去~
核心机制,在于接收方收到了或者没有收到,都会有个应答~~
教人知识也是如此,我如果教一个同学一个知识,他没有如何应答,我就没法判断他掌握了没有~
- 同学会了:我接着讲~~
- 同学不会:我重新讲~~
1.1 TCP协议段格式:
1.2 TCP实现可靠性传输——确认应答机制
实现可靠性的最核心机制!!
举例说明:
假设我追女神,想约女神出来吃饭~~于是我给女神发短信~
在网络上,经常会出现,后发先至 的情况~~比如:
如果先收到了 滚,后收到了 好啊好啊~~
这样的话,我是非常高兴的~~~
为了解决上述问题,就需要针对消息进行编号!!给发送的消息分配一个“序号“,同时应答报文也给出”确认序号“
真实的 TCP 数据传输也是引入了 序号 和 确认序号!!
TCP将每个字节的数据都进行了编号,即为序列号。
TCP是针对每个字节都去编号!!(并没有”一条两条消息“这样的说法)
从前往后,把每个字节分别分配一个编号~~
发送方的确认序号是一个无意义的数据
注意,确认序号的规则!!!
不是说,发送方的序号是什么,确认序号就是什么,而是取的是发送方发过来的所有数据,最后一个字节的下一个字节的序号;
确认序号 1001 的含义:
- < 1001 的数据,我已经收到
- 我接下来想向发送方索要从 1001 开始的数据
于是,接收方就可以通过 ack 的确认序号,告诉发送方哪些数据已经收到了~
为什么网络上会出现 后发先至??
举个结婚的例子:
接亲需要车队,车队的头车是新郎坐的,排在最前面
这个车队只要开出了村口,就开始放飞自我~
新郎家和新娘家,如果中间的路很远,这些车就各走各的,最后到终点集合~~
当这些车分开的时候,就很难保证到达的顺序了~~经常是头车还没到,其他的车就先到了,后发先至~~
当后面的车先到娘家,先在村口等,等车都到齐了,重新整队~~慢慢的开到终点
那么对于 TCP 来说,自身也承担了这个整队的任务~~
每个TCP会有个接收缓冲区(一块内核中的内存空间),每个 socket 都有一份自己的缓冲区的~
TCP 就可以按照序号针对收到的消息进行整队了~~(也是TCP 序号的一个重要用途)
1.3 丢包现象——超时重传
如果中间的任何一个节点,出现了问题,都可能导致丢包~~
每个设备,都是在承担很多的转发任务的
每个设备,转发能力都是有上限的!!
某一时刻,某个设备,上面的流量达到峰值,就可能引起部分数据丢包~~
如果发送方在一段时间后,迟迟没收到ACK,则发送方会判定为刚才的数据丢包了!
- 发送方就会重新再发一遍,即 超时重传
- 大概率重新发过去是能成功的,如果又失败了,超时重传即可
其实没有收到ACK有两种情况:
- 数据直接丢了,接收方没收到,自然不会发ACK
- 接收方收到数据了,但是返回的ACK丢了
- 发送方是区分不了这种情况的,一定会超时重传
- 这样的话,接收方将收到重复的一个数据
- 这很严重,要是是涉及付款的操作,重复扣款两次,那就不太好了~~
这种情况下,B会收到重复的数据,还记得TCP的序号机制吗??
- TCP会根据发过来的数据的序号,自动去重!
- 保证应用程序读到的数据仍然只有一份
- 哪有什么岁月静好,是TCP在给咱们负重前行罢了
重传的数据也是有可能又丢了!!
例如丢包率为10% ==> 连续丢包两次==>10% * 10 % ==> 1%,概率很低很低了,如果还丢包,那网络出现问题的概率更大!
- 丢包很多次 ==> 丢包率50% + ,这就太离谱了(网络大概率出现了严重故障!)
所以,TCP处理这种多个包丢失的情况,仍然会超时重传
- 但是,每次丢包异常,超时等待时间,都会变长~~(重传的频率降低了)TCP就开始摸鱼了~
- 如果连续丢包很多次,多半是你网络出现严重问题了,TCP将尝试重连
- 如果重连都失效,TCP就会关闭连接,放弃此次网络通讯!
能重传就重传,实在传不了了,就关闭连接了,尽最大可能完成传输~~
确认应答,保证可靠性
超时重传,弥补丢包现象
这两个机制,是 TCP 可靠性的基石!!!
1.4 连接管理(三次握手,四级挥手)
问题:TCP 是如何实现可靠性的?
答:确认应答 + 超时重传~~
(很多人会回答,因为TCP 的三次握手和四次挥手,这是不对的!!)
TCP 建立连接:三次握手
TCP 断开连接:四次挥手
但是,上述这两个过程,和可靠性,多少有一点点关系,仅此而已~~
三次握手:
一次握手 (handshake):指通信双方,进行一次网络交互~
三次握手表示客户端和服务器之间,通过三次交互,建立了连接关系~~各自记录对方的信息~
其中,syn 是 同步报文段,意思就是一方要向另一方,申请建立连接~~
ack 是 应答报文段,意思是接收方 收到了应答~~
- 客户端发出请求连接的申请 【syn】
- 服务器返回 【ack】 确认连接,并发送 【syn】,申请与客户端连接
- 客户端也返回 【ack】 确认连接
只有双方都确认了连接,关系就建立起来了,连接建立完成!!
注意~~上述过程内核自动完成,应用程序干预不了,等到连接完成了,服务器 accept 把建立好的连接从内核拿到应用程序中~~
那么四次交互,为啥叫做三次握手???
首先你要认识这 6 个特殊的比特位,这几位默认是 0
如果设为 1,则表示特定含义
其中第二位,是 ACK,如果这一位为 1,表示 当前 TCP 数据报是一个 应答报文
其中第五位,是 SYN,如果这一位为 1,表示 当前 TCP 数据报是一个 同步报文
第六位为1:fin报文 ==> 结束报文
如果一个 TCP 数据报,第二位和第五位都是 1,则表示当前这个报文是 SYN + ACK
- 这就是三次握手的第二次握手发送的报文
所以,中间的两次交互合并成了一次!!!
知道了三次挥手,还要了解下,为啥要三次挥手??起到了什么效果,达成了什么目标??
三次握手,本质上是投石问路,验证了 客户端 和 服务器,各自的发送能力和接收能力是否正常!
从这个角度看,三次握手 和 可靠性,也是有关系的(但是肯定没有 确认应答 超时重传,更重要)
疑问:
上述流程中,如果两次握手行不行???四次握手行不行???
可以把中间的 syn 和 ack 拆开分别发送,同样也能达成目的,但是没有必要,会降低效率!!(封装和分用)
四次挥手:
断开连接,四次挥手~~
通信双方,各自给对方发一个 FIN (结束报文),再各自给对方返回 ACK~~
跟三次握手很像,但是有点差异
建立连接,一定是客户端主动先发起
断开连接,客户端和服务器都有可能先发起~~
就比如男生提分手和女生提分手,都是有可能的
此时,有个疑问,那么中间的两次交互不能合并吗???
三次握手中,ack 和 syn 是同一个时机触发的(都是内核来完成的,第一时间返回ack)
四次挥手中,ack 和 fin 则是不同时机触发的(fin 是应用程序代码来完成,在调用close方法才触发)
一般来说,服务器那个线程是不会立马结束的,所以ack和fin分两次发
- 但是也有个别情况,ack和fin,应答报文和结束报文是同一个数据报发送
总流程:
1.5 批量发送——滑动窗口
TCP 要保证的不仅仅是可靠性,还要效率!!!
TCP为了可靠性,牺牲了效率
- 因为要等待应答,超时重传…
单看效率,是完全比不上UDP的
- 但是TCP也在挽救它的效率~
A在这边花了大量的时间在等待 ACK~~
想要提高效率,就要批量发送数据了
批量发送:一次发多条数据,同时等待一个ack,既然这样一发一收的方式性能较低,那么我们一次发送多条数据,就可以大大的提高性能(其实是将多个段的等待时间重叠在一起了)
这里就是批量发送 4 条数据,发完之后,统一等待 ack
每次收到一个ack 就立即发下一条(不是收到4个ack再发下一组),使用一份时间,等待多个ack
批量发送不是无限制发送数据,而是发送到一定程度后,等待ack
- 发太多对方也可能受不了
而这个限制就是窗口的大小~
此时的效果就好像,窗口还是那么大,但是往后挪了一个格子,如果收到的 ack 非常快,此时这个窗口就在快速的往后滑动~~
如果批量发送的过程中,如果出现丢包怎么办??
情况一:数据包已经抵达,ACK被丢了
这个图中,相当于一半的 ack都丢了,相当高的丢包率~~~
注意,这种情况下,啥事没有,即使丢了这么多的 ack,对于可靠性没有影响
确认序号的含义,表示该序号之前的数据都已经收到了,后一个ack,能够涵盖前一个ack
当收到 2001 这个ack 的时候,此时发送方就知道,2001之前的数据都收到了
情况二:数据包就直接丢了。
这个情况下,由于1001 - 2000这个数据丢了,所以接收方仍然再次索要 1001,不会说因为收到的是其他的序号就返回,接下来的几次的数据的 ack,继续索要 1001 这个数据!!!
A这边连续收到几个 1001 之后,就知道这个事情不简单,于是重传数据!!
当A把 1001 - 2000这个数据重传后,返回的 ack确认序号是 7001,因为2001 - 7000 这些数据已经都收到了!!!
当 1001 这个数据重传过来之后,就会补全,这个重传过程也叫做 快速重传
滑动窗口,快速重传,是在批量传输大量数据的时候,会采取的措施~~
如果你只传输一条两条,少量的,低频的操作,就不会按滑动窗口那么传输了,仍热是确认应答和超时重传!!
1.6 流量控制
也是保证可靠性的机制
滑动窗口,批量发送,窗口越大,相当于批量的数据越多,整体的速度就越快~~
但是,传输数据并非是越快越好,如果发送的太快了,瞬间把接收方的接收缓冲区给打满了,接下来继续发送,数据就会丢包,还不如发送的慢点!!
当ack为1时,ack报文此时的窗口大小字段就会生效,这里的值就是建议发送方发送的窗口大小~
直接拿接收缓冲区,剩余空间,作为窗口大小 !!
接收方根据自己的处理能力,反向约束发送方传输速度
1.7 拥塞控制
滑动窗口的大小 取决于 流量控制 和 拥塞控制~~
- 流量控制:衡量了接收方的处理能力
- 拥塞控制:衡量了传输路径的处理能力
拥塞控制 做的事情,就是衡量中间节点,传输的能力~~
通过实验的方式,找到一个合适的发送速率!!!
开始的时候,按照一个小的速率发送
如果不丢包,就可以提高一下速率(扩大窗口大小)
如果出现丢包,则立即把速率再调小
重复上述过程~~(动态平衡)
实际窗口就是:min{拥塞窗口, 流量窗口}
- 指数增长快速到达ssthresh(阈值)后,加法增长稳步上升(减少丢包率)
- 丢包后,重新去做实验,并且ssthresh阈值更新(变大 / 变小 /不变)
- 当TCP开始启动的时候,慢启动阈值等于窗口最大值;
- 在每次超时重发的时候,慢启动阈值会变成原来的一半,同时拥塞窗口置回1;
1.8 延时应答
TCP的可靠性核心为:确认应答
- ACK要发,但是不是立即发,而是等一小会儿再发~
但是通过刚才对TCP的了解,决定传输效率的关键因素为“窗口大小”
- 要是你ack立马发过去,那就有数据立马发过来,那么就相当于传输过来的数据又多了,可能会导致窗口变小~
相当于说:
老师现在要检查作业,而我现在偷偷的补一点作业,过一会给老师检查,这样就不会被说啦~~
这个ack延时发送后,窗口大小大概率是变大了的,因为这个过程里,接受方一直消耗接受缓冲区里的内容~
延时应答的效果,就是通过这个延时,让接收方应用程序,趁机多消费点数据,此时反馈的 窗口大小 就会更大一点,此时发送方的速度就会快一点!
那么所有的包都可以延迟应答么?肯定也不是
- 数量控制:每隔N个包就应答一次
- 时间控制:超过最大延时时间就应答一次
1.9 捎带应答
在延迟应答的基础上,我们发现,很多情况下,客户端服务器在应用层也是 "一发一收" 的。
意味着客户端给服务器说了 "How are you",服务器也会给客户端回一个 "Fine, thank you";
那么这个时候ACK就可以搭顺风车,和服务器回应的 "Fine,thank you" 一起回给客户端
此时ack 就可能稍等一会再发,就很可能和 response 合并成一个数据报!!效率更高!!
为啥四次挥手,有可能是三次挥完??同理~~捎带应答起到的效果!!
1.10 面向字节流
有一个杀机问题~~粘包问题!!(一句话相当于一个“应用层数据报”)
- 我们分不清这三个请求都是哪到哪
定义报与报之间的分割符,这就是一个有效方案
- 这里,我们就可以定义,说完话要加句号或者问号~
还有一个常见方案:约定数据的前4个字节(或者更多),表示整个数据报的长度
- 这些都是应用层数据报协议的注意事项~
- 协议里会提到报的数据格式,和报的边界
1.11 异常情况
1.进程关闭 / 进程崩溃~
进程没了,socket 是文件,随之被关闭~~虽然进程没了,但是连接还在,仍然可以继续四次挥手~
2.主机关机(正常流程关机)
先杀死所有用户进程~~
也会触发四次挥手~~如果挥完,更好
如果没挥完,比如,对方发来的 fin 过来了,没来得及 ack 就关机了~此时对端就会重传 fin,重传几次之后,发现都没有 ack,尝试重置连接,如果还不行,就直接释放连接~~
3.主机掉电(拔电源,啪的一下,很快的)
1)对端是发送方
对端就会收不到 ack => 超时重传 => 重置连接 => 释放连接
2)对端是接收方
对端是没法立即知道,你这边是还没来得及发新的数据,还是直接没了~~
TCP内置了 心跳包 保活机制~~会定期询问对方是否还在
- 周期性
- 如果心跳没了,挂了~~
就是说,对方是接收方,对方就会定期给我发一个心跳包(ping),我就返回一个(pong)
- 如果每个ping,都有即使的pong,那么就说明我“没挂”,机器良好
- 我如果在控制台输入中或者等等情况下,机器正常,就会收到ping,返回pong,这个不需要代码控制
- 如果ping没收到pong,超时重传 ==> 重载连接 ==> 释放连接
4.网线断开
同上
- 来不及任何的挥手操作,跟第三点是一样的
二、TCP十大核心机制小节
- 确认应答
- 超时重传
- 连接管理
- 滑动窗口
- 流量控制
- 拥塞控制
- 延时应答
- 捎带应答
- 面向字节流 => 粘包问题
- 异常处理 => 心跳包
三、TCP 与 UDP的差别
TCP 可靠传输,效率没那么高,UDP不可靠传输,效率高~
两者的不同在于应用场景的不同
- 绝大多数情况下,都可以使用TCP
- 但是对于一些要求高效率,对可靠性要求不高的情况下,UDP会更好
- 例如,机房内部的内网之间的数据传输 / 分布式系统~
场景:既需要可靠性,又需要比较高的效率(LOL,CSGO,dota2....)
- 这两种协议都不合适,而传输层协议当然不止有这两种,如果你感兴趣的话,可以去了解哪些协议更适合处理这样的场景