之前我们说了 UDP 协议,也说到了 UDP 协议和 TCP 协议的对比,知道了 TCP 协议有那么一些复杂,所以这次就来仔细说说这个复杂的 TCP

前言

提到 TCP 你第一想到的应该就是两个字 “靠谱”~ 因为它的所有设计都是围绕着这个任务展开的。

报文格式

TCP 报文格式

很多人一看到这个就头疼了,因为格式好复杂啊,和 UDP 对比起来,怎么多了那么多东西呢?

别急,其实并不是很多,而且只要你想着为什么,你就知道会有什么。

  • 首先是两个端口,这个和 UDP 一样,肯定需要端口,不然的话不知道是发给谁的
  • 然后是序号和确认序号,序号是为了解决乱序的问题,如果没有序号,那么我是不知道那个先那个后,同时我还要告诉对方我收到了这个序号的信息,所以需要一个确认序号,确认的方式是告诉你我要的下一个序号是多少
  • 首部长度也很好理解,告诉你首部有多长,你就知道前面有多长,你才知道数据从哪里开始
  • 中间那几个标识位需要知道的是:
    • SYN:建立连接时用的,建立连接会标识为 1
    • ACK:回复你建立连接,建立连接之后标识为 1
    • RST:重新连接
    • FIN:结束连接
  • 窗口大小,接收方告诉发送方别发太快了或者太慢了,用于流量控制

三次握手

过程

这个问题已经在面试的时候被问烂了,总是有面试官喜欢问网络,而作为第一个开头炮,三次握手总是被提及。

三次握手

中间的标识位,可能就有人从来就没弄懂,可能即使懂了,也是靠死记硬背的,不复习肯定忘记。

其实没有那么玄乎啦。分两个部分来看:

  1. 大写的 SYN 和 ACK 其实上面都已经说了,就是建立连接的时候,第一个给你的报文里面 SYN 标识位为 1 告诉你开始建立连接了,否则你怎么知道这次是建立连接呢?而 ACK 就是告诉你我收到了,可以建立连接了
  2. 而小写的 seq 和 ack 就更简单了,他们表示的就是上面说的序号,确认号 ack 为收到的 seq 序号 + 1

需要注意的是,序号每次都是不一样的,它是一个 32 位的每 4ms 递增的一个值,为什么这样设计呢?是因为如果每次序号都从 1 开始,那么势必在多次发送或者重连之后导致序号重复,误以为收到的新的数据,其实是旧数据,只不过序号相同。

为什么要三次

等你描述的不清不楚的时候,面试官下一个问题就来了,为什么要做三次握手呢?

其实这个问题也不复杂,你时刻要记住网络其实很不稳定的,不是两根网线连接两边就好了,中间可能隔了一个太平洋。所以丢包太常见了。

而作为可靠的 TCP,三次握手能保证每个人都能有至少一次正反馈,对于发送方来说肯定有一问一答,对于接收方来说也有一问一答,否则都属于发了没反馈。

还有一个重要原因就是重试,如果由于网络原因,第一次建立连接迟迟收不到反馈,就会发第二个,而对于接收方来说,很有可能就会收到多个一模一样的建立连接的请求,如果只是一次或者两次,那么 就会建立很多无用的连接 占用资源。

四次挥手

过程

四次挥手

其实当你把三次握手想清楚了,四次挥手也就不足为奇。对于四次挥手来说,其实就是从 SYN 表示位改到了 FIN 标识位,对于数据的序号还是一样的规则。只不过确实有几个要点需要注意。

  • B 收到 FIN=1 的时候,此时 TCP 属于并关闭状态了,但是很有可能 B 还有数据需要发送,但是 A 收不收就是 A 自己决定了。
  • A 收到 FIN=1 回复了之后,还有一个 2MSL(最大报文存活时间)的 TIME-WAIT 状态

为什么要四次

其实和建立连接时的原因一样:每个人都收到了一问一答,而建立连接时还没有正在发送的数据,所以中间那次可以复用,所以就三次了,而断开连接显然是一方主动发起的,而另一方是毫无准备的,就如同你关闭显示器,服务器是毫无准备的。

还有原因就是如果 A 告诉 B 之后直接走了,不管了,那么 B 就懵逼了,这个时候可能 B 还在发数据,B 就算发起结束,也没人回应了,B 就只能傻等着;如果 A 告诉 B,B 不管了,那么也有问题了,A 不知道 B 收到没有,B 如果没收到 A 还要继续发…

为什么需要 TIME-WAIT

这也是一个很奇怪的问题,明明都已经断开连接了,一直保持这个状态等着干啥?

  • 原因 1:如果这个时候不等了,直接断开连接,那最后一个挥手可能就到不了了
  • 原因 2:如果等的时间短,直接关闭,因为关闭了,那么就可以和别人建立新的连接,那么很有可能还在网络上的老数据会跑到新的连接里面捣乱了

为了可靠你究竟做了什么

TCP 为了做一个可靠的人,付出了很多你看不见的努力

重传

  1. 如果一个发送的报文在超时时间内没有收到确认,那么就会重新发送,超时时间并不是固定的,因为网络的环境不同,所以利用了一个 自适应重传算法,会根据当前的往返时间进行加权平均进行计算。

  2. 当然这样还是不够的,因为触发的周期还是太长,于是还有一个机制叫 快速重传,当接收方收到序号 5,但是现在还是要序号 3 的时候,那么会发送几个冗余的请求来要中间丢的报文。

滑动窗口

窗口在很多网络协议中都有被设计来控制流量,我们知道如果发的太猛不管客户端死活,或者发的太慢,让客户端饿死都是不好的。所以窗口的存在,就是维持一个返回,让你知道后面该不该往下发了,只有一家发送并得到一个确认,窗口才会移动或改变大小。

最重要的是,当窗口被设置为 0 时,则发送端就不能再发送数据了。

拥塞控制

流量控制是为了控制客户端和服务端的处理和发送速度,不至于压垮对方。而拥塞控制则是避免去压垮网络。说白了,可能你客户端处理能力很强,一收到处理,你怎么发都可以,但是网络带宽不允许你这么干。

拥塞控制

这个图是不是很熟悉?没错,只要你大学里面学过计算机网络,这个图太经典了,可能当时你都不理解它为什么要这么设计,一开始慢慢发,然后越来越快,然后进行拥塞控制,然后到超时,再慢开始。

慢开始、拥塞避免、快重传、快恢复就是拥塞控制所做的事情了。

注意的是,图中的第 4 个点就是上面说的快速重传 3 个冗余 ACK 来找到丢的报文,并且是从 5 开始的,并不是再慢开始。

有个比喻很形象这里的控制,就像往一个管子里面倒水,你不知道管子中间多粗,总是先倒的很快;等水满出来了,哦,知道了,倒太快了,就慢一点,等水下去一点然后再开始倒。

总结

其实回头看看,是不是 TCP 并没有想象中的那么复杂。所以它的负责,它的所作所为都是为了成为一个可靠的人,面对现在复杂的社会网络环境,它不得不做一个可靠的人,如果没有它可靠的负责,可能我们聊天聊到一半消息不见了,也不是不可能的。