网络协议 8 - TCP协议(上):性恶就要套路深

系列文章:html

  1. 网络协议 1 - 概述
  2. 网络协议 2 - IP 是怎么来,又是怎么没的?
  3. 网络协议 3 - 从物理层到 MAC 层
  4. 网络协议 4 - 交换机与 VLAN:办公室太复杂,我要回学校
  5. 网络协议 5 - ICMP 与 ping:投石问路的侦察兵
  6. 网络协议 6 - 路由协议:敢问路在何方?
  7. 网络协议 7 - UDP 协议:性善碰到城会玩

    上次说了“性本善”的 UDP 协议,这哥们秉承“网之初,性本善,不丢包,不乱序”的原则,徜徉在网络世界中。算法

    与之相对应的,TCP 就像是老大哥同样,了解了社会的残酷,变得复杂而成熟,秉承“性恶论”。它认为网络环境是恶劣的,丢包、乱序、重传、拥塞都是常有的事儿,一言不合可能就会丢包,送达不了,因此从算法层面来保证可靠性。网络

TCP 包头格式

    老规矩,我们先来看看 TCP 头的格式。tcp

    从上面这个图能够看出,它比 UDP 要复杂的多。而复杂的地方,也正是它为了解决 UDP 存在的问题所必需的字段。设计

    首先,源端口号和目标端口号是二者都有,不可缺乏的字段。htm

    接下来是包的序号给包编号就是为了解决乱序的问题。老大哥作事,稳重为主,一件件来,面临再复杂的状况,也临危不乱。blog

    除了发送端须要给包编号外,接收方也会回复确认序号。作事靠谱,答应了就要作到,暂时作不到也要给个回复。路由

    这里要注意的是,TCP 是个老大哥没错,但不能说他必定会保证传输准确无误的完成。从 IP 层面来说,若是网络的确那么差,是没有任何可靠性保证的,即便 TCP 老大哥再稳,他也管不了 IP 层丢包,他只能尽量的保证在他的层面上的可靠性。get

而后是一些状态位。有如下常见状态位:同步

  • SYN(Synchronize Sequence Numbers,同步序列编号):发起一个链接
  • ACK(Acknowledgement,确认字符):回复
  • RST(Connection reset):从新链接
  • FIN:结束链接

    从这些状态位就能够看出,TCP 基于“性恶论”,警觉性就很高,不像 UDP 和小朋友似的,随便一个不认识的小朋友都能玩到一块儿,他与别人的信任要通过屡次交互才能创建。

    还有一个窗口大小。这个是 TCP 用来进行流量控制的。通讯双方各声明一个窗口,标识本身当前的处理能力,让发送端别发送的太快,要否则撑死接收端。也不能发送的太慢,要否则就饿死接收端了。

根据上述对 TCP 头的分析,咱们知道对于 TCP 协议要重点关注如下几个问题:

  • 顺序问题,稳重不乱;
  • 丢包问题,承诺靠谱;
  • 链接伟豪,善始善终;
  • 流量控制,把握分寸;
  • 拥塞控制,知进知退。

TCP 的三次握手

    了解完 TCP 头,咱们就来看下 TCP 创建链接的过程,这就是著名的“三次握手”。

三次握手,过程是这样子的:

  • A:你好,我是 A(SYN)。
  • B:你好 A,我是 B(SYN,ACK)。
  • A:你好 B(ACK 的 ACK)。

    着重记忆上述过程,后续不少分析都是基于这个过程来的。

    记得刚接触三次握手的时候,就一直很纳闷,为啥必定要三次?两次不行吗?四次不行吗?而后不少人就解释,若是是两次,就怎样怎样,四次,又怎样怎样?但这其实都是从结果推缘由,没有说明本质。

    咱们应该知道,握手是为了创建稳定的链接,这个是最终目的。而要达到这个目的,就要通讯双方的交互造成一个确认的闭环

    拿上述 A、B 通讯的例子来看,A 给 B 发信息,B 要告诉 A 他收到信息了。这时候,算是一个确认闭环吗?明显不是,由于 B 没有收到来自 A 的确认信息。

    因此,要达到咱们上述的目标,还要 A 给 B 一个确认信息,这样就造成了一个确认闭环

    A 给 B 的确认信息发出后,遇到网络很差的状况,也会出现丢包的状况。按理来讲,还应该有个回应,可是,咱们发现,好像这样下去就没玩没了啦。

    因此,咱们说,只要通讯双方造成一个确认闭环后,就认为链接已创建。一旦链接创建,A 会立刻发送数据,而 A 发送数据,后续的不少问题都获得了解决。

    例如 A 发给 B 的确认消息丢了,当 A 后续发送的数据到达的时候,B 能够认为这个链接已经创建。若是 B 直接挂了,A 发送的数据就会报错,说 B 不可达,这样,A 也知道 B 出事情了。

    三次握手除了通讯双方创建链接外,主要仍是为了沟通 TCP 包的序号问题

    A 要告诉 B,我发起的包的序号起始是从哪一个号开始的,B 也要告诉 A,B 发起的包的序号的起始号。

    TCP 包的序号是会随时间变化的,能够当作一个 32 位的计数器,每 4ms 加一。计算一下,这样到出现重复号,须要 4 个多小时。可是,4 个小时后,还没到达目的地的包早就死翘翘了。这是由于 IP 包头里的 TTL(生存时间)。

    为何序号不能从 1 开始呢?由于这样会很容易出现冲突。

    例如,A 连上 B 以后,发送了 一、二、3 三个包,可是发送 3 的时候,中间丢了,或者绕路了,因而从新发送,后来 A 掉线了,从新连上 B 后,序号又从 1 开始,而后发送 2,可是压根没想发送 3,而若是上次绕路的那个 3 恰好又回来了,发给了 B ,B 天然就认为,这就是下一包,因而发生了错误。

    就这样,双方历经千辛万苦,终于创建了链接。前面也说过,为了维护这个链接,双方都要维护一个状态机,在链接创建的过程当中,双方的状态变化时序图就像下面这样:

总体过程是:

  1. 客户端和服务端都处于 CLOSED 状态;
  2. 服务端主动监听某个端口,处于 LISTEN 状态;
  3. 客户端主动发起链接 SYN,处于 SYN-SENT 状态。
  4. 服务端收到客户端发起的链接,返回 SYN,而且 ACK 客户端的 SYN,处于 SYN-RCVD 状态;
  5. 客户端收到服务端发送的 SYN 和 ACK 以后,发送 ACK 的 ACK,处于 ESTABLISHED 状态;
  6. 服务端收到 ACK 的 ACK 以后,处于 ESTABLISHED 状态。

TCP 的四次挥手

    说完了链接,接下来就来了解下 TCP 的“再见模式”。这也常被称为四次挥手

还拿 A 和 B 举例,挥手过程:

  1. A:B 啊,我不想和你玩了。
  2. B:哦,你不想玩了啊,我知道了。这个时候,还只是 A 不想玩了,就是说 A 不会再发送数据,可是 B 此时还没作完本身的事情,仍是能够发送数据的,因此此时的 B 处于半关闭状态
  3. B:A啊,好吧,我也不想和你玩了,拜拜。
  4. A:好的,拜拜。

    这样这个链接就关闭了。看起来过程很顺利,是的,这是通讯双方“和平分手”的场面。

    A 开始说“不玩了”,B 说“知道了”,这个回合,是没什么问题的,由于在此以前,双方还处于合做的状态。

    若是 A 说“不玩了”,没有收到回复,那么 A 会从新发送“不玩了”。可是这个回合结束以后,就极可能出现异常状况了,由于有一方率先撕破脸。这种撕破脸有两种状况。

    一种状况是,A 说完“不玩了”以后,A 直接跑路,这是会有问题的,由于 B 尚未发起结束,而若是 A 直接跑路,B 就算发起结束,也得不到回答,B 就就不知道该怎么办了。

    另外一种状况是,A 说完“不玩了”,B 直接跑路。这样也是有问题的,由于 A 不知道 B 是还有事情要处理,仍是过一会发送结束。

    为了解决这些问题,TCP 专门设计了几个状态来处理这些问题。接下来,咱们就来看看断开链接时的状态时序图

总体过程是:

  1. A 说“不玩了”,就进入 FIN_WAIT_1 状态;
  2. B 收到 “A 不玩”的消息后,回复“知道了”,就进入 CLOSE_WAIT 状态;
  3. A 收到“B 说知道了”,进入 FIN_WAIT_2 状态。这时候,若是 B 直接跑路,则 A 将永远在这个状态。TCP 协议里面并无对这个状态的处理,可是 Linux 有,能够调整 tcp_fin_timeout 这个参数,设置一个超时时间;
  4. B 没有跑路,发送了“B 也不玩了”的消息,处于 LAST_ACK 状态;
  5. A 收到“B 说不玩了”的消息,回复“A 知道 B 也不玩了”的消息后,从 FINE_WAIT_2 状态结束。

    最后一个步骤里,若是 A 直接跑路了,也会出现问题。由于 A 的最后一个回复,B 若是没有收到的话就会重复第 4 步,可是由于 A 已经跑路了,因此 B 会一直重复第 4 步。

    所以,TCP 协议要求 A 最后要等待一段时间,这个等待时间是 TIME_WAIT,这个时间要足够长,长到若是 B 没收到 A 的回复,B 重发给 A,A 的回复要有足够时间到达 B。

    A 直接跑路还有一个问题是,A 的端口就空出来了,可是 B 不知道,B 原来发过的不少包可能还在路上,若是 A 的端口被新的应用占用了,这个新的应用会受到上个链接中 B 发过来的包,虽然序列号是从新生成的,可是这里会有一个双保险,防止产生混乱。所以也须要 A 等待足够长的时间,等到 B 发送的全部未到的包都“死翘翘”,再空出端口。

    这个等待的时间设为 2MSL,MSL 是 Maximum Segment Lifetime,即报文最大生存时间。它是任何报文再网络上存在的最长时间,超过这个时间的报文就会被丢弃。

    由于 TCP 报文基于 IP 协议,而 IP 头中有一个 TTL 域,是 IP 数据报能够通过的最大路有数,每通过一个处理他的路由器,此值就减 1,当此值为 0 时,数据报就被丢弃,同时发送 ICMP 报文通知源主机。协议规定 MSL 为 2 分钟,实际应用中经常使用的是 30 秒、1分钟和 2 分钟等。

    还有一种异常状况,B 超过了 2MS 的时间,依然没有收到它发的 FIN 的 ACK。按照 TCP 的原理,B 固然还会重发 FIN,这个时候 A 再收到这个包以后,就表示,我已经等你这么久,算是仁至义尽了,再来的数据包我就不认了,因而直接发送 RST,这样 B 就知道 A 跑路了。

TCP 状态机

    将链接创建和链接断开的两个时序状态图综合起来,就是著名的 TCP 状态机。咱们能够将这个状态机和时序状态机对照看,就会更加明了。

图中加黑加粗部分,是上面说到的主要流程,相关说明:

  • 阿拉伯数字序号:创建链接顺序;
  • 大写中文数字序号:断开链接顺序;
  • 加粗实线:客户端 A 的状态变迁;
  • 加粗虚线:服务端 B 的状态变迁;

总结

  • TCP 包头很复杂,主要关注 5 个问题。顺序问题、丢包问题、链接维护、流量控制、拥塞控制;
  • 创建链接三次握手,断开链接四次挥手,状态图要牢记。

参考:

  1. 百度百科-TCP 词条;
  2. 刘超-趣谈网络协议系列课;
相关文章
相关标签/搜索