目录
1. TCP 协议格式
2. TCP 原理
(1) 确认应答
(2) 超时重传
(3) 连接管理
a) 三次握手
b) 四次挥手
(4) 滑动窗口
(5) 流量控制
(6) 拥塞控制
(7) 延时应答
(8) 捎带应答
3. TCP 特性
4. 异常情况的处理
1) 进程崩溃
2) 主机关机 (正常流程)
3) 主机掉电 (非正常)
4) 网线断开
5. TCP 和 UDP 之间的对比
6. 基于 TCP 的应用层协议
前文:TCP 的使用
1. TCP 协议格式
想要知道 TCP 的原理,我们首先就得了解 TCP 协议报文的格式。
TCP 数据报分为两部分,报头 + 载荷(应用层数据报),选项(可以有,也可以没有)也是报头中的一部分。
大家都知道,TCP 的特点是:有连接,可靠传输、面向字节流,全双工。
其中可靠传输,是 TCP 最最最核心的特性(初心)。
可靠传输,不是说发送方能够 100% 的传输给接收方(再厉害的技术,抵不过挖掘机一铲子)
而是退而求其次:
1) 发送方将数据发出去之后,数据到没到接收端,发送方能心里有数,知道接收方是否收到数据
2) 如果发现接收端没收到数据,能采取一系列的措施进行补救。
那么 TCP 是如何保证可靠传输的呢?这就涉及到一个非常关键的机制,确认应答。
2. TCP 原理
(1) 确认应答
发送方,把数据发给接收端之后,接收端会返回一个应答报文(acknowledge, ack)。
发送方,如果收到这个应答报文了,就说明发送方把数据成功传给对方了。
而在网络传输过程中,因为每个数据包传输是走的路径不同,所以就可能会出现数据包的 "先发后至" 的情况。
举个例子:
为了处理这种情况,TCP 就需要完成两个工作:
1. 确保应答报文和发出去的数据,能够对得上号,不出现歧义。
2. 确保在出现先发后至的现象时,能够让应用程序这边仍然按照正确的顺序来理解数据。
而引入序号和确认序号,给每条数据进行编号,针对性应答,或者按照序号,对数据进行重新排序,就能解决上述问题。
TCP 是按照字节的方式来编序号的,如图:
TCP 的初心就是实现可靠传输,达成可靠传输的核心机制就是确认应答,通过确认应答,发送方就能够知道数据是否到达了接收方。如果数据到达了,那么接收方返回的 ack 里面的确认序号就是下一次发送方发送的数据的第一个字节。如果数据没到达,那么接收方就不会返回 ack。(不考虑滑动窗口的情况下)
那么如何区分一个数据报是普通的数据,还是 ack 应答数据呢?
可以通过 TCP 报文协议中的,六位标志位的 ACK 来确认,数据报是否是应答报文。
如果 ACK = 1,则说明是应答报文,其中的 "确认序号字段" 就能够生效。
如果 ACK = 0,那么数据报中的 "确认序号字段" 不会生效。
确认应答,是 TCP 最核心的机制,支持了 TCP 的可靠传输。
但是仅仅只有确认应答还不够,还需要其他的机制来辅助,超时重传就是这样的一个辅助机制。
(2) 超时重传
确认应答,描述的是一个比较理想的情况,
如果网络传输中,出现丢包了,那么发送方就没有办法收到 ack 了,那该怎么办呢?
通过超时重传,就可以解决上述问题。超时重传,是针对确认应答机制的补充。
超时重传,就是等待一定的超时时间,发送方还没有收到 ACK,发送方会主动把刚刚的数据重新传输一遍给接收端。
可以先来思考一下,为什么会出现丢包。
因为丢包是一个随机的事件,所以在 TCP 传输过程中,丢包就存在两种情况:
第一种是发送方发的数据报丢了,第二种是接收方发送的应答报文丢了。
如图:
所以当引入可靠性的时候,是会付出代价的,最明显的两方面:
1. 传输效率 因为有超时重传,所以传输数据效率不高。(这也是 UDP 不会被 TCP 完全取代的意义)
2. 复杂程度
这里其实还有一个问题:
(3) 连接管理
连接管理就是建立连接和断开连接。
其实 TCP 建立连接的过程也叫做三次握手,断开连接的过程叫做四次挥手。
那这个握手到底是什么意思呢?
其实握手就是打个招呼,就是给对方传输一个简短的,没有业务数据的数据报,通过这个数据报来唤起对方的注意,从而触发后续的操作。四次挥手的 "挥手" 和三次握手的 "握手" 是同一个意思。
举个例子:比如说你走路遇到熟人的时候,对方主动跟你说 "你好" "hello" 之类的,打招呼的内容通常没有什么实际的意义,就只是起到唤起对方的注意力的效果。
前面也提到过,TCP 是有连接的,需要主机双方各自保存对端的信息。
a) 三次握手
那 TCP 的三次握手具体流程是怎样的呢?
TCP 的三次握手,TCP 在建立连接的时候,需要通信双方一共打三次招呼,才能完成建立连接。
TCP 的初心,是为了实现可靠传输,确认应答是核心,超时重传等机制是辅助。
但进行确认应答和超时重传有个大前提,那就是当前的网络环境是基本可用的,通畅的,
如果当前网络已经存在重大故障了,那么可靠传输是无从谈起的。
三次握手的核心作用一:
投石问路,确认当前网络是否是畅通的。
如果连 syn 和 ack 这样没携带业务数据的数据报都不能够正常传输的话,那么之后要传输的携带了业务的数据报也不可能正常传输。
三次握手的核心作用二:
让发送方和接收方都能确定自己的发送能力和接收能力均正常。
三次握手的核心作用三:
让通信双方,在握手过程中,针对一些重要的参数,进行协商。
TCP 通信过程中,序号是从几开始,就是双方协商出来的(一般不是从 1 开始),
每次连接建立的时候,都会协商出一个比较大的,和上次不太一样的值。
这样做是放了防止上一次遗留的数据,影响到本次数据的传输。
有的时候,网络如果不太好,那么客户端和服务器就会断开,再重新建立连接,重连的时候,就有可能在新的连接建立好的时候,旧的数据姗姗来迟,这种迟到的数据报,是应该被丢弃的,而根据本次连接的正常数据报的序号,对比收到的数据报的序号,如果发现差别非常大的话,就说明收到的数据报是旧连接迟到的数据报,那就可以直接丢弃掉。
三次握手可以的话,那四次握手行不行?两次握手行不行?
四次握手是可以的,但是没必要,将中间的两次合并成一次能够提升效率。
两次握手是不可以的,因为少了最后一次的握手,服务器就无法确定自己的发送能力是否正常和客户端的接收能力是否正常。
b) 四次挥手
断开连接的过程就是四次挥手,和三次握手类似。
TIME_WAIT 的意义就是当主动连接断开方发送完最后一次 ACK 时,先进入 TIME_WAIT 状态,等待 2MSL 的时间,如果在这个时间内,ACK 丢包了,对端重传了 FIN,那么 主动断开连接方就能马上返回 ACK,这样对端重传的 FIN 才有意义。
MSL:是一个可配置的参数,这个参数数值是拍脑门拍出来的。
(4) 滑动窗口
前面的确认应答 ,超时重传,连接管理都是用来保证 TCP 的可靠性的。
滑动窗口是用来提高效率的,其实是一种亡羊补牢。
TCP 因为引入了可靠传输,所以传输的效率不太高(多出了一些等待 ACK 的时间,单位时间内能传输的数据就减少了)
而滑动窗口,就是用来减小可靠传输对性能的影响的。
但是再怎么提高 TCP 的效率,也是不可能超过没有引入可靠传输的 UDP 的效率的。
那么具体滑动窗口是怎么做的才能提高效率呢?
其实 TCP 慢就慢在要等对端的 ACK,那就可以在保证可靠传输的前提下,将等待时间缩小就好了。
那就可以进行批量传输数据,这样做效率就上来了。
效率是提高了,但是 TCP 的核心是可靠传输,在提高效率的前提是数据能可靠传输。
上述滑动窗口中,确认应答是可以正常工作的。
但如果在滑动窗口的过程中,出现丢包了,那该怎么办呢?
这里的重传,相比于前面的超时重传,有些不同。
还是得分两种情况讨论:
如果 ACK 全丢了呢?那此时的网络肯定出现严重故障了,平时丢包率达到 10% 都算是比较严重的了,现在直接丢包率 100% ,就别想着 "可靠传输" 了。
如果通信双方,传输数据的量比较小,也不频繁,就仍然是普通的确认应答和超时重传。
如果通信双方,传输数据的量比较大,也更频繁,就会进入到滑动窗口的模式,按照快速重传的方式处理。
(5) 流量控制
通过滑动窗口的方式传输数据,效率是会提升的。
窗口越大,传输效率就会越大。(一份等待时间,等待的 ack 更多了,总的等待时间就更少了)
那么滑动窗口的窗口大小是设置的越大就越好吗?
显然不是的,提高效率的前提是保证可靠传输,如果因为传输的速度太快,接收方处理不过来,就会导致接收方出现丢包的情况,发送方还得重传。
而流量控制,就是站在接收方的角度,反向制约发送方的传输速度。
发送方发送的速率,不应该超过接收方的处理能力。
那么如何知道接收方的处理能力是多少呢?
如图,可以通过 接收方的接收缓冲区的剩余空间大小,来衡量处理能力的大小。
接收方每次收到数据之后,都会把接收缓冲区剩余空间大小通过 ack 返回给发送方,
发送方就会按照这个数值来调整下一轮的发送速度。
如图:
(6) 拥塞控制
流量控制,考虑的是接收方的处理能力,
但是不仅仅要考虑到接收方的处理能力,还要考虑网络通信过程中经过的节点(路由器/交换机)的处理能力,也就是说,还需要考虑整个通信的路径,如果中间某个节点的传输速度达到了瓶颈,那么此时,也会对整体的传输产生影响。
拥塞控制,就是 考虑/衡量 通信过程中,中间节点的情况。
如图:
但是关键的问题,在于怎么衡量中间节点。
之前接收方的处理能力,是很好衡量的。
由于中间节点,结构更复杂,更难以直接的进行量化。
因此可以使用 "实验" 的方式,来找到合适的值。
可以让发送方先按照比较低的速度开始发送数据(小窗口),如果数据传输过程非常顺利,也没有丢包,那就再尝试使用更大的窗口,更高的速度进行发送(一点一点变化),随着窗口不断增大,达到一定程度,可能中间节点就会出现问题了,此时这个节点就可能会出现丢包。发送方发现丢包了,就把窗口大小再调整小,此时如果发现还是继续丢包,那就继续缩小。如果不丢包了,就继续尝试变大。
在这个过程中,发送方不停的调整自己的窗口大小,逐渐达成一个 "动态平衡".
这种做法,就相当于把 "中间节点" 视为一个整体,通过 "实验" 的方式,来找到中间节点的瓶颈在哪里。
上述过程如图:
流量控制和拥塞控制都是在限制发送方的发送窗口大小,
最终实际发送的窗口大小,是取 流量控制 和 拥塞控制 中的较小值。
(7) 延时应答
A 把数据传给 B,B 就会马上返回 ack 给 A (正常)。
也有的时候,A 传输给 B,此时 B 等一会再返回 ack 给 A (延时应答)。
本质上也是为了提升传输效率。
发送方的窗口大小,就是传输效率的关键。
流量控制这里,就是根据接收方的接收缓冲区的剩余空间,来控制发送方的发送速率的,
如果有办法,能让流量控制得到的窗口更大点,发送速率就更快点(大点的前提是,能让接收方处理的过来)
通过延时返回 ack,给接收方更多的时间读取接收缓冲区的数据,此时接收方读了这个数据之后,缓冲区的剩余空间,就变大了,返回的窗口大小,也就更大了。
比如,初始情况下,缓冲区的剩余空间是 10kb,如果立即返回 ack,就返回了 10kb 这么大的大小窗口。如果延时个 200ms 再返回,那么在这 200ms 的过程中,接收方的应用程序,又读了 2kb,此时,返回的 ack 就是返回 12kb 的窗口了。
延时应答,才促成了前面的四次挥手,能够三次挥完。
(8) 捎带应答
在延时应答的基础上,进一步提高效率。
3. TCP 特性
TCP 的特性是:有连接,可靠传输,面向字节流,全双工
面向字节流的特性是:
传输数据的时候可以非常灵活,可以一次传输一个字节,也可以一次传输多个字节。
但是这里存在一个问题:粘包问题(不是 tcp 独有的,而是面向字节流的机制都有类似的情况)
这里的包指的是应用层数据包,如果同时有多个应用层数据包被传输过去,此时就容易出现粘包问题。
如图:
那么该如何解决粘包问题呢?
核心思路:通过定义好应用层协议,明确应用层数据报之间的边界。
1. 引入分隔符
2. 引入长度
比如使用 \n 作为分隔符:
引入长度:
4. 异常情况的处理
如果在使用 tcp 的过程中,出现意外,会如何处理?
1) 进程崩溃
进程崩溃,本质上就是进程没了,进程终止了,那么文件描述符表就释放了,也就相当于调用 socket.close() ,此时就会触发 FIN,对方收到之后,自然也就会返回 ACK 和 FIN,这边再进行 ACK,这里就是一个正常的四次挥手断开连接的流程。
TCP 的连接,可以独立于进程存在。(进程没了,TCP 连接不一定没)
2) 主机关机 (正常流程)
在进行关机的时候,就是会先触发强制终止进程的操作(相当于 1)
此时就会触发 FIN,对方收到之后,自然会返回 ACK 和 FIN。
此时,不仅仅是进程没了,整个系统也关闭了。如果在系统关闭之前,对端返回的 ACK 和 FIN 到了,此时系统还是可以返回 ACK,进行正常的四次挥手的。如果系统已经关闭了,ACK 和 FIN 迟到了,就无法进行后续 ACK 的响应。站在对端的角度,以为是自己的 FIN 丢包了,就会重传 FIN,连续重传好几次都没有响应,最后对端就会放弃连接(把持有的对端信息删除)。
3) 主机掉电 (非正常)
主机掉电,是一瞬间的事情,还来不及杀进程,也来不及发送 FIN,主机直接就停机了。
1. 如果对端是在发送数据(接收方掉电),发送的数据就会一直等待 ack,触发超时重传,重传好几次还是没有响应,就会触发 TCP 的连接重置功能,发起复位报文段(RST = 1),如果复位报文段发过去之后也没有效果,此时就会释放连接了。
2. 如果对端是在接收数据(发送方掉电),对端还在等待数据到达,等了半天没消息,此时其实也无法区分,是发送方没发消息,还是发送方挂了。
针对这种情况,TCP 提供了心跳包的机制,接收方也会周期性的给发送方发送一个特殊的,不携带业务数据的数据包,并且期望对方返回一个应答,如果对方没有应答,并且重复了多次之后,仍然没有,就视为对方挂了,此时就可以单方面释放连接了。
4) 网线断开
网线断开和刚刚的主机掉电非常类似。
如何识别某个机器是否挂了,一般都是通过心跳来检测的。
5. TCP 和 UDP 之间的对比
TCP 有连接,可靠传输,面向字节流,全双工。
UDP 无连接,不可靠,面向数据报,全双工。
TCP 的优势是可靠传输,TCP 适用于绝大部分场景。
UDP 的优势是更高效率,UDP 适合于对 "可靠性不敏感","性能敏感" 的场景,比如局域网内部(同一个机房)的主机之间的通信。
如果要传输比较大的数据包,TCP 优先。(UDP 有 64kb 的限制)
如果要进行 "广播传输" ,优先考虑 UDP。UDP 天然支持广播,TCP 不支持(得自己写代码实现)
6. 基于 TCP 的应用层协议
- HTTP
- HTTPS
- SSH
- Telnet
- FTP
- SMTP
- 还包括程序员自身写 TCP 程序时定义的应用层协议