网络协议到了 TCP 这一层,主要解决的是跨主机进程之间的通讯问题。我们可以回忆一下,链路层解决点对点的数据传输问题,IP 层解决全球组网的问题,通过IP可以找到全球范围的目标主机,但是最终如何处理这些收到的数据,还得落实到进程。于是,协议设计了端口,一个介于 1-65535 的整数,来关联处理进程。当主机收到 TCP 数据包后,便从协议头部里面寻找端口号,再根据本系统的端口注册信息,通知对应的进程来收数据。
TCP 号称可靠传输协议,得考虑进程之间通讯的各种异常情况,给出解决方案。首先最容易想到的就是丢失问题,丢失怎么解决?重传就是。
我们知道,建立连接后,进程可以自由地双向地传输数据,数据量大小是由进程的需求来确定的,但是由于底层网络的限制,一次传输的数据太大,效率比较低,得有个限制。一次传输最大的数据量称为 MSS,英文全称 Maximum Segment Size,默认值一般为 536 字节。
于是,我们把进程要发送的数据按照 MSS 分批发送,每发送一个数据包,要等对方进程的确认,如果确认收到则继续发送下一个数据包,如果超时没收到回复,则重传数据包。
等等,这样的效率是不是有点低,等待的时间是不是可以利用起来。于是,我们很容易想到,可以连续发送多个,同时等待多个回复,哪个超时重传哪个。
那么问题来了,应该连续发送多少个?发少了效率低,发多了,对方不一定收得过来,很容易出现大量的丢失,浪费网络带宽。于是,TCP 协议设计了一个窗口值,他表示,发送方可以连续发送数据到窗口指定的范围为止,再后面就先别发了,我收不过来。
然后问题又来了,窗口应该设计多大合适?这个很容易理解,跟接收方进程的处理能力相关,资源有限的时候应该小点,资源充足的时候可以大点。于是,发送方会等待接收方发过来的带有窗口值的数据包,然后根据窗口值来调整自身的发送策略。忽然发现,这个方法真是一箭双雕,窗口既提高了发送效率,同时又实现了流量控制。
下面,我们详细了解一下窗口的实现。
如图,一个方格代表一个字节的数据,从建立连接开始,累计发送的数据可以想象成一个很长的字节数组,每个字节在数组里面会有唯一的一个编号。当然,数据并不是一个一个字节发送的,TCP 协议头里面有一个“序号”区段,一共4个字节,他表示当前数据包的起始的那个字节的编号(因此,编号的范围就是0-2^32-1)。序号加上数据段的长度,就是本次数据包的编号范围了。有人会忽然想到,字节数超过了怎么办?由于数据范围已经足够大了,超过了会简单取模重新开始。
前面说到,发送方必须等到接收方返回的窗口值。实际上,除了窗口值,接收方还会返回一个“确认号”,在 TCP 头部,紧跟着“序号”字段,也是 4 个字节。他表示当前接收方已经确认接受的连续的最大的那个字节的编号,简单说,这个确认号以前的字节,我都确认接受了,后面的有些有,有些还没有。于是,以确认号开始,到确认号加上窗口值这个编号为止,就是窗口范围了,这区间的数据可以连续发送,酌情重发。
我们不禁要问,如果窗口范围的字节都确认收到了咋办?发送方会等接收方的下一个窗口值再行动。
那如果一直没等待呢?发送方会开启一个计时器,超时了之后会主动发一个只有1个字节数据的包去询问。
下图向我们呈现了流量控制的具体过程。