传输层协议(8):滑动窗口(2)

5.3.1.3 PUSH

在上一小节中,我们讲述了 TCP 为了提升网络传输效率以及避免一些不必要的网络拥塞,而不惜付出些许时延的代价,以将多个小包攒成1个大包进行发送。

本小节讲述的“PUSH”,其目标则恰恰相反,它是为了减少“时延”。PUSH 本身是 TCP 报文头中的一个 Flag,如图5-90所示:

 

 图5-90 TCP 报文结构

 

图5-90中的“P”标记位,代表的就是 PUSH(有时候简写为 PSH),占用1个 bit。当 PSH = 1 时,它从一定意义上来讲,就可以减少“时延”。

为什么说是“从一定意义上来讲”,为什么“时延”两个字又打了引号呢?这首先要从 TCP 接收端与它的上层之间的数据提交模型说起,如图5-91所示:

 

 

图5-91 TCP 接收端与上层之间的内存模型

 

图5-91中,TCP 接收方收到数据以后,首先是将数据存储在自己的接收缓存中,然后再提交给上层(应用层)相关缓存中。那么问题来了,TCP 接收方何时将这些数据提交给应用层呢?这分为两种提交模型,如图5-92、5-93所示:

 

 图5-92 满泻提交模型

 

 

图5-93 直流提交模型

 

图5-92中,T0、T1 时刻,TCP接收方虽然接收了一些数据,但是其缓存并没有满,所以它暂时还不把所接收到的数据提交给应用层。待到 T3 时刻,TCP 接收方又接收了一些数据,此时其缓存已满,它才将缓存中所有的数据一次性提交给应用层。正如张学友的一首歌所唱的那样:

便爱你多些再多些至满泻

所以,我们把TCP 的这种提交模型称为“满泻提交模型”:接受缓存满了以后,才提交给应用层。

  

古老的磁带里藏着谁的青春

 

图5-93则没有图5-92那么复杂,TCP 接收方在任意时刻(Ti),只要接收到数据,就直接从接收缓存提交给应用层。我们把这种提交模型称为“直流提交模型”:直接提交过去。

之所以会有这两种模型,是因为 TCP 认为直流提交模型的效率并不高,所以期望自己先缓存一下,待缓存满了以后再一次新提交,这样提交的效率会高些。

客观地说,所谓的满泻提交模型的效率会高些,似乎并没有多大意义,毕竟有的 TCP 实现(比如 BSD(Berkeley Software Distribution)),就是采用直流提交模型,这么多年来也没觉得有什么效率问题。

而且,让人沮丧的是,满泻提交模型对于效率提升没有什么显著作用,但是它的坏处——时延——却是非常显著的。想象一下图5-92中,如果从 T2 时刻到 T3 时刻,中间如果经历了很长时间(比如几秒、甚至几小时),那么 TCP 几乎就是不可用了。

为此,TCP 提出了“PUSH”方案来解决这个问题。我们再回到前面所提的问题:为什么“时延”要打引号,这是因为这个时延并不是一般意义上的网络时延,而是 TCP 接收方与应用层之间的时延;为什么说是“从一定意义上来讲”,是因为这个时延原本可以避免,比如采用直流提交模式。

唉......说什么好呢?反正笔者感觉“满泻提交模式”就是一个车祸现场。好吧,我们就从车祸现场说起,看看 PUSH 是怎么拯救这个车祸现场的,如图5-94所示:

 

 图5-86 PUSH:拯救车祸现场

 

图5-94表达的是满泻提交模式与 PUSH 相遇的故事。T0 时刻的报文,其 PSH = 0(图5-92、5-93中的 PSH 都是0),所以此时数据(data0,3个字节)并不会被 TCP提交给应用层。T1 时刻的报文,PSH = 1,此时 TCP 会将已接收未提交的数据全部提交给应用层缓存:data1(4个字节) + data0(3个字节)。

也就是说,对于接收方而言,TCP 只要看到 PSH = 1,无论其接收缓存是否已满,它都会将缓存中所接收的所有数据(包括本次接收和历史接收),全部一次性提交给应用层。

由此看来,PSH 标签(等于1),对于 TCP 接收方来说,其含义就是:立刻马上全部提交(PUSH)接收缓存中的数据。也正是源于此,PSH 标签可以有效地减少或者避免 TCP 接收方缓存提价的时延。

PSH 标签作用如此明显,那么它是怎么来的呢?当然,是由发送方打上的——发送方决定 PSH 等于0或者等于1。

这里的发送方指的是两个角色:TCP 发送方、用户(调用 TCP 接口发送数据,从程序的角度,是应用层。当然,应用层的代码也是人编写的,所以把用户理解为“人”,也无不可)。

我们先说用户这个角色。用户设置 PSH = 0 或者 PSH = 1,是一个主动行为。也就是说,用户根据不同的场景、不同的应用特点,决定 PSH 的值是多少。这个我们就不展开说了,毕竟这里没有绝对的规则——当然,一般来说,如果要追求低时延,报文中最好还是打上 PSH 标签。

PSH = 1,不仅可以是 TCP 接收方立刻提交数据,也可以使 TCP 发送方立刻发送数据。在“5.3.1.2 窗口大小与发送效率”中,我们讲过,为了提高发送效率,TCP 发送方可能会延迟发送(把多个小包攒成1个大包)。但是这样做的缺点也是明显的:在发送方造成了时延。PSH 标签正是为了解决此问题,如图5-95所示:

 

图5-87 PUSH:立刻发送数据

 

图5-95中,T0 时刻,用户(应用层)调用 TCP 的 SEND 接口,发送了 data0(3个字节),此时 PSH = 0,TCP 发送方并不会立刻发送 data0(假设 TCP 采用了 Nagle 算法)。T1 时刻,用户又调用 SEND 接口,发送了 data1(4个字节)。此时,data1 + data0,一共也才7个字节,仍然是属于小包,但是由于用户设置了 PSH 标签(PSH = 1),TCP 会立刻将其发送缓存中的数据发送出去(当然发送本身,仍然要受发送窗口、MSS 等约束)。

所以,PSH 标签,对于 TCP 发送方而言,其目的就是:立刻马上全部发送发送缓存中的数据。也正是源于此,PSH 标签也可以有效地减少或者避免 TCP 发送方缓存发送的时延。

前面我们一直揶揄 TCP 的“满泻提交模式”,说它是车祸现场。现在来看,造成时延的,不仅仅是 TCP 接收方的不太必要数据延迟提交(满泻提交模式),也有 TCP 发送方的延迟发送。所以如果说 PUSH 仅仅是为了拯救 TCP 发送方的“车祸”,那是不公平的,^_^

 

 

这个锅一人一半

 

PSH 标签,不仅用户可以主动打上,TCP 发送方(TCP 协议栈)也会自动打上,即:就算用户没有打上 PSH 标签,TCP 发送方也会根据具体情形,自动打上 PSH 标签。一个典型的场景就是“发送缓存空了(each write empties the sender buffer)”,如图5-96所示:

 

图5-88 发送缓存为空

 

图5-96中,TCP 发送方将一批 data 发送出去以后,会发现自己发送缓冲区空了——未发送已允许,没有数据;未发送未允许,没有数据——也就说没有数据可以发送了。于是 TCP 在发送这批数据时,会自动打上 PSH 标签(PSH = 1)。

自动打上 PSH 标签,并不是为了发送,因为无论打不打标签,TCP 都会发送。打上标签是为了 TCP 接收方。前文我们说过,TCP 接收方可能会采用“满泻提交模式”,当这批数据发送过去,接收方收到这批数据以后,接收方的缓存很有可能是未满的,那么 TCP 接收方就会在那里等待——等待下一批数据的到来......

但是,TCP 接收方又能等到什么呢?对于发送方来说,TCP 发现自己的发送缓存已经空了,它自己都不知道下一批数据何时到来,它能让对方等待吗?

 

国足如果赢得世界冠军,我就娶你

 

最感人的誓言不是等到海枯石烂,而是等到中国足球队赢得世界冠军。TCP 的爱情没有誓言,而是放手。既然自己都不知道下一批数据在哪里,那就赶紧通知对方收到这批数据以后,马上提交个应用层,不必再等待了。

莫等闲,白了少年头,空悲切

如果我们把“莫等闲”的意思篡改一下,整句话的意思就可以形象地解释“如果发送缓存为空,TCP 发送方会自动在发送报文里打上 PSH 标签(PSH = 1)”:我已经很闲了(发送缓存为空),你莫要等待了(收到数据赶紧提交给应用层吧),否则恐怕会白了少年头,空悲切!

除了“发送缓存空”这个场景,还有一个场景,TCP 发送方也会自动打上 PSH 标签,那就是:用户调用 CLOSE 接口,以关闭一个 TCP 连接。这个时候 TCP 会发送1个 FIN 报文,并且为这个报文打上 PSH 标签(PSH = 1)。

这很好理解,既然都决定要关闭连接了,那就更有必要通知接收方收到报文后,马上提交给应用层,因为自己再也不会发送数据了——这下彻底空了。

世界那么空

我和谁相拥

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值