电脑技术学习

低速串行链路上的TCP/IP头部压缩(1)

dn001

1简介
随着功能日益强大的计算机进入人们家庭,扩展这些计算机的功能使之与Internet
连接成为日益迫切的要求。不幸的是,这个扩展在链路层帧(linklevelframing)、地址
分配(addressassignment)、路由选择、认证以及性能等方面暴露出很多很复杂的问题。
在写本文档时所有这些领域的工作还在进行。本文档描述一种方法,这种方法已经被用
来提高低速(300-900bps)串行链路上的TCP/IP的性能。
这里推荐的压缩方法与Thinwire-II协议(参考文献[5])描述的思想是相似的。但是
本协议压缩的效率更高(压缩后TCP/IP头部为3个字节,而Thinwire-II为13个字节),并且
实现起来既高效又简单(Unix实现需要250行C代码,在20MHzMC68020中压缩或者解
压一个数据包平均需要90μs(_170指令集)。
该压缩专门针对TCP/IP数据包(注1),作者研究了UDP/IP数据包的压缩但发现这
种情况极少出现,并且没有足够的datagram-to-datagram一致性来进行很好的压缩(例如,
名字服务器查询)或者高层协议头部沉没了UDP/IP头部的开销(例如,Sun'sRPC/NFS)。
作者还研究了分开压缩数据报的IP和TCP部分,但因为压缩后头部平均大小比原来增加
50%,并且压缩和解压缩的代码加倍,因而被否决了。
2问题
人们可能期望通过串行IP链路从家中访问从“终端”击键(type)类型连接(如telnet,
rlogin,xterm)到批量数据传输(例如FTP,smtp,nntp)的Internet服务。头部压缩的动机就
是出于对良好的交互响应的需要求。也就是说协议的链路效率(lineefficiency)为数据
报中header占header+data的百分比。假如高效的批量数据传输是我们的目标,,通过把数
据报的尺寸扩大到足够大总是可以使链路效率接近100%。
对人的因素(Human-factor)的研究(参考文献[15])结果表明交互操作在低层反
馈(feed-back)(字符回显,characterecho)花费超过100-200ms时被认为是“差的”。协议
头部从以下几方面与这个极端交互:
(1)假如链路速度太慢,也许不可能把头部和数据都放在一个200ms的窗口中:每敲
击一个键产生一个字符就要导致发送一个41字节的TCP/IP数据包和接收一个41字节的
反馈(echo)。链路速度至少达到4000bps以便在200ms内能够处理这82个字节的数据
包。







注1:与TCP的联系(tie)可能比明显的要强(deeper)。除了压缩“知道”TCP和IP的头部,TCP
的某些特征已经被用来简化压缩协议。非凡是,TCP的可靠传输以及字节流对话模型被本协
议用来消除不必要的错误改正对话(见第4章)


Jacobson[Page1]
RFC1144CompressingTCP/IPHeadersFebruary1990

(2)即使由一条足够快的链路(大于等于4800bps)来处理击键反馈的数据包,可能
在批量数据和交互流量上产生不想要(undisirable)的交互。为了合理的链路效率,要求批
量数据包的大小要达到头部大小的10--20倍。也就是说,对于40字节的TCP/IP头部,链
路的最大传输单元(MTU)应该为500到1000字节。即使服务类型(type-of-service)的排
队认为交互式业务优先,一个telnet数据包还是得等待当前的批量传送的数据包传送结
束,假设数据传输仅在一个方向上进行,等待时间为传输MTU的一半,对于MTU为1024
字节9600bps的链路来说,约为500ms。

(3)任何通信介质都有一个最大信号传输速率,即香农极限(AT&T研究结果,参
考文献[2])。对于典型的拨号电话线香农极限为22,000bps左右。因为全双工的9600bps
的Modem已经达到了该极限的80%,modem的制造商开始提供不对称(带宽)分配方
案来提高有效带宽:既然一条链路链路的两个方向很少同时相同的数据量,就有可能通
过对一条半双工链路进行时分多路复用(例如TelebitTrailblazer),或者提供一条低速的
“反向信道”(例如USRCourierHST)(注2)来给链路的一端分配大于11,000bps的带宽。
在两种情况下,modem通过假设对话的一方为人(也就是说带宽要求小于300bps,取决于
击键的速度)动态地试图猜测对话的哪一端需要更高带宽。由于协议头部而导致带宽乘
以40,从而欺骗这种带宽分配方案并引起modem“逆风而行”(thrash)。

从上面的讨论来看,很明显,压缩算法的一个主要设计目标是限制击键(typing)
和确认(ack)流量的带宽要求最多为300bps。典型的最大击键速度大约为每秒钟5个字符
(注3),对于每敲击一个键,留下30-5=25个字节给头部,或者说每敲击一个键就需要
5个字节的头部(注4),5个字节的头部直接解决了问题(1)和(3)并间接解决问题(2):长
度为100-200字节的数据包将很轻易偿还5个字节头部的代价并且把链路带宽的95-98%
提供给用户用于数据部分。


注2:见参考文献([1],11章)中关于双绞线拨号线路性能的讨论。非凡是,对于
“echo-canclling”modem的(诸如符合CCITTV.32的Modem)性能有很广泛的误解:
Echo-cancellation能够提供给双绞线的每一端全部的链路带宽,但是,由于远程用户的信
号增加了本地噪音,没有达到线路的全部链路性能。22kbs的香农极限是双绞线电话连
接的数据速率的硬限制(hard-limit)。

注3:见参考文献[13]。击键流或多字符击键如光标键将超过该平均速率(2-4倍)。但
是带宽要求大致保持不变,因为TCPNagle算法(参考文献[8])每隔不到200ms对流量
进行统计,提高的头部/数据比补偿了增加的数据。

注4:类似的分析可以得出本质上相同的批量数据传输确认(bulkdatatransferack)
的头部大小限制。假设MTU已经被选来对“不客气的”(unoBTrusive)后台文件传输(也
就是说,选用数据包发送时间为200-400ms,见第5章),在“高带宽”方向每秒钟最多
能传送5个数据包。合理的TCP实现将最多每隔一个数据包发送一个确认(ack),以便5
字节的ack的反向信道的带宽为2.5_5_12.5字节/秒。
Jacobson[Page2]
RFC1144CompressingTCP/IPHeadersFebruary1990




这些小的数据包意味着在交互式及批量传输之间很少产生冲突(见章节5.2)。

另一个设计目标是压缩协议仅仅建立在保证串行链路的两端都知道的信息基础上。
考虑图1所示的拓扑结构,通信主机A和B在各自的本地网(黑线表示)上,两个网络通过
两条串行链路连接(网关C和D之间,及E和F之间的空心线)(注5)。一种可能的压缩就
是把每一个TCP/IP对话转变成语义上等价的另一个协议对话,这种协议的头部比TCP/IP
头部更小,例如,X.25。但是因为路由的瞬变性(transient)和多路性(multipathing),
完全有可能是A-B的某些流量沿着A-C-D-B路径而某些流量沿着A-E-F-B路径。同样,有
可能A-B的流量沿着A-C-D-B,B-A的流量沿着B-F-E-A。没有一个网关能指望在一个
具体的TCP对话中看到该对话的所有数据包,图1的拓扑结构的压缩算法不能与TCP连接
的语法联系起来。

把一个物理信道视为两个独立的、(每一个方向上)单向的链路,对拓扑结构、路
由选择、流水线操作的要求最小。单向链路每一端仅必须在该链路最近收到的数据包保
持一致(agreeon)。这样,虽然任意压缩方案涉及到共享状态,但状态在空间上是临
时、局部的并且符合DaveClark的fatesharing原则(参考文献[4]):两端仅在它们之间
的链路连接不可操作时(状态)才不一致,在这种情况下的不一致并无大碍(doesn't
matter)。




注5:注重虽然TCP的端点为A和B,在本例中压缩/解压缩必须在网关之间的串行链路上
进行,也就是说在C和D之间以及在E和F之间。因为A和B使用IP,它们不可能知道他们的
通信路径中包含一段低速串行链路,一个很明显的要求是压缩不能破坏IP模型,也就是
说压缩运行在中间系统(intermediate)中而不仅是在端点系统中。




Jacobson[Page3]
RFC1144CompressingTCP/IPHeadersFebruary1990

3压缩算法
3.1基本思想
图2显示了典型的(最小长度的)TCP/IP数据报头部注6,头部为40字节:20字节
的IP头部和20字节的TCP头部。不幸的是,因为TCP和IP并不是由一个委员会设计的,
头部中所有这些域都各自用于某个目的,不可能因为效率的原因简单忽略掉某些域。
但是,TCP建立起连接并且每个连接都进行着几十甚至几百个数据包的交换。在整
个连接中每一个数据包有多少信息可能保持不变呢?一半——即图3中的阴影部分。因
此假如发送者和接收者跟踪(keeptracKOF)这些活动的连接注7,并且每个连接的接收者
保留上次收到数据包的头部的拷贝,这样发送者通过发送一个很小的(≤8位)连接标
识符(connetionidentifier)和变化的20字节,让接收者从上次保存的头部中把另外20个
保持不变的字节填入,就可以使数据包压缩为原来的1/2(afactor-of-twocompression)。
还可以从中提取其它几个字节。注重到任何合理的链路帧协议将告诉接收者所接收
消息的长度,由此TotalLength(第2、3字节)也是多余的;然后,headerchecksum域(第
10和11字节)用来保护各跳(individualhops)不处理“脏的”(corrupted)IP头部,


注6:TCP和IP协议议及协议头在参考文献[10]和[11]中描述。
注7:96位的元组(srcaddress,dstaddress,srcport,dstport)唯一定义了一个TCP连接。

Jacobson[Page4]
RFC1144CompressingTCP/IPHeadersFebruary1990




本质是正在发送的IP头部的唯一部分。对不是正在传送的信息的传送进行保护是可笑的。
所以,接收者可以在头部被实际发送时检查headerchecksum(也就是说未压缩的数据包),
但是,对于压缩后的数据报文,在IP头部的其它部分被重构的同时,在本地重构header
checksum注8。

这样要发送的头部信息有16字节。所有这些字节在对话的整个过程中都有可能发生
变化但它们不会同时改变。例如在FTP数据传输过程中仅仅packetID,sequencenumber
和checksum在发送者——>接收者方向变化,仅有packetID,ack,checksum可能还有
window,在接收者——〉发送者方向发生变化。有了每个方向上上一次发送的数据包的
拷贝,发送者可以算出当前数据包中哪些域发生变化,然后发送一个比特掩码后跟变化







注8:IP头部检验和并不是参考文献[14]意义上的端对端检验和:time-to-live的更新迫使每一次转发(hop)
时都要重新计算IP检验和。作者曾有很不愉快的个人经历,因为违反了参考文献[14]中的端对端的讨论
(argument),本协议对端对端的TCP检验和未加改变地给予透传(passthrough)。见第4章。

Jacobson[Page5]
RFC1144CompressingTCP/IPHeadersFebruary1990

的域来表明变化的部分发生了哪些变化(注9)。
假如发送者仅发送变化的域,上面的方案可得到平均10个字节左右的头部。但是,
值得一看的是这些域的变化情况:典型地,数据包ID由每发送一个包就增加1的计数器
得到,也就是说,当前数据包与前一个数据包的ID之差应该是一个很小的正整数,通常
小于<256(一个字节)并且经常等于1。对于从发送方传来的数据包,当前数据包中的顺序
号(sequencenumber)将等于前一个数据包的顺序号加上前一个数据包的数据量(假设
数据包按顺序到达)。因为IP数据包最大为64K,顺序号的变化必须小于216(两个字节)。
因此,假如传送的是变化的域之差(deference)而不是这些域自身,每一个数据包可以节
省另外三四个字节。
这就使我们向5个字节头部的目标迈进。考虑几个非凡情况可使我们得到两种最常
见情形下的3个字节的头部——交互式击键流量和批量数据传输——但基本的压缩方案
是上面的差分编码(differentialcoding)。假如这种智力练习表明可以得到5个字节的头
部,似乎丢失某些细节的信息而真正实现了某些东西应该是合理的。
3.2大致细节
3.2.1综述
图4显示了压缩软件的模块图。网络系统调用一个SLIP输出驱动程序(参数为一个
要在该串行链路上发送的IP包)。数据包进入压缩程序(compressor),压缩程序检查
数据包的协议是否TCP。非TCP数据包以及“不可压缩的”(“uncompressible”)TCP
数据包(如后面所述)仅被标记为TYPEIP并传送到成帧器(framer)。对于可压缩的TCP数
据包,则在数据包头部阵列中查找(与之匹配的连接)。假如找到匹配的连接,则进来
的(incoming)数据包被压缩,(未压缩之前的)数据包头部被拷贝到阵列中,类型为
COMPRESSEDTCP的数据包被送到framer。假如没有找到匹配的连接,阵列中最旧的
(oldest)表项(entry)被废弃,数据包的头部被拷贝到该槽(slot)中,并且把一个类
型为UNCOMPRESSEDTCP的数据报送到成帧器。(UNCOMPRESSEDTCP数据包与初始
IP包相同,除了IPprotocol域被一个到已保存的每个连接的数据包头部阵列的索引
connectionnumber取代之外。这就是发送者与接收者(重新)同步,并把它作为压缩数据
包序列的第一个未压缩数据包的“种子”(seed)的方法。
framer负责传送该数据包的数据,类型以及边界(boundary)(以便解压程序知道压
缩程序产生的字节数)。因为编码是差分编码(differentialcoding,),不答应framer对数
据包进行重新排序(在一个单独的串行链路上很少需要考虑)。framer必须提供良好的

注9:这与Thinwire-I(参考文献[5])大致相同。一个稍微的改变是增量编码("deltaencoding"),
即发送者从当前数据包减去前一个数据包(每一个数据包视为16位整数的阵列),然后发送一
个20-bit的掩码表明差(deference)为非0的各域(后跟变化内容)。假如分离不同的对话,
这是一个非常有效的压缩方案(也就是说,典型的得到12-16字节的头部),压缩方不涉及数据
包的包格式细节。这种方案的多个变种已经被成功使用了很多年(例如Proteon路由器的串行
链路协议,参考文献[3])。
Jacobson[Page6]
RFC1144CompressingTCP/IPHeadersFebruary1990

错误检测能力并且假如connectionnumber被压缩,它还必须向解压程序提供错误暗示
(errorindication,见第4章)(注10)。
解压程序在进来的(incoming)数据包的类型来一个'switch':对于TYPEIP,数据包被
简单地透传(passthrough);对于UNCOMPRESSEDTCP,连接号(connectionnumber)
从IPprotocol域中提取(extracted),IPPROTOTCP被存储,然后连接号被用来作为接
收方所保存的TCP/IP头部阵列的索引值,数据包的头部被拷贝到该索引所对应的槽中。
对于COMPRESSEDTCP,连接号用来作为得到该连接上一个数据包的TCP/IP头部的索
引值,压缩数据包中的信息用来更新(上一个数据包的)头部,然后构建一个新的数据
包(包含从阵列中得到的头部,连接从压缩数据包中取得的数据。)
注重通信是单向的——解压程序到压缩程序的方向上没有信息流。非凡地,隐含着
解压程序依靠TCP的重传来在发生链路错误(lineerrors)时改正已保存的状态(见4)。



3.2.2压缩数据包格式
图5显示了压缩后TCP/IP数据包的格式。其中changemask标识了预期变化的哪些域
发生了变化,connectionnumber使得接收方能够定位该TCP连接的上一个数据包的拷贝





注10:链路帧超出本文档的范围。任何帧只要提供了本段中列出的功能(facility),对于本压
缩协议应该是足够的。但是作者鼓励实现者参照参考文献[9]的一个推荐的标准的SLIP帧。


Jacobson[Page7]
RFC1144CompressingTCP/IPHeadersFebruary1990



所存放的位置,不变的TCP检验和使得端到端的完整性检查依然有效,对于changemask
中设置的位,相关域的变化量(可选的域,由改变掩码控制,在图中用虚线表示)。所有
情况下,假如相关的域出现时该位设置为1,假如相关的域不出现则该位为0注11。
因为sequencenumber等的改变量一般都很小,非凡是遵循第5章的调节(tuning)原
则时,所有的数字实际上按一个可变长度方案编码,该方案用8位控制大部分的业务:1
—255的改变量用一个字节来表示,0是不可能出现的(改变量为0则不发送),所以为0
的字节表明一种扩展:接下去的两个字节为16位值的MSB和LSB。大于16位的值强迫发
送为未压缩包。例如15(十进制)编码为0f(十六进制),255编码为ff,65534为00fffe,
而0编码为000000。这种方案的压缩和解码效率很高:通常情况下在MC680x0上编
码和解码都需要执行3条指令。
作为TCP序列号以及ack发送的数值为当前数据包与前一个数据包中的值之差(如
果差为负或大于64K则发送未压缩数据包)注12。作为window发送的值也是当前值与前




注11:图中“P”位与其它位不同:它是TCP头部的"PUSH"位的拷贝。"PUSH"曾被Internet
协会中的某些人认为是必不可少的。因为PUSH能(实际上也)在任何数据包中改变,
因此保留信息的压缩方案必须显式的传送(pass)PUSH。
注12:所有的差使用2'scomplementarithmetic计算。



Jacobson[Page8]
RFC1144CompressingTCP/IPHeadersFebruary1990

一个值之差,但是正负值都答应,因为window域为16位。在设置URG时发送数据包的紧
急事件指针(假如紧急事件指针发生改变而没有设置URG位时发送未压缩数据包)。对于
packetID,发送的值为当前值与前一个值之差。但与域中其它部分不同,当清除I位时假
定的变化值为1,而不是0。
有两种很重要的非凡情况:
(1)sequencenumber和ack都改变(改变值为上个数据包的数据量);window和URG
均不变。
(2)sequencenumber改变(改变值为上个数据包的数据量);ack,window和URG
均不变。
(1)就是终端反馈流量(echoedterminaltraffic)的情况。(2)是非终端反馈流量或单向
数据传输的情况。S,A,W,U的组合用来表明这些非凡情形。'U'(urgentdata)很少出现,
所以两种不太可能的组合是SWU(情形1)和SAWU(情形case2)。为避免二义性,假如
实际改变值是S*WU,则发送未压缩数据包。
因为“活动的”('active')连接很少改变(也就是说用户在换到另一个窗口前将在
一个telnet窗口击键几分钟),C位答应连接号被忽略掉。假如清除了C位,则假设与上一
个压缩或未压缩的数据包是一样的连接;假如设置了C位,则连接号就是紧跟在change
mask后面的那一个字节注13。
从上面的讨论得知,很明显压缩后的终端流量通常看起来像(十六进制):0Bcc
d,0B表明情形(1),cc是两个字节的TCP检验和,d是所敲击的键。命令vir或emacs,或
者FTP“put”或“get”数据传输方向的数据包看起来为0Fccd...,该FTP的ack看起
来为04cca,其中a为被确认的数据量注14。
3.2.3压缩过程(Compressorprocessing)
由待压缩IP数据包和输出串行线的“压缩状态结构”调用压缩程序。它返回的是最
终成帧(framing)的数据包以及该数据包的链路层“类型”。


注13:连接号限制为一个字节,即同时最多能有256个活动的TCP连接。在将近两年的操作
中,作者从没有看到超过16个连接状态的情形有什么用处(即使在SLIP链路用于一个
很繁忙的64-port的终端复用器的网关时也是如此)。这样看来这个限制没有多大意义,
这就答应UNCOMPRESSEDTCP数据包使用protocol域作为连接号,以简化这些数据
包的处理过程。
注14:也很明显changemask很少改变,经常可被忽略。实际上,可以通过保存上一个压缩
数据包(最多为16字节,因此thisisn'tmUChadditionalstate)并检查(除了TCP检验和)是否
发生变化,而得到更好的结果。假如没有变化,发送一个意思为"压缩TCP,与上次相同"的数
据包和一个仅包含检验和和数据的数据包。但是,由于改进最多为25%,增加的复杂性和状
态结构似乎并不可行(justified)。见附录C。



Jacobson[Page9]
RFC1144CompressingTCP/IPHeadersFebruary1990

正如上一节提到的那样,压缩程序把进来的每一个数据包转换成TYPEIP,
UNCOMPRESSEDTCP或者COMPRESSEDTCP数据包。TYPEIP数据包是输入数据包未
加修改的拷贝注15,处理它不会改变压缩程序的状态。
UNCOMPRESSEDTCP数据包除了IPProtocol(第9个字节)从‘6’(TCP协议)改
为连接号connectionnumber外与输入数据包相同。另外,与连接号关联的状态槽被输入
数据包的IP和TCP头部更新,连接号记为本串行链路的lastconnectionsent(对下面的C
压缩程序而言)。
COMPRESSEDTCP数据包包含初始数据包的数据(假如初始数据包有数据的话),
但是IP和TCP头部已完全被一个新的经过压缩的头部取代,连接状态槽和lastconnectionsent
被输入数据包中像UNCOMPRESSEDTCP那样更新。
压缩程序的决定过程为:
● 假如数据包的协议不是TCP,把它作为一个TYPE_IP数据包发送。
● 假如数据包是一个IP分片(即fragmentoffset域不为0或设置morefragments位),
把它作为TYPEIP数据包发送。注16
●假如设置了TCP控制位SYN,FINorRST或者ACK为0,把该数据包视为不可压
缩,把它作为TYPE_IP发送。注17

假如数据包通过了上述检查,它将被作为UNCOMPRESSEDTCP或COMPRESSED
TCP发送。
● 假如没有找到与数据包的源地址、目的IP地址及TCP端口号匹配的连接状态,
将收回某状态(可能为最近最少使用的),发送一个UNCOMPRESSEDTCP。

.
注15实际上不必要(也不想)为三种输出数据包中的任一种复制输入数据包。注重压缩器
不能增加一个数据报的大小。正如附录A中的代码显示的那样,本协议能“就地”(in
place,取代式地)完成头部的修改。
注16仅仅第一个分片包含TCP头部所以分片偏移量的检查是必要的。第一个分片可能包
含一个完整的TCP头部,因而,可以被压缩。但是检查一个完整的TCP头部增加很多代
码,按照参考文献[6]中的讨论,未经压缩地发送所有IP分片似乎是合理的。
注17 ACK检查是多余的,因为符合标准的实现必须在所有数据包中设置ACK,除了初
始的SYN数据包以外。但是,该检查不花任何代价并且避免把一个假(bogus)的数据包
变成一个有效的数据包。
SYN数据包不压缩是因为仅有一半包含有效的ACK域并且他们通常包含一个TCP
选项(最大分段大小,themaxsegmentsize),而在以后的数据包中没有该选项。这样下
一个数据包将不经压缩便发送,因为TCP头部长度已经改变,而发送SYN为
UNCOMPRESSEDTCP,而不是TYPEIP,将不花任何代价(wouldbuynothing)。
决定不对FIN数据包进行压缩值得质疑。对附录B.1中的技巧打一下折扣,头部中有
一个空闲位可用来传送FIN标志。但是,因为连接过程中传送很多数据包,把一个
比特赋予在连接的生存期仅出现一次的标志似乎不太合理。


Jacobson[Page10]
RFC1144CompressingTCP/IPHeadersFebruary1990

● 假如找到一个连接状态,则比较其所包含的数据包头部与当前的数据包头部以确保
没有出现实现没有预料的变化(举例说来,图3中所有的阴影域都一样)。检查IP
protocol,fragmentoffset,morefragments,SYN,FIN以及RST域,检查源、
目的地址及端口号以定位状态结构所处的位置。所以剩下要检查的域是
protocolversion,headerlength,typeofservice,don'tfragment,
time-to-live,dataoffset,IP选项(假如有)和TCP选项(假如有)。假如两者头
部中任一个域不同,则发送UNCOMPRESSEDTCP数据包。

假如所有的“不改变"域匹配,将试图对当前数据包进行压缩:
——假如设置了URG标志,urgentdata域被编码(注重可能为0),设置改变掩码中
的U位。不幸的是,假如URG被清除,urgentdata域必须与前一个数据包进行比
较,假如发生改变,则发送UNCOMPRESSEDTCP数据包。(“Urgentdata”在
URG被清除时不应该改变,但参考文献[11]不作这样的要求)。
——计算当前数据包与前一个数据包中window域之差,假如非0,差被编码并且设置
changemask中的W位。
——计算ack域之差,假如结果小于0或者大于216-1,发送一个UNCOMPRESSEDTCP
数据包注18。否则,假如结果为非0,差被编码,设置changemask中的A位。
——计算sequencenumber域之差。假如结果小于0或大于216-1,发送UNCOMPRESSED
TCP数据包注19。否则假如结果为非0,被编码且设置changemask中的S位。

一旦判定出U,W,A和S发生改变,检查非凡情形的编码:
--假如设置了U,S和W,改变量符合非凡编码的一种情形。发送一个
UNCOMPRESSEDTCP数据包。
--假如仅设置S,比较改变量是否等于最后一个数据包中的用户数据。即从
上一个数据包中的totallength中减去TCP和IP头部长度,将结果与S的改
变量进行比较。假如它们相同,设置changemask为SAWU(“单向数据传输”
的非凡情形),并丢弃编码后的sequencenumber改变(压缩程序可以重建
它,因为它知道最后一个数据包的总长度和头部长度)。







注18.这两个比较(test)可以合并为最高有效16位的差是否为非0。
注19.改变量为负的sequencenumber暗示着可能是一次重传。因为这有可能是因为压缩程序
丢失了一个数据包,所以发送一个未压缩数据包以对解压器进行重同步(re-sync,见4)。



Jacobson[Page11]
RFC1144CompressingTCP/IPHeadersFebruary1990

——假如仅设置了S和A,检查是否它们的改变量是否相同,并且该变量是否
等于最后的数据包中的用户数据量。假如这样,设置changemask为
SWU(“回显交互式”流量的非凡情形),并丢弃已编码的改变量。
——假如什么都没有改变,检查是否该数据包没有用户数据(这种情况可能
是一个重复的确认或窗口检测windowprobe),或者前一个数据包是否包
含用户数据(意味着该数据包是非流水线连接上重传数据包
retransmissiononaconnectionwithnopipelining)。在这两种情况下,发
送一个UNCOMPRESSEDTCP数据包。

最后,输出数据包的TCP/IP头部被(压缩程序产生的)压缩头部取代。
——计算packetID的变化,假如不是1注20,差被编码(注重可能为0或负数)并
设置改变掩码的I位。
——假如最初数据包中设置PUSH位,设置改变掩码中的P位。
——数据包的TCP和IP头部被拷贝到连接状态槽(connectionstateslot)中。
——(最初)数据包的头部被丢弃,新的头部被添加到前面(prepended)。包括(以
倒序):
–累积的变化量编码。
–TCPchecksum(假如新的头部“就地”(inplace,取代式地)创建,checksum
有可能已经被覆盖,必须从最初的头部被丢弃之前拷贝到连接状态结构中或
者临时保存的拷贝中获取。
–connectionnumber(假如与该串行链路上最后一次发送的不同)。这也意味着
该链路的lastconnectionsent必须设为connectionnumber并在改变掩码中设
置C位。
–改变掩码(changemask)
这时候,压缩的TCP数据包传到成帧器(framer)以待发送。
3.2.4解压过程
因为这是一个单向通信模型,解压程序的处理过程比压缩程序的处理过程要简单的
多——所有的判定(decisions)已经决定好了,解压程序只要简单地按照压缩程序叫它
做的工作。



注20.注重是与1而不是与0进行比较。packetID典型的是每发送一个数据包增1,所以该变
量为0的情况是不太可能出现的。改变量为1的情况是可能的:在系统仅在一个连接上有活动
时发生。




Jacobson[Page12]
RFC1144CompressingTCP/IPHeadersFebruary1990

解压程序由输入数据包注21,数据包的类型以及输入串行链路的压缩状态结构调
用。返回一个(可能被重新构建的)IP数据包。
解压器可接收4种类型的数据包:由压缩程序产生的三种包以及当帧接收器检测到
错误(注22)时所产生的一个TYPE_ERROR假(pseudo-)数据包。第一步是对这些数据
包类型来一个“switch”:
● 假如数据包为TYPEERROR或者未知的类型,状态结构中设置一个“丢弃”
('toss')标志以迫使COMPRESSED_TCP数据包被丢弃,直到收到设置C位的数
据包或者一个UNCOMPRESSEDTCP数据包。不返回任何东西(空数据包)。
● 假如数据包为TYPE_IP,不加改变地返回它的拷贝并且状态结构不改变.
● 假如数据包为UNCOMPRESSEDTCP,检查IPProtocol域的状态索引(注23)。如
果非法,设置“丢弃”标志,不返回任何值。否则,清除“丢弃”标志,索引被拷
贝到状态结构的lastconnectionreceived域,创建输入数据包的一份拷贝(注
24)。TCPprotocol号被恢复(restore)到IPprotocol域,数据包头部被拷贝到
(索引)表明的状态槽中,返回数据包的拷贝。

假如数据包类型不在上面讨论之中,它就是COMPRESSEDTCP,必须由数据包的
信息和状态槽中上一个数据包头部合成一个新的TCP/IP头部。首先,显式或隐式的
connectionnumber用来定位状态槽:
——假如改变掩码中设置了C位,那么检查状态索引。假如非法,设置“丢弃位”
标志,不返回任何值。否则,lastconnectionreceived设为该数据包的状态索
引值,清除“丢弃”位。
——假如C位被清除(为0),并且设置了“丢弃”位标志,该数据包被忽略,不
返回任何值。
此时,lastconnectionreceived是适当的状态槽的索引,压缩数据包的第一个(头
几个)字节(改变掩码,可能还有连接索引connectionindex)已经被消化掉(consumed)。


注21.假设链路层framing此时已被去除,数据包和length不包括type或framing字节数。
注22.TYPEERROR数据包不需与任何数据相关。它之所以存在是为了使帧接收器能告诉
解压程序数据流中可能有断点(gap)。解压器使用TYPE_ERROR通知(signal)解压程序:数
据包应该被丢弃直到一个有显式连接号(connectionnumber)的数据包(即设置了C位)到达。
这样做的必要性见4.1节最后的讨论。
注23.状态索引遵循C语言的习惯,从0到N-1,0<N≤256是可用状态槽的索引。
注24.对于压缩程序来说,可以对代码进行结构化(structured)以便不需要拷贝,所有的修改
都是“取代式”(inplace)的。但是,因为输出数据包可能比输入数据包大,必须在输入
数据包缓冲区的前部保留128个字节的空间以答应放入TCP/IP头部。





Jacobson[Page13]
RFC1144CompressingTCP/IPHeadersFebruary1990

因为状态槽中的TCP/IP头部必须终结以反映新到来的数据包,把这些数据包的改变量
应用于头部,然后由头部连接(concatenate)输入数据包的数据从而构建出输出数据包
是最简单的。(在下面的描述中“已保存头部”用作“保存在状态槽中的TCP/IP头部”
的缩略语)
——输入数据包中接下来两个字节是TCP检验和。它们被拷贝到“已保存头部”中。
——假如改变掩码中设置了P位,“已保存头部”中设置TCPPUSH位。否则清除
PUSH位。
——假如改变掩码的低4位(S,A,W和U)都设置为1(“单向数据”的非凡情形),上
一个数据包的用户数据量通过从“已保存头部”中的IPtotallength中减去TCP
和IP头部长度计算得到。该用户数据量(计算的结果)被加到“已保存头部”
的TCPsequencenumber中。
——假如设置了S,W和U,但清除了A(“终端流量”非凡情形),上一个数据包的
用户数据量被计算并被加到“已保存头部”中TCPsequencenumber和ack域中。
——否则,改变掩码的各位按压缩程序设置的顺序解释:
—假如设置了U位,在“已保存头部”中设置TCPURG位,输入数据包接下
来的字节被解码并填充到到TCPUrgentPointer中。假如清除了U位,则清
除TCPURG位。
—假如设置了W位,输入数据包中接下来的字节被解码并被添加到“已保存
头部”中的TCPwindow域。
—假如设置了A位,输入数据包接下来的字节被解码并被添加到“已保存头部”
的TCPack域中。
—假如设置了S位,输入数据包中接下来的字节被解码并被加到“已保存头部”
的TCPsequencenumber域中。

——假如改变掩码设置了I位,输入数据包接下来的字节被解码并被加到已保存数
据包的IPID域。否则IPID.加1。

至此,输入数据包中的所有头部信息被消耗(consumed)完毕,仅剩下数据部分。
剩下的数据部分的长度与已保存TCP和IP头部的长度相加,结果放入已保存IPtotal
length域中。已保存IP头部现已被更新,故其检验和被重新计算并被保存到IPchecksum
域中。最后,包括“已保存头部”与剩下的输入数据的输出数据报被构建并返回。










Jacobson[Page14]
RFC1144CompressingTCP/IPHeadersFebruary1990
4错误处理
4.1错误检测
根据作者的经验,拨号连接非凡轻易出现数据错误。这些错误与压缩从两方面交互:
首先是已压缩数据包中的错误的局部(local)影响。所有的错误检测建立在数据冗
余基础上,但压缩几乎挤出了TCP和IP头部中的全部冗余数据。也就是说,解压程序将
很乐意把随机的链路噪音变成一个有效的TCP/IP包(注25)。人们可以根据TCP检验和
检测“脏的”(“corrupted”)的压缩数据包,但是不幸的是,某些很有可能发生的错
误不会被检测到。例如,TCP检验和经常不会检测到被16位分开的2个单独的比特的错
误。对于V.32modem以2400波特率,4bits/baud发送信号,任意持续超过40us的链路
瞬时干扰(linehit)将破坏16比特。按照参考文献[2],住宅电话的链路瞬时干扰有可能达
到2ms。
处理这个问题的正确方法是在链路层提供错误检测。因为framing(至少在理论上)
可以处理使之符合特定链路上的某些特性,检测可以根据该链路作为轻权或重权(注
26)。因为数据包错误检测是在帧级别上进行,解压程序简单的假设它会收到当前接收
的数据包有错误的暗示(解压器总是忽略(丢弃)有错误的数据包,但是,需要有暗示
来防止错误被进一步传播——见下面)。
“丢掉错误的数据包”策略引起错误和压缩的第二种交互。考虑下面的对话:
Orignal
Sent
Recived
reconstucted
1:A
2:BC
4:DE
6:F
7:GH
1:A
Δ1,BC
Δ2,DE
Δ2,F
Δ1,GH
1:A
Δ1,BC
——
Δ2,F
Δ1,GH
1:A
2:BC
——
4:F
5:GH
(上面的每个表项都有“startingsequencenumber:datasent”或“Δ_sequencenumber
change,datasent”的格式)。发送的第一个数据包是一个未压缩数据包,跟着的是四个
压缩数据包。第三个数据包出现一个错误并被丢弃。为重构第4个数据包,接收者把输
入的压缩数据包中的sequencenumber变化量应用到上一个正确接收的数据包(即第2个
数据包)的sequencenumber上,产生第4个数据包的不正确的sequencenumber。在错误



注25:模(modula)TCP检验和。
注26:虽然合适的错误检测方法依靠于链路,参考文献[9]中使用的CCITTCRC在很多链路
上取得了计算的简易性和错误检测的健壮性(robust)之间的完美的平衡,尤其是为了好的
交互响应而需要相对小的数据包时。这样,由于互操作性的原因,除非有强有力的原因以外,
应该使用参考文献[9]中的帧(格式)。
Jacobson[Page15]
RFC1144CompressingTCP/IPHeadersFebruary1990

出现之后,所有重建数据包的sequencenumber将都是错误的,均比正确值减小所丢失数
据包的数据量(注27)。
假如没有某种类型的检查,前述错误将导致接收者在传输过程中看不见丢失了的两
个字节(因为解压器重新产生sequencenumber,包含F和GH的数据包到达接收者时将有
正确的sequencenumber,假如DE数据包不曾存在)。虽然TCP对话可以在丢失数据后继
续存活(注28),但是不鼓励这么做。幸运的是,由于TCP检验和是一个数据包内容(包
括sequencenumber)的简单和(singlesum),它可以百分之百地检测到这种错误。例
如,上面的接收方计算出的最后两个数据包的检验和,总是与数据包的检验和相差2。
不幸的是,假如输入压缩数据包(incomingcompressedpacket)的变化量被应用到
错误的对话时,总是有办法令上面所讲的的TCP检验和失效:考虑两个活动对话C1和C2,
一个从C1来的数据包后面跟着两个来自C2的数据包。因为连接号没有改变,则连接号
被C2的第二个数据包省略(omitted)掉。但是,假如C2的第一个数据包接收时发生CRC
错误,则C2的第二个数据包将被错误的当成C1的第二个数据包。因为对于C1的sequence
number来说,C2的检验和是一个随机数,至少有2-16的概率使得该数据包被C1的TCP
接收方接收(注29)。为防止这种情况发生,在收到来自成帧器(framer)的CRC错误
暗示后,接受方丢弃数据包直到收到一个设置了C位的COMPRESSEDTCP数据包或者是
一个UNCOMPRESSEDTCP数据包。也就是说,数据包被丢弃直到接收到带显式
connectionnumber的数据包。
总结本节,有两种不同的错误类型:数据包的“变质”(corruption),和会话同步
的丧失(loss-of-sync)。第一种类型解压器方通过链路层CRC错误检测到,第二种在TCP
接收方通过一个(保证)有效的TCP检验和检测到。这两种独立机制的联合使用确保了
错误数据包被丢弃。
4.2错误恢复
前面章节提到,在CRC错误之后解压器将在每一个未压缩数据包中引进TCP检验和
错误。虽然检验和错误防止了数据流的变质(corruption),在解压器产生出一个有效的
数据包之前该TCP对话将不可用。这是怎样发生的呢?
解压器产生无效的数据包是因为其状态结构(已保存的“上一个数据包头部”)与
压缩器的状态结构不一致。一个UNCOMPRESSEDTCP数据包将修正解压器的状态。这样,
错误恢复等于在解压器在感到混乱(confused)时迫使压缩器发送一个未压缩数据包。





注27:这是使用差分编码或增量编码时一个普遍的问题的一个实例,称为"losingDC"。
注28.很多系统治理员声称NNTP数据流中的漏洞(holes)比数据更有价值(morevaluable)。
注29.在最差情况下的流量下,该概率理解为一条9600波特率,错误率为30%的链路上每3
个小时就有一个错误没有被检测出来。

Jacobson[Page16]
RFC1144CompressingTCP/IPHeadersFebruary1990




首先要考虑的是利用全双工通信链路,让解压器请求压缩器发送一个非压缩数据
包。很显然这是我们不想要的,因为它包含的拓扑结构比第2章中建议的最小拓扑结构
复杂得多,并且要求给解压器和压缩器增加很多协议。稍加考虑就可以知道这种选择不
仅仅是我们不想要的,它简直就无法工作(奏效):压缩数据包很小,以至于链路瞬时
干扰(linehit)可能把它完全沉没,最终解压器什么也没收到。这样数据包被错误地重
构(因为压缩包的丢失),但是仅有TCP端(end)知道数据包是错误的,解压器根本不
知道。
但是,既然TCP端知道该错误,而TCP设计为运行在不可靠介质上的可靠协议,这
就意味着TCP两端必须最终采取某种错误恢复措施,压缩器必须有明显的触发器以对解
压器进行重新同步:TCP进行错误恢复时发送一个非压缩包。
但是压缩器怎样才能认出这是TCP错误恢复呢?考虑图6中的TCP数据传输示意图。
感觉到混乱的(confused)解压器处于TCP对话的前向部分(即数据传输部分)。TCP
接收方丢掉数据包而不是对它们进行确认(因为检验和出错),TCP发送方最终超时并
重发数据包,前向中的压缩器发现重传数据包的sequencenumber和它所看到的上一个数
据包的sequencenumber之差为负数(假如有几个数据包通过)或者0(只有一个数据包通


Jacobson[Page17]
RFC1144CompressingTCP/IPHeadersFebruary1990





过)。第一种情况在压缩阶段计算序列号之差的步骤检测到。第二种情况在检查“非凡
情形”编码阶段检测,但还要求另一个检查:交互式对话中经常先发送一个无数据的ack
再发送一个包含数据的数据包,ack和包含数据的数据包有相同的sequencenumber但是
数据包却不是(该ack的)重传。为防止发送一个不必要的未压缩数据包,应该检查前
一个数据包的长度,假如它包含数据,sequencenumber变化量为0则肯定(must)意味
着重传。
图7中会话的反向部分链路(即确认方向)上感到混乱的解压器也很轻易检测到:
TCP发送方丢掉ack(因为它们包含检验和错误),最终超时,然后重传某数据包。这样TCP
接收方就收到重复的数据包,它必须发送一个ack以指明期望的下一个sequencenumber。
值产生一个ack(参考文献[11],p.69)。该ack重复了TCP接收方产生的上一个ack,所
以反向的压缩器将发现ack,seqnumber,window或urg没有改变。假如这发生在不包含数
据部分的数据包上,压缩器就会假设它是对应于重传而发送的重复的ack,因而发送一
个UNCOMPLESSEDTCP数据包(注30)。





注30:该数据包可以为一个0-窗口检测而不是重传ack,但窗口检测不应太频繁,未压缩便
发送它们没有坏处

Jacobson[Page18]
RFC1144CompressingTCP/IPHeadersFebruary1990