计算机网络 TCP 协议总结

TCP 相关知识

TCP/IP 协议占据了互联网通信的一大半江山,特别像 TCP 这种保障端到端的可靠传输更是相当重要,关于它的实现也很复杂,今天介绍下关于 TCP 的相关重要知识。

我们先来看下 TCP 的头格式:

TCP 头部

我们看到有一个源端口目的端口,这 2 个元素再加上 IP 层的源地址目的地址,就可以用来表示 TCP 的某个连接了,相当于数据库里的唯一标识。所以当我们发起一个 TCP 连接时,会发现如果存在相同的端口在运行时,操作系统是不允许的,只有这样才能保证唯一连接的存在,避免数据接收混乱。

在上图中,我们会看到以下几个元素,它们是 TCP 协议对几个重要问题的保障:

  • 序列号(seq):数据包的序号,通过序号来确认包的连续性,解决包的乱序问题。
  • 响应号(ack):针对上面字段的确认序号,比如服务器接收到客户端的请求,里面包含了 seq 序号,此时服务器响应回去时,会进行 ack = seq + 1 的字段设置,表示已接收到的累积数据。
  • Window:滑动窗口使用,用来反馈接收方接下来能处理的包大小,防止双方对数据包的处理能力不对等,主要解决了流控问题。
  • TCP Flag:TCP 包的类型,用来辅助 TCP 的阶段处理,比如 SYN 表示建立连接,FIN 表示关闭连接。

TCP 状态流转

协议之所以会存在,就在于双方需要互相配合协作,以确定哪个阶段该做哪些事情。在 TCP 协议总体划分为建立连接数据传输连接断开这三个过程。这些阶段将会涉及对应状态的流转:

[图片上传失败...(image-4eefea-1635261169238)] (图片地址)

TCP 的三次握手、四次挥手

而在上面的状态流转中,最重要,也是经常会提起的关于连接建立的三次握手以及连接断开的四次挥手

三次握手、四次挥手

从上面的流程图中,我们会发现对于 Client 端来讲,经历了一个请求-确认过程;对于 Server 端来讲,也经历了一个请求-确认过程。这就相当于双方都已经互相确认过眼神了。所以,三次握手对于连接的建立是刚刚好的。

如果我们只进行 2 次握手就建立连接,那么对于 Server 端来讲太容易建立起连接了,基本是有客户端过来,那么 Server 就要建立起连接了。这种情况就会导致连接成本太低,Server 端很容超负载。

至于在关闭连接时需要四次挥手,主要是因为 TCP 是全双工的,存在了两个方向的数据发送与接收,所以需要在两个方向都进行关闭流程。否则一方关闭了,另一方还在傻傻等待,只能等待异常超时结束了。

TIME_WAIT

在四次挥手中,有一个状态是 TIME_WAIT,它是主动关闭者在最后会进行的动作,是一个定时设置,在 2*MSL(MSL 表示一个包在网络环境中的生存时间,一般为 2 分钟, Linux 里为 30s)时间过后就会真正的 CLOSED。

之所以不立即关闭,主要为了让被动关闭方能有足够的时间接收到最后的 Ack 包,如果没有接收到,被动方就会重新发送 Fin 包,重新触发主动方发送最后的 Ack 包。这样的话,就能尽量保证被动关闭方尽快关闭连接了,毕竟主动关闭方需要承担起主要责任,所以会有 TIME_WAIT 的等待了。

另外一个原因也是怕当前连接立马释放,有一定概率会立马被使用到,就有可能产生包的混乱问题了。

TCP 重传

TCP 发送的包都需要接收方进行一个 Ack 包的响应,如果在一定时间内没有响应的话,那么发送方就会认为包未能正确到达,需要进行重传动作。这就是 TCP 的重传机制。

TCP 里的重传机制会有一个超时的判断,这个超时时间并不是很准确,或者说并不是很标准,毕竟不同的网络环境,包的到达情况都会是不一样的。

所以 TCP 会使用一个采样时间,先记录了正常情况下一个数据包从发送到响应确认这么一来一回的时间,即所谓的 RTT(Round Trip Time) 时间,根据这个时间进行一些公式计算,得到了超时时间的值:RTO(Retransmission TimeOut)

对于重传机制,还有另外一种触发机制。上面的情况属于发送方去探知发送情况,也有另一种情况是接收方能探知的。比如发送方发送了 1, 2, 3 的包,但实际上接收方只接收到 1 和 3,一直没能收到 2 这个包,那此时接收方就会连续响应三个 关于 2 的 ack 包。

当发送方收到这么一个连续的 3 个 ack 包后,就知道需要重传 2 了,此时就不需要等到 2 的超时未确认触发,可以提前的重传 2 这个包了。

TCP 滑动窗口

重传机制使得数据包能够可靠的传输,然而如果接收方数据处理能力有限,而发送方未能感应到这种情况,不停的发送数据包,则会增加接收方的压力,还会导致网络拥塞的发生。

为此,TCP 采用滑动窗口进行了流量的控制,所谓的滑动窗口即在发送方和接收方各自维护了一个窗口,在这个窗口里将会维护对应的数据包,以感知当前的数据处理情况。

在接收方这边的窗口称之为接收窗口,它具体表示当前所能接收的数据包大小,计算公式为:当前最大可接收缓冲区大小 - 当前已接收的大小,在连接建好的开始一般为 65535 字节。

在计算出可接收大小后,接收方就会将此值设置在 TCP 头部里的 Window 字段,然后响应回发送方,发送方也就知道了当前所能允许发送的数据包大小了。

在发送方这边的窗口称之为发送窗口,按正常逻辑来讲,发送窗口维护的是即将要发送的数据,即根据刚刚反馈回来的接收窗口大小计算出的发送数据。

但由于一个数据包的发送需要有一个 ACK 响应才算完整流程,所以对于这些“已发送未响应”的数据也应该纳入到发送窗口的管理,并且只有真的 ACK 响应回来,才能继续下个数据包的准备发送。

[图片上传失败...(image-9b834a-1635261169238)]](https://img-blog.csdnimg.cn/img_convert/cb724c4210ceb584d1a7fdde7a4d21c7.png#pic_center)

需要注意的是,如果发送方接收到的 Window 大小为 0,则表示当前的接收方已经无能力处理新的包了,此时发送方就不会再下发数据了,直到接收方发送一个窗口通告,才继续数据的发送。

但此时需要考虑一种情况,就是接收方由于网络问题没能将窗口通告送达发送方,那此时发送方就会一直干等着了.所以对于发送方来讲,会启动窗口探知动作,让接收方 ACK 它当前的接收窗口大小,如果超过 3 次的探知动作,则直接断开连接了。

TCP 的拥塞控制

在一个复杂的网络环境中,数据包的传输不仅仅只涉及到端到端的,还可能会出现很多网络问题,导致包的堆积,影响了整体的传输速度。所以,TCP 协议需要将网络的阻塞情况考虑进来,避免加剧。这就是 TCP 的拥塞控制。

为此,TCP 协议抽象出了拥塞窗口(cwnd)的概念,它会根据当前的网络拥塞程度进行动态的调整。由于加入了拥塞情况的考虑,前面我们提到过的发送窗口则不能仅仅只考虑接收窗口这个因素了,需要进行 min(拥塞窗口,接收窗口)的选择了。

由此可见,拥塞窗口的计算很重要,它将决定了数据包的发送大小。而关于拥塞窗口的计算,它将在几个场景里会涉及到,下面我们一一来分析。

MTU 和 MSS

在分析拥塞窗口的具体场景之前,我们先来看看拥塞窗口的基本单位:MSS。MSS 表示 网络传输数据的最大值,如果 MSS 加上包头大小,则表示网络传输最大报文:MTU 了。

在 Internet 这种互联网中,一般 MTU 定义为 576 字节,减去 TCP、IP 的包头 40 字节,则可以得到 MSS = 536 字节的值;而在以太网这种局域网里,一般 MTU 会大点:1500 字节,MSS 为 1460 字节。

慢启动

当连接建立完毕,开始传输数据时,TCP 协议规定不能一开始就发送大尺寸的数据包,这样避免了网络环境有问题时,新加入的连接加剧了拥塞状况。所以,对于新加入的连接而言,需要一点一点的增大数据量,这就是所谓的慢启动

其中,慢启动涉及的拥塞窗口计算过程如下:

  • 刚开始建立好连接时,拥塞窗口 = 1
  • 每当接收到一个 ACK 包时,拥塞窗口 = 拥塞窗口 + 1,此时呈线性增加。
  • 每当经过一个 RTT,拥塞窗口 = 拥塞窗口 * 2,此时呈指数上升趋势。

拥塞避免

从慢启动的算法来看,每经过一个 RTT 后,拥塞窗口 的增长速度将会变得很厉害,如果没有进行限制的话,那么很快就会占满带宽了。因此, TCP 协议使用了一个叫慢启动门限(ssthresh)的变量(一般取 65535 字节)。当 cwnd 超过该限制后,就会进入所谓的拥塞避免阶段了。

在拥塞避免阶段,拥塞窗口的计算过程如下:

  • 每接收到一个 ACK 包时,拥塞窗口 = 拥塞窗口 + 1/拥塞窗口
  • 每当经过一个 RTT,拥塞窗口 = 拥塞窗口 + 1

从上面的算法可以看出,进入拥塞避免阶段后,数据包的发送大小将呈线性增加了。通过这样的方式,使得 TCP 的传输在前期很快,然后再慢慢降下来,达到网络最佳值。

拥塞发生

前面都是在避免拥塞的发生去动态调整拥塞窗口(cwnd)的,然而按照线性增长的趋势,始终会导致网络拥塞的。当发送拥塞(一般只要丢包,需要重传数据包就认为发送了拥塞)时,TCP 协议该如何处理呢?此处也算是 TCP 协议比较复杂的地方,因为它在不断的改进,也衍生出了很多版本,下面我们来看看这些不同版本的区别和处理吧。

Tahoe 版本

Tahoe 版本是 TCP 的最早版本,当它发现需要进行重传动作,即触发了 RTO 超时或发送方收到三个重复 ACK 包时,此时会进行的动作为:

  • sshthresh = cwnd /2
  • cwnd 重置为 1
  • 重回慢启动阶段

Reno 版本

Reno 版本进行的动作为:

  • sshthresh = cwnd /2
  • cwnd = sshthresh + 3 * MSS (将 3 个重复 ACK 考虑进去)
  • 进入快速恢复阶段

其中,快速恢复阶段的步骤如下:

  • 当重传的包发出去后,收到了重传包的 ACK 后,cwnd = cwnd + 1
  • 当收到新的数据包的 ACK 后,此时快速恢复过程已结束,则 cwnd = sshthresh,然后重回拥塞避免阶段

NewReno 版本

NewReno 是对 Reno 的改进,主要是优化了快速恢复阶段,在 Reno 版本中,所考虑的都是一个包的丢失情况。然而,在实际情况中,一次数据窗口的发送,是有可能出现很多数据包丢失情况的。

这样的话,就会触发多次的 cwnd 和 ssthresh 减半动作,一旦 cwnd 降到小于 3 时,即发送窗口会出现小于 3 的情形,此时将再也触发不了 3 次快速重传动作了,只能依赖 RTO 超时,而一般 RTO 的值是比较大(太小会经常触发重传)的,此时整个传输速度将会大大降低。

所以 NewReno 会在收到所有数据包的确认后才结束快速恢复阶段,这样 cwnd 和 sshthresh 就不会轻易被降低了。

NewReno 主要是使用了一个 recover 变量,作为当前数据窗口中,可能丢包的最大序号。即如果有丢包情况产生,并且大于当前的 recover 值,则会更新该值。

当收到接收方的 ack 后,会进行 ack_seq 的判断,如果 ack_seq > recover,此时就可以结束快速恢复阶段了;如果 ack_seq < recover,则意味着多包丢失,还不能结束快速恢复阶段。通过这样的控制,来提高了整个的吞吐量。

Nagle 算法

下面我们来看看 TCP 里的其他经典算法:Nagle。Nagle 算法是一种通过减少小数据包的发送来提高 TCP 效率的机制。它会把多个小数据包合并到一个片段,并且等待满足一定条件后,再一起发送过去。具体的触发条件是:

  • 如果包长度达到 MSS,则允许发送
  • 如果包含 FIN,则允许发送
  • 如果设置了 TCP_NODELAY,则允许发送
  • 未设置 TCP_CORK 选项时,若所有发出去的小数据包(包长度小于 MSS)均被确认,则允许发送

当上述条件都未满足,但发生了超时(一般为 200ms),则立即发送。

对于 TCP 协议来讲,默认会启用 Nagle 算法,降低网络负载,减少网络拥塞,提高网络吞吐。

Delay Ack

Delay Ack 指延迟发送 Ack。在 TCP 的确认机制里,可以在通信过程中不对每一个 TCP 数据包进行单独的 ACK 包响应,而是在传输数据时,顺便把 ACK 信息随数据包一起发送,这样可以提高利用率。

如果在一定时间内(一般 40 ms)没有数据包要发送,那么就会单独的进行 ACK 包响应。这个过程就被称为 Delay Ack 了。

粘包与拆包

TCP 是面向字节流的传输,它会根据接收方的包处理能力以及当前网络的拥塞情况来一部分一部分的加载数据发送,再加上有 Nagle 这种整合小数据包的算法存在。所以对于接收方来讲,接收到的数据有可能是粘合在一起的,也有可能是被拆分开的,即所谓的粘包和拆包。

对于粘包和拆包现象,常用的解决方案有:

  • 在包的首部添加当前要传输的数据包的长度,让接收方根据长度去切割。
  • 将数据包封装为固定长度,不够的补 0,让接收方按固定长度解析。
  • 人为的给数据包添加边界,比如在数据包结尾添加特殊字符,当解析到特殊字符时,接收方就认为读取到了一个完整有意义的数据段了。

感兴趣的朋友可以搜一搜公众号「 阅新技术 」,关注更多的推送文章。
可以的话,就顺便点个赞、留个言、分享下,感谢各位支持!
阅新技术,阅读更多的新知识。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,132评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,802评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,566评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,858评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,867评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,695评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,064评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,705评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,915评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,677评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,796评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,432评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,041评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,992评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,223评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,185评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,535评论 2 343

推荐阅读更多精彩内容