目录
1.TCP协议定义
2.TCP原理
2.1确认应答机制
2.2超时重传机制
2.3连接管理
2.3.1建立连接(三次握手)
2.3.2断开连接(四次挥手)
2.4滑动窗口
2.5流量控制
2.6拥塞控制
2.7延迟应答
2.8捎带应答
2.9面向字节流(粘包问题)
2.10异常处理
1.TCP协议定义
TCP即Transmission Control Protocol,传输控制协议。TCP特点是有链接、面向字节流、全双工、可靠传输。
图解TCP协议段格式
各个字段的含义:
- 源/目的端口号:表示数据是从哪个进程来,到哪个进程去;
- 32位序号:指的是报文段序号,有时候我们会发多条数据,为了方便回答,进行编号;
- 32位确认号:这个确认号是针对序号设定的,为了防止回复串行;
- 4位TCP报头长度:表示该TCP头部有多少个32位bit(有多少个4字节);所以TCP头部最大长度是
- 15 * 4 = 60
- 6位标志位:
- URG:紧急指针是否有效
- ACK:确认号是否有效
- PSH:提示接收端应用程序立刻从TCP缓冲区把数据读走
- RST:对方要求重新建立连接;我们把携带RST标识的称为复位报文段
- SYN:请求建立连接;我们把携带SYN标识的称为同步报文段
- FIN:通知对方,本端要关闭了,我们称携带FIN标识的为结束报文段
- 16位窗口大小:控制每次滑块的流量
- 16位校验和:发送端填充,CRC校验。接收端校验不通过,则认为数据有问题。此处的检验和不光包含TCP首部,也包含TCP数据部分。
- 16位紧急指针:标识哪部分数据是紧急数据;
2.TCP原理
TCP中可靠性核心机制有很多,在这里只对其中最重要的10个最重要的核心机制进行讲解。
2.1确认应答机制
TCP最核心的特点就是可靠性传输,确认应答机制就是保证可靠性传输的根本了,TCP在发送数据时会一起过去一个同步报文段(SYN),当接收方收到数据后,接收方会给发送方 发送一个应答报文(ACK)。如果发送方没有接收到应答报文大概率就是丢包了(下面说解决方案)。
上方的例子是简单的考虑发一个消息回答一个消息,如果发送方发送了2条消息过来,第一条还没到,第二条已经到了(后发先至),那接收方应该如何对每个消息进行精确回答呢?这个就要提到序号/确认号了,他两个的作用就是让每条信息对应到每个回答的问题上去。看下方例子:我给女神发了两条信息,对这两条信息添加序号,当女神回复时,回复的信息就会携带确认号,这样就避免出现不确认女神先回答那个问题的尴尬场景。
在TCP中并没有一条消息、两条消息的概念,上方的例子只是帮助理解才用了,TCP中的数据传输是面向字节流的,TCP会针对每一个字节进行编码,从前向后,把每一个字节分别配一个编号。
2.2超时重传机制
在上方的确认应答中,我们提到了没有接收到应答报文的问题(丢包),超时重传机制就填了这个丢包的坑, 当发送方迟迟没有收到接收方的应答报文(ACK),那么接收方会再发送一边同步报文段(SYN),若过了一会还是没有接收到应答报文(ACK),一直尝试上方的操作,当超过一定的时长,就会停止发送;此时,TCP会重写建立连接,如果重置建立连接也失败了,就放弃网络通信了。
触发超时重传有两种方式:
- 数据直接丢了,接收方没收到,自然不会发ack
- 接收方收到数据了,返回的ack丢了
✨情况1很好理解,如果发送方的东西都没有到,那他就不会接收到接收方的回应,发送方感觉不对劲,就一直进行发送。
✨情况2,会有特殊的情况,当发送方发送数据时,接收方可能会接收到相同的数据报,不用担心,TCP帮我们给降重,不会出现一式两份的错误。
面试题:TCP如何实现可靠性传输的?
答:确认应答+超时重传(两个的具体实现)
2.3连接管理
连接管理中包含建立连接(三次握手)与断开连接(四次挥手),如图是两个操作的步骤,下方会进行解读,这在提一下:建立连接一定是客服端发送的请求,断开连接可能是客服端提出的也可能是服务器提出的。
2.3.1建立连接(三次握手)
三次握手指通信双方,进行三次网络交互,相当于客户端和服务器,通过三次交互,建立了连接关系。
三次握手就是建立客户端和服务器的连接关系,举个易懂的例子:
A对B说你好吗?这就相当于发送了同步报文段(SYN);很好,你呢?相当于发送了应答报文(ACK)+同步报文段(SYN);me too。相当于发送了应答报文(ACK)。
如果你听别人说这是四次握手,其实这也是对的,我们常常将B方的ACK和SYN一起发送给A,如果将这两个部分拆开发送,那就是四次,如果合并到一起就是三次。
总结三次握手的作用:
- 三次握手相当于投石问路,验证客户端和服务器各自的发送能力和接收能力是否正常。
- 在握手的过程中,双方还会“协商”配置一些参数。
2.3.2断开连接(四次挥手)
四次挥手就是客户端发送一个结束报文,服务器收到后回应一下,然后服务器发送一个结束报文,客服端再回应一下,在服务器会有发送结束报文时,我们无法确定他是什么时候发的,他的时间不固定,因此我们一般将ACK和FIN两个报文分开发送。
2.4滑动窗口
TCP不仅要保证可靠性,还有保证效率,因此他来了---滑动窗口。
上方是我们刚才举得例子,此时A这边就要花费大量的时间等待ACK,这样会影响到传送的速率,降低TCP的传输效率。
想要提高效率,就需要缩短等待得到时间,如何解决这个问题?那就是批量发送数据,一次发送多条数据,一次等待多个ACK,这一总的等待时间减少了,整体的效率就提高了,这个批量传输就是滑动窗口。我们把不需要等待,就直接发送的最大数据量称为“窗口大小”,上方图片中的窗口大小是4000。
他是如何滑动的呢?收到第一个ACK后,滑动窗口向后移动,继续发送第五个段的数据;依次类推,这个形式运行起来就像一个窗口在滑动,因此才取名为滑动窗口。
如果在这种情况下发送丢包了,如何重传?还是分两种情况
✨情况1:数据包已经抵达,ACK被丢了
这种情况下,部分ACK丢了并不要紧,因为可以通过后续的ACK进行确认;就拿上方的图片解释,在图中1001、3001、4001丢了,但6001传了过去,这一主机A可以通过判断6001传了过去来推测前方的也都传过去了。
✨情况2:数据包就直接丢了
由于1001-2000这之间的数据丢了,所以接收方仍然在索要1001,接下来的几次ACK确认序号都是1001, 发送方就会感觉到异常,重写发送1001-2000这个数据了,发送成功后,接收方会返回7001这个确认序号,因为2001-7000之间的数据我们已经收到过了,因此,我们再要数据就要没有发送过来的数据了,看下图:
当1001这个数据重写传过来之后,缺少的拼图补全了,接下来就会找下一个缺少的部分,如果没有缺少的部分,就找到最大的报文向索要。
这种丢包重传的方式被称为“高速重发控制(快重传)”,只重传丢失的数据,可以视为是超时重传机制在滑动窗口下的变形; 如果当前传输数据密集, 按照滑动窗口的方式来传输, 此时按照快速重传来处理丢包; 如果当前传输数据稀疏, 就不再按照滑动窗口方式了传输了, 此时还是按照之前的超时重传处理丢包。
2.5流量控制
流量控制的特性:
- 接收端将自己可以接收的缓冲区大小放入 TCP 首部中的 "窗口大小" 字段,通过ACK端通知发送端;
- 窗口大小字段越大,说明网络的吞吐量越高;
- 接收端一旦发现自己的缓冲区快满了,就会将窗口大小设置成一个更小的值通知给发送端,如果发现满了。还是阻塞等待;
- 发送端接受到这个窗口之后,就会减慢自己的发送速度;
- 如果接收端缓冲区满了,就会将窗口置为0;这时发送方不再发送数据,但是需要定期发送一个窗口探测数据段,使接收端把窗口大小告诉发送端。
接收缓冲区就像一个水池,发送数据就是向里面灌水,当水多的时候,我们就让它流慢一点,如果满了,就停止流动。这个上方的接收缓存区大小为3000,传输三千后,接收缓冲区满了就会阻塞等待,发送方会过每一段时间发一个窗口探测报文,如果探测发现对方的接收缓冲区不是0了,就继续发送数据。
2.6拥塞控制
虽然TCP有了滑动窗口这个大杀器,能够高效可靠的发送大量的数据。但是如果在刚开始阶段就发送大量的数据,仍然可能引发问题。
因为网络上有很多的计算机,可能当前的网络状态就已经比较拥堵。在不清楚当前网络状态下,贸然发送大量的数据,是很有可能引起雪上加霜的。
TCP引入慢启动 机制,先发少量的数据,探探路,摸清当前的网络拥堵状态,再决定按照多大的速度传输数据;
如图这就是TCP的慢启动:
- 先给一个非常小的窗口,然后以指数的形式翻倍增长。
- 增长到基本稳定了再进行线性增长。
- 增长到网络拥塞,就循环进行上方的操作。
通过上方的实验发送,找到一个合适的发送速率,确认实际发送方的窗口的大小,
实际的窗口大小=min(拥塞串口,流量控制窗口)
2.7延迟应答
延迟应答是提高TCP传输效率的机制,通过这个延时,让接收方的应用程序趁机多消费点数据,此时反馈回来的窗口大小就会大一点,发送速率就可以快一点。
如果发送方发送了10条消息,接收方立即应答的话,就是10条消息未处理,如果我们对这些消息处理一部分(比如处理了5条)了,再去应答,我们就可以回答5条未处理,那么就是有5条的空闲区域,发送方就可以再次发送5条过来。
2.8捎带应答
在延迟应答的基础上,我们发现,很多情况下,客户端服务器在应用层也是 "一发一收" 的。意味着客户端给服务器说了 "How are you",服务器也会给客户端回一个 "Fine, thank you";那么这个时候ACK就可以搭顺风车,和服务器回应的 "Fine,thank you" 一起回给客户端。
2.9面向字节流(粘包问题)
因为TCP是面向字节流的,我们无法确定一条消息是从哪里开始,从哪里结束的,因此我们需要对读取数据采取一定的策略:
- 对于定长的包,保证每次都按固定大小读取即可;例如上面的Request结构,是固定大小的,那么就从缓冲区从头开始按sizeof(Request)依次读取即可;
- 对于变长的包,可以在包头的位置,约定一个包总长度的字段,从而就知道了包的结束位置;
- 对于变长的包,还可以在包和包之间使用明确的分隔符(应用层协议,是程序猿自己来定的,只要保证分隔符不和正文冲突即可);
2.10异常处理
⚒️1.进程关闭/进程崩溃
进程没有了,socket是文件,随之被关闭,虽然进程没有了,但是连接还在,仍然可以继续四次挥手结束。
⚒️2.主动关机(正常流程关机)
会关闭所以的进程,也会出发四次挥手,如果四次挥手完成了,就正常关闭;如果没有挥完,例如:对方发fin过来了,这边还没有来的及发送ack,就会发送重传,如果重传几次仍没了ack,就是尝试重置连接,如果连接不是,就释放连接。
⚒️3.主动掉电(拔电源的情况)
瞬间机器就关了,来不及进行任何挥手操作。
1.接收方掉电
收不到ack=>超时重传=>重置连接=>释放连接
2.发送方掉电
发送方断电,接收方不能立即知道,你这边是没有及时发送新的数据?还是直接噶了?
这就不得不提到TCP的一个机制“心跳包”这个机制就是保活机制,它的特点就是周期性,接收方会周期性的ping一下,发送方就就会发送一个pong来回复接收方,如果没有这个回复的话,怕就是挂了。