浅谈TCP的窗口字段
该掸掸这里的灰尘了,写一篇关于TCP的文章吧。今天的主题是TCP的滑动窗口。在开始这个话题之前,我想先提几个关于TCP协议的常见误区。
误区1:TCP协议三次握手过程中后两个包都是[ACK]包。
解释:这种说法并不错,只是不严谨。首先,第一个包是[SYN],SYN位在TCP报头flag字段中,见上图TCP报头结构。第二个包更确切地说应该叫[SYN,ACK]包,因为除了ACK位被置位以外,SYN位也被置位了。第三个包才是单纯的ACK包,因为只有ACK位被置位。所以三次握手的过程为:
客户端----[SYN]------------------>>>服务器
客户端<<<-------------[SYN,ACK]---服务器
客户端----[ACK]------------------>>>服务器
有一种非常经典的***叫SYN Flood***,是DDOS***的一种,方法是客户端发送多个SYN包请求但是不回复第三个ACK包以占用服务器有限的资源。
误区2:TCP数据传输过程中,序列号增长的单元是包的个数。
解释:这是初学者最常犯的一个错误,原因是绝大多数老师为了方便学生理解,刚开始举例子时序列号都是+1地往上增。其实不然。序列号增长的单元是包应用层数据(也叫Payload)的字节数。
举个栗子:
假如某一个数据包序列号:1000,Payload长度500。那么下一个包的序列号就是1500。
误区3:对于TCP连接的双方,序列号都会增长。
解释:对于多数应用(HTTP,Telnet),双方都存在数据的发送,那么双方的序列号都会增长,而对于FTP这样的应用,在数据通道中,只是某一方在传输数据(上传或下载),那么另一方只是在扮演简单确认的角色,在这种情况下,另一方的序列号并不会增长。
说了这么多,现在开始进入正题,来聊聊TCP的窗口机制,这是理解TCP协议的一个关键点。
1. 大家都知道TCP和UDP的区别是,TCP是基于连接的(三次握手),而且TCP数据的传输是可靠的,所谓可靠是依托序列号及确认号机制让TCP的传输过程中即使出现丢包也会重传。
2. 但是TCP的确认机制也让TCP连接双方的数据传输速度变慢,也就是说,一方发送数据需要等待对方的确认才继续发送后续数据。这就体现的窗口机制的作用,所谓窗口,也就是充分利用双方的带宽及缓冲区(Buffer)。举个栗子,发送方不必等待对方的确认,可以连续发送多个数据包给对方,而对方可以暂时把这些数据存放在缓冲区,并给对方一个确认。这样,可以大大增加数据传输的速度。
3. 这样做的问题是一旦当接收方的缓冲区填满或即将填满时,就会产生不堪重负的情况,那么这就需要TCP窗口的实时更新机制,举个栗子,接收方窗口大小设置的是50000,也就是说发送方可以不必等待确认而一次性发送50000字节的数据,一旦接收方意识到不堪重负的情况,可以发送窗口更新通知告诉发送方,现在的窗口大小是30000,请减少一次性发送数据的字节数。某些极端情况,接收方的Buffer完全填满,这时,会发送ZeroWindowSize通知,让发送方暂时停止数据传输,并等待下一个确认通知。
下面我们来看看抓包中是如何体现窗口的特征的:
上图是抓包工具抓到的一个ACK包的头部信息,可以看出其windowsize的值是最大值,因为16bit的字段最大取值就是65535。红框中下面那一行可能会让大家疑惑。为什么会有一个Calculated window size呢?下面我们就来看一个TCP的选项:TCP Window Size Scaling。
1. 既然是选项,那么毫无疑问需要体现在上图TCP报头的选项(Options)字段。
2. 接下来我们再看一张抓包的截图:
这张图中我们可以看到,windows size的值是114,但是Calculated Windows Size的值却是14592,这是怎么回事呢?
3. 在TCP三次握手过程中,可以通过SYN包开启TCP选项Window Size Scaling,设计出这个选项是因为如今的带宽已经大规模提升,千兆到桌面也是一件常事儿,因此,65535长度的窗口大小已经显得有些小了,为了突破这个限制,便有了Window Size Scaling选项,看下图SYN包截图:
可以看到这个字段的值为7,也就是要将Window Size的值左移七位,即乘以128,因此,上图中我们看到window size值是114,但是真正选用的值却是14592(114*128)。