这是计算机网络相关的第四篇文章。首先要明确:信道本身不可靠(丢包、重复包、出错、乱序),不安全。所以引出了七层或五层模型来保证。因此,任何一个东西的提出都是为了解决某个问题的。学习计算机,从他的历史出发,理解为什么会有不断低迭代,因为是为了解决某个痛点问题。比如HTTP的发展,为什么在HTTP1.0基础上还要提出HTTP1.1,为什么还要提出HTTP2.0,我们学习了他的发展历史之后就会明白了。同样,下面再说一说为什么要有TCP协议,TCP到底解决了什么问题。

一、回顾

首先简单回顾一下。

1.1 物理层

物理层是相当于物理连接,将0101以电信号的形式传输出去。

1.2 数据链路层

数据链路层,有一个叫做以太网协议,规定了电子信号是如何组成数据包的,这个协议的头里面,包含了自身的网卡信息,还有目的地的网卡信息(一般我们可以知道对方的IP,IP可以通过DNS解析到,然后根据ARP协议将IP转换为MAC地址)。那么,如果在同一个局域网内,我们就可以通过广播的方式找到对应MAC地址的主机。—即以太网协议解决了子网内部的点对点通信。

1.3 网络层

但是呢,以太网协议不能解决多个局域网通信,每个局域网之间不是互通的,那么以太网这种广播的方式不可用,就算可用,网络那么大,通过广播进行找,是一个可怕的场景。那么,IP协议可以连接多个局域网,简单来说,IP 协议定义了一套自己的地址规则,称为 IP 地址。物理器件,比如说路由器,就是基于IP协议,里面保存一套地址指路牌,想去哪个局域网,可以通过这个牌子来找,然后逐步路由到目标局域网,最后就可以找到那台主机了。IP层就是对应了网络层。

1.4 传输层

那么,此时解决了多个局域网路由问题,也解决了局域网内寻址问题,即我这台主机已经可以找到那台主机了,下面还有什么事情需要做呢?显然,找到主机还不行啊,比如我用微信发一条消息,我发到你主机了,但是你主机上的微信不知道这条消息发给他了,这里说的就是端口,信息要发到这个端口上,监听这个端口的程序才会收到消息。

1.5 应用层

OK,最上层的应用层,就是最贴近用户的,他的一系列协议只是为了让两台主机会互相都理解而已。

二、问题和解决

2.1 存在的问题

在明白了计算机网络为什么要这几层模型之后,我们再回到一开始,如何保证安全、可靠、完整地传输信息呢?

很显然,上面提到的,只是保证信息能找到对方主机和端口,但是这个信息中途被拦截了、甚至被篡改了、信息延迟了(几分钟或者几个小时,或者几个世纪)、网络不通或者挂了,信息自己可不会告诉你他挂了或者要迟到一会,如果没有一个协议来保障可靠性,那么我这条消息发出去,能不能到、能不能及时到、能不能完整到、能不能不被篡改到等这些问题将会造成灾难,网络传输也就没有了意义。

计算机的前辈们,为我们提供了一系列的措施来尽可能保证信息能正确送达。

2.2 数据校验

首先在数据链路层,可以通过各种校验,比如奇偶校验等手段来判断数据包传的是否正确。

2.3 数据可靠性

好了,解决了数据是否正确之后,但是还不能保证线路是可靠的,加入某个包没发出去或者发错了,应该有一个出错重传机制,保证信息传输的可靠性。这就引出了TCP协议。

TCP(Transmission Control Protocol 传输控制协议)是一种面向连接的、可靠的、基于字节流的传输层通信协议。

下面来看看TCP是如何解决丢包、重复包、出错、乱序等不可靠的一些问题的。

三、滑动窗口协议的提出

这就引出了TCP中最终的一个东西:滑动窗口协议。

3.1 朴素的方法来确保可靠性

先从简单的角度出发,自己想一下,如何保证不丢包、不乱序。

image

按照顺序,发送一个确认一个,显然,吞吐量非常低。那么,一次性发几个包,然后一起确认呢?

3.2 改进方案

image

那么就引出第二个问题,我一次性发几个包合适呢?就这引出了滑动窗口。

四、数据包编号和重传机制

4.1 数据包编号

在说明滑动窗口原理之前,必须要说一下TCP数据包的编号SEQ。

我们知道,由于以太网数据包大小限制,所以每个包承载的数据是有限的,如果发一个很大的包,必然是要拆分的。

发送的时候,TCP 协议为每个包编号(sequence number,简称 SEQ),以便接收的一方按照顺序还原。万一发生丢包,也可以知道丢失的是哪一个包。

第一个包的编号是一个随机数。为了便于理解,这里就把它称为1号包。假定这个包的负载长度是100字节,那么可以推算出下一个包的编号应该是101。这就是说,每个数据包都可以得到两个编号:自身的编号,以及下一个包的编号。接收方由此知道,应该按照什么顺序将它们还原成原始文件。

4.2 数据重传机制

TCP协议就是根据这些编号来重新还原文件的。并且接收端保证顺序性返回ACK确认,比如有两个包发过去,为1号和2号,2号接收成功,但是发现1号包还没接收到,所以2号的ACK是不会发回去的,这个时候,如果在重传时间内收到1号了,那么就把这两个包的ACK都返回回去,如果超时了,就重传1号包。知道1号包接收成功,后续的才会返回ACK。

具体是如何做到的呢?

前面说过,每一个数据包都带有下一个数据包的编号。如果下一个数据包没有收到,那么 ACK 的编号就不会发生变化。

举例来说,现在收到了4号包,但是没有收到5号包。ACK 就会记录,期待收到5号包。过了一段时间,5号包收到了,那么下一轮 ACK 会更新编号。如果5号包还是没收到,但是收到了6号包或7号包,那么 ACK 里面的编号不会变化,总是显示5号包。这会导致大量重复内容的 ACK。

如果发送方发现收到三个连续的重复 ACK,或者超时了还没有收到任何 ACK,就会确认丢包,即5号包遗失了,从而再次发送这个包。通过这种机制,TCP 保证了不会有数据包丢失。

image

(图片说明:Host B 没有收到100号数据包,会连续发出相同的 ACK,触发 Host A 重发100号数据包。)

五、慢启动

下面再来说说慢启动。

服务器发送数据包,当然越快越好,最好一次性全发出去。但是,发得太快,就有可能丢包。带宽小、路由器过热、缓存溢出等许多因素都会导致丢包。线路不好的话,发得越快,丢得越多。

最理想的状态是,在线路允许的情况下,达到最高速率。但是我们怎么知道,对方线路的理想速率是多少呢?答案就是慢慢试。

TCP 协议为了做到效率与可靠性的统一,设计了一个慢启动(slow start)机制。开始的时候,发送得较慢,然后根据丢包的情况,调整速率:如果不丢包,就加快发送速度;如果丢包,就降低发送速度。

Linux 内核里面设定了(常量TCP_INIT_CWND),刚开始通信的时候,发送方一次性发送10个数据包,即"发送窗口"的大小为10。然后停下来,等待接收方的确认,再继续发送。

默认情况下,接收方每收到两个 TCP 数据包,就要发送一个确认消息。"确认"的英语是 acknowledgement,所以这个确认消息就简称 ACK。

ACK 携带两个信息:

  • 期待要收到下一个数据包的编号
  • 接收方的接收窗口的剩余容量

发送方有了这两个信息,再加上自己已经发出的数据包的最新编号,就会推测出接收方大概的接收速度,从而降低或增加发送速率。这被称为"发送窗口",这个窗口的大小是可变的。

我们可以知道,发送发和接收方都维护了一个缓冲区,可以理解为窗口。根据接收速度可以调整发送速度,逐渐达到这条线路最高的传输速率。

ok,下面就可以研究一下滑动窗口了。

六、滑动窗口原理

正常情况下:

image

(如图,123表示已经正常发送并且收到了ACK确认。4567属于已发送但是还没有收到ACK。8910表示待发送。这个窗口当前长度为7.正常情况下,4号包收到ACK,那么窗口就会右移一格。)

但是往往会出现一些问题,比如5号包迟迟收不到ACK,在接收端可能是没有收到5号包,但是可能会收到6号包甚至是7、8号包,那么此时只能等待5号包,如果5号包顺利到达了,那么就把5678号包的ACK都发给发送端,那么发送端滑动窗口向右右移四格。如果迟迟收不到5号包,只能重传。

image

以上就是关于TCP中比较重要的出错重传、编号、慢启动以及滑动窗口。这些保证了数据传输的可靠性。

整理自:http://www.ruanyifeng.com/blog/2017/06/tcp-protocol.html