1. TCP协议段格式
2. TCP原理
2.1 可靠性机制
2.1.1 确认应答
确认应答是实现可靠性的最核心机制
首先来看一个例子,下面是我和女神的对话~
所以为了解决上述问题呢,就需要针对消息进行编号!给发送的消息分配一个”序号“,同时应答报文,给出”确认序号“
在真实的TCP数据传输中也引入了 序号和 确认序号
TCP 将每个字节的数据都进行了编号。即为序列号
同时对TCP来说,自身也承担了这个整队的任务,TCP会有一个接收缓冲区(一个内核中的内存空间),每个 socket 都有一份自己的缓冲区。 TCP就可以按照序号针对收到的消息进行整队了。此时就可以确保应用程序读数据,读到的一定是有序的(和发送顺序一样)。
2.1.2 超时重传
网络中会出现一种非常典型的情况:丢包问题
为什么会丢包呢? 因为数据在传输过程中是受到网络的影响的,传输并不是直达的,而是会通过很多中转设备,而每个中转设备都是承担很多的转发任务,每个设备转发能力有限。如果在某一时刻,某个设备,上面的流量达到峰值,就可能引起部分数据被丢包~
发送方对于丢包的判定,是一定时间内,没有收到 ack 报文,分为两种情况:
- 数据直接丢了,接收方没有收到,自然不会发 ack
- 接收方收到数据,返回的 ack 丢了
发送方是区分不了这两种情况的,都只能重传!
TCP针对多个包丢失的情况,是继续超时重传,但是没丢包一次,超时等待时间就会增加(重传的频率降低),如果连续多次重传,都无法得到 ack,此时TCP就会尝试重置连接,如果重置连接也无效,TCP就会关闭连接。
如果一切顺利,使用确认应答保证可靠性;出现丢包,则使用超时重传作为补充~
2.1.3 连接管理
- TCP建立连接(三次握手),存在的意义:验证了客户端和服务器,各自的发送能力和接收能力是否正常!
- TCP断开连接(四次挥手)
2.2 效率机制
2.2.1 滑动窗口
但是在批量发送的过程中,如果出现了丢包该怎么办? 丢包分为两种情况
- 情况1:数据报到达,ACK丢了
- 情况2:数据报直接丢了上述重传过程中,没有任何冗余操作!丢了数据才会重传,不丢是不会重传的,整体比较快,这个重传过程也称为 快速重传
滑动窗口,快速重传,是在批量传输大量数据的时候,会采取的措施~
如果只传输一两条,少量的,低频的操作,就不会按滑动窗口这么搞了
仍然是前面朴素的确认应答和超时重传~
2.2.2 流量控制
滑动窗口,批量发送,窗口越大,相当于批量发送的数据越多,整体的速度就越快~ 但是并不是越快越好!
如果你发的太快,瞬间把接收方缓冲区给打满了,接下来的继续发送,此时数据就会丢包
通过流量控制,本质上就是让接收方来限制下发送方的速度,具体如何控制?
上述过程是把返回的窗口大小,当作实际的窗口,实践中可能会有所出入~
发送窗口大小 = 流量控制 + 拥塞控制
2.2.3 拥塞控制
上述中说到,滑动窗口的大小 = 流量控制 + 拥塞控制
流量控制:衡量了接收方的处理能力
拥塞控制:衡量了传输路径的处理能力
实际发送的窗口大小 = min(拥塞窗口,流量控制窗口)
拥塞控制做的事情,就是衡量中间节点的传输能力。拥塞控制,要衡量中间路径,中间路径上有多少个节点?每个节点当前的情况?甚至,每次传输,走的路径都不同。
通过实验的方式,找到一个合适的发送速率。 开始的时候按照一个小的速率发送,如果不丢包,可以提高下速率(扩大窗口大小);如果出现丢包,则立即把速率再调小,反复执行上述过程。
2.2.4 延时应答
我们知道TCP可靠性的核心,就是确认应答。但是ACK要发,并不是立即发送,而不是稍等一会再发。
延时应答的效果,就是通过这个延时,让接收方应用程序,趁机多消费点数据,此时反馈的窗口大小,就会更大一点。此时发送方的发送速率也能快一些(同时也能满足让接收方能够处理过来)
那么所有的包都可以延时应答? 答案不是
- 数量限制:每隔N个包就延时应答一次
- 时间限制:超过最大延迟时间就延时应答一次
具体的数量和超时时间,依据操作系统不同也有差异;一般N取2,超时时间取200ms
2.2.5 捎带应答
捎带应答是基于延时应答的,由于客户端服务器之间的通信模型,通常是一问一答这种模式。
为啥四次挥手,有可能是三次挥完?? 同理~ 捎带应答起到的效果!
3. 面向字节流
- 可能会出现粘包问题,首先要明确 此处的 ”包“,是应用层的数据包
- 在TCP的协议头中,没有如同UDP一样的”报文长度“这样的字段,但是有一个序号这样的字段
- 站在传输层的角度,TCP是一个一个报文过来的,按照序号排好序放在缓冲区中
- 站在应用层的角度,看到的只是一连串的字节数据
- 那么应用层看到了一连串的字节数据,就不知道从哪个部分开始到哪个部分结束
那么如何解决粘包问题, 定义分隔符或者是约定长度
思考:对于UDP协议来说,是否也存在”粘包问题“呢?
- 对于UDP,如果还没有向上层交付数据,UDP的报文长度仍然在。同时,UDP是一个一个把数据交给应用层,有很明确的边界。
- 站在应用层的角度,使用UDP的时候,要么收到完整的报文,要不么收,不会出现”半个“的情况。
4. 异常情况
4.1 进程关闭/进程崩溃
进程没了,socket是文件,随之也会被关闭。 但是虽然进程没了,但是连接还在,仍然可以继续四次挥手
4.2 主机关机(正常流程关机)
- 先杀死所有的用户进程,后续就是进程没了,socket是文件,随之也会被关闭。 但是虽然进程没了,但是连接还在,仍然可以继续四次挥手。
- 如果挥完更好,如果没挥完。比如对方发的 FIN 过来,咱们没来得及ACK就关机了~ 此时对端就会重传FIN,发现都没有ACK,尝试重置连接,如果还不行,就直接释放连接!
4.3 主机断电(拔电源,迅速关机)
- 对端是发送方:对方接收不到ACK => 超时重传 => 重置连接 => 释放连接
- 对端是接收方:对方没法立即知道,你这边没来得及发送数据,还是直接没了
- TCP内置了心跳包机制, 对端是接收方,会定期向发送方发一个心跳包,如果有及时的回应说明状态良好,否则就是挂了
4.4 网线断开
这个情况和主机断电是一样的~
5. 应用场景
- TCP可靠传输,效率没那么高:绝大部分场景下,都可以使用TCP
- UDP不可靠传输,效率高:对于效率要求比较高,但是可靠性没那么高的场景(同一个机房内部的内网之间的数据传输)