附錄A:一個實現示例
下面是本文檔中描述的協議的一個實現例子。
因為很多可能處理這些代碼的人很熟悉BerkeleyUnix內核及其編碼風格(被親切地稱為
“內核規范格式”),這些代碼正是遵循該風格。使用了Berkeley“子程序”(subroutines,
實際上就是宏和/或內聯匯編的擴展)來轉換網絡字節序(networkbyteorder)以及拷貝
/比較字節串。這些例行程序在A.5中為那些不熟悉它們的讀者作簡要介紹。
這些代碼在25頁列出的所有機器上運行通過。作者希望沒有字節序或對齊方面的問
題(但是其中嵌入了假設的對BerkeleyUnix有效的對齊方式,這種對齊方式對于其他ip
實現可能不正確。見sl_comPRess_tcp和sl_decompress_tcp中提到的關于對齊的注釋)。
曾試圖嘗試使這些代碼更高效,不幸的是可能會使其中的一部分不可理解。作者為
此感到抱歉(老實說,我的C語言風格被認為是晦澀的,并且借口就是“效率”)。
該實例代碼以及完整的BerkeleyUnix實現(機器可讀的)通過匿名FTP從
ftp.ee.lbl.gov(128.3.254.68)上得到,即文件cslip.tar.Z。這是一個壓縮的Unix
tar文件,必須以二進制模式進行ftp。附錄中所有代碼均包含下面的版權通告:/*
*Copyright(c)1989RegentsoftheUniversityofCalifornia.
*Allrightsreserved.
*
*Redistributionanduseinsourceandbinaryformsare
*permittedprovidedthattheabovecopyrightnoticeandthis
*paragraphareduplicatedinallsUChformsandthatany
*documentation,advertisingmaterials,andothermaterials
*relatedtosuchdistributionanduseacknowledgethatthe
*softwarewasdevelopedbytheUniversityofCalifornia,
*Berkeley.ThenameoftheUniversitymaynotbeusedto
*endorSEOrpromoteproductsderivedfromthissoftware
*withoutspecificpriorwrittenpermission.
*THISSOFTWAREISPROVIDED``ASIS''ANDWITHOUTANYEXPRESS
*ORIMPLIEDWARRANTIES,INCLUDING,WITHOUTLIMITATION,THE
*IMPLIEDWARRANTIESOFMERCHANTIBILITYANDFITNESSFORA
*PARTICULARPURPOSE.
*/
Jacobson[Page27]
RFC1144CompressingTCP/IPHeadersFebruary1990
A.1DefinitionsandStateData
#defineMAX_STATES16/*mustbe>2and<255*/
#defineMAX_HDR128/*maxTCP+IPhdrlength(byprotocoldef)*/
/*packettypes*/
#defineTYPE_IP0x40
#defineTYPE_UNCOMPRESSED_TCP0x70
#defineTYPE_COMPRESSED_TCP0x80
#defineTYPE_ERROR0x00/*thisisnotatypethateverappearson
*thewire.Thereceiveframerusesitto
*tellthedecompressortherewasapacket
*transmissionerror.*/
/*Bitsinfirstoctetofcompressedpacket*/
#defineNEW_C0x40/*flagbitsforwhatchangedinapacket*/
#defineNEW_I0x20
#defineTCP_PUSH_BIT0x10
#defineNEW_S0x08
#defineNEW_A0x04
#defineNEW_W0x02
#defineNEW_U0x01
/*reserved,special-casevaluesofabove*/
#defineSPECIAL_I(NEW_SNEW_WNEW_U)/*echoedinteractivetraffic*/
#defineSPECIAL_D(NEW_SNEW_ANEW_WNEW_U)/*unidirectionaldata*/
#defineSPECIALS_MASK(NEW_SNEW_ANEW_WNEW_U)
/*"state"dataforeachactivetcpconversationonthewire.Thisis
*basicallyacopyoftheentireIP/TCPheaderfromthelastpackettogether
*withasmallidentifierthetransmit&receiveendsofthelineuseto
*locatesavedheader.*/
structcstate{
structcstate*cs_next;/*nextmostrecentlyusedcstate(xmitonly)*/
u_shortcs_hlen;/*sizeofhdr(receiveonly)*/
u_charcs_id;/*connection#associatedwiththisstate*/
u_charcs_filler;
union{
charhdr[MAX_HDR];
structipcsu_ip;/*ip/tcphdrfrommostrecentpacket*/
}slcs_u;
};
#definecs_ipslcs_u.csu_ip
Jacobson[Page28]
RFC1144CompressingTCP/IPHeadersFebruary1990
#definecs_hdrslcs_u.csu_hdr
/*
*allthestatedataforoneserialline(weneedoneoftheseperline).
*/
structslcompress{
structcstate*last_cs;/*mostrecentlyusedtstate*/
u_charlast_recv;/*lastrcvdconn.id*/
u_charlast_xmit;/*lastsentconn.id*/
u_shortflags;
structcstatetstate[MAX_STATES];/*xmitconnectionstates*/
structcstaterstate[MAX_STATES];/*receiveconnectionstates*/
};
/*flagvalues*/
#defineSLF_TOSS1/*tossingrcvdframesbecauseofinputerr*/
/*
*Thefollowingmacrosareusedtoencodeanddecodenumbers.Theyall
*assumethat`cp'pointstoabufferwherethenextbyteencoded(decoded)
*istobestored(retrieved).Sincethedecoderoutinesdoarithmetic,
*theyhavetoconvertfromandtonetworkbyteorder.
*/
/*
*ENCODEencodesanumberthatisknowntobenon-zero.ENCODEZchecksfor
*zero(zerohastobeencodedinthelong,3byteform).
*/
#defineENCODE(n){/
if((u_short)(n)>=256){/
*cp++=0;/
cp[1]=(n);/
cp[0]=(n)>>8;/
cp+=2;/
}else{/
*cp++=(n);/
}/
}
#defineENCODEZ(n){/
if((u_short)(n)>=256(u_short)(n)==0){/
*cp++=0;/
cp[1]=(n);/
cp[0]=(n)>>8;/
cp+=2;/
}else{/
*cp++=(n);/
}/
}
/*
*DECODELtakesthe(compressed)changeatbytecpandaddsittothe
Jacobson[Page29]
RFC1144CompressingTCP/IPHeadersFebruary1990
*currentvalueofpacketfield'f'(whichmustbea4-byte(long)integer
*innetworkbyteorder).DECODESdoesthesamefora2-byte(short)field.
*DECODEUtakesthechangeatcpandstuffsitintothe(short)fieldf.
*'cp'isupdatedtopointtothenextfieldinthecompressedheader.
*/
#defineDECODEL(f){/
if(*cp==0){/
(f)=htonl(ntohl(f)+((cp[1]<<8)cp[2]));/
cp+=3;/
}else{/
(f)=htonl(ntohl(f)+(u_long)*cp++);/
}/
}
#defineDECODES(f){/
if(*cp==0){/
(f)=htons(ntohs(f)+((cp[1]<<8)cp[2]));/
cp+=3;/
}else{/
(f)=htons(ntohs(f)+(u_long)*cp++);/
}/
}
#defineDECODEU(f){/
if(*cp==0){/
(f)=htons((cp[1]<<8)cp[2]);/
cp+=3;/
}else{/
(f)=htons((u_long)*cp++);/
}/
}
Jacobson[Page30]
RFC1144CompressingTCP/IPHeadersFebruary1990
A.2壓縮
該子程序看起來令人生畏,其實并非如此。代碼分為大小大致相等的四塊:第一塊治理
一個最近最少使用的活動TCP連接循環鏈表(注46),第二塊計算sequence/ack/window/urg
變化量并確定壓縮數據包的大致結構(bulk),第三部分處理非凡情形的編碼,第四塊進行
數據包ID和connectionnumber的編碼并且用壓縮后的頭部代替初始數據包的頭部。
該子程序的參數為一個指向待壓縮數據包的指針,一個指向串行鏈路壓縮狀態結構的指
針,一個答應/禁止對connection進行壓縮的標志(即C位)。
壓縮是“取代式”(inplace)的,所以每產生一個壓縮數據包,輸入數據包的開始地址
和長度(m中的off和len域)以反映出初始數據包頭部已被移除并被經過壓縮的頭部所取
代。不管產生壓縮數據包還是未壓縮數據包,壓縮狀態結構都要更新。該子程序返回幀傳輸
器(transitframer)傳送的數據包類型(TYPE_IP,TYPE_UNCOMPRESSED_TCP或者
TYPE_COMPRESSED_TCP)。
由于頭部各種域中有16位和32位,所以輸入IP數據包必須做好對齊(例如,在SPARC
上,IP頭部在32位邊界對齊)。假如不是這樣的話,必須對下面的代碼進行徹底修改(先把
輸入頭部按字節拷貝到某個地方然后再轉變可能代價會小一些)。
注重輸出數據包可以可按任意方式對齊(也就是說,它可以很輕易的從奇字節邊界開始)。
注46:注重對連接表的兩個最常見的操作是終止于第一個入口的查找(最近使用連接的一
個新的數據包)以及把表的最后一個入口移到表的頭部(新連接來的第一個數據包)。一個
循環表可以有效的處理這兩個操作。
Jacobson[Page31]
RFC1144CompressingTCP/IPHeadersFebruary1990
u_char
sl_compress_tcp(m,comp,compress_cid)
structmbuf*m;
structslcompress*comp;
intcompress_cid;
{
registerstructcstate*cs=comp->last_cs->cs_next;
registerstructip*ip=mtod(m,structip*);
registeru_inthlen=ip->ip_hl;
registerstructtcphdr*oth;/*lastTCPheader*/
registerstructtcphdr*th;/*currentTCPheader*/
registeru_intdeltaS,deltaA;/*generalpurposetemporaries*/
registeru_intchanges=0;/*changemask*/
u_charnew_seq[16];/*changesfromlasttocurrent*/
registeru_char*cp=new_seq;
/*BailifthisisanIPfragmentoriftheTCPpacketisn't
*`compressible'(i.e.,ACKisn'tsetorsomeothercontrolbitis
*set).(Weassumethatthecallerhasalreadymadesurethepacket
*isIPprotoTCP).*/
if((ip->ip_off&htons(0x3fff))m->m_len<40)
return(TYPE_IP);
th=(structtcphdr*)&((int*)ip)[hlen];
if((th->th_flags&(TH_SYNTH_FINTH_RSTTH_ACK))!=TH_ACK)
return(TYPE_IP);
/*Packetiscompressible--we'regoingtosendeithera
*COMPRESSED_TCPorUNCOMPRESSED_TCPpacket.Eitherwayweneedto
*locate(orcreate)theconnectionstate.Specialcasethemost
*recentlyusedconnectionsinceit'smostlikelytobeusedagain&
*wedon'thavetodoanyreorderingifit'sused.*/
if(ip->ip_src.s_addr!=cs->cs_ip.ip_src.s_addr
ip->ip_dst.s_addr!=cs->cs_ip.ip_dst.s_addr
*(int*)th!=((int*)&cs->cs_ip)[cs->cs_ip.ip_hl]){
/*Wasn'tthefirst--searchforit.
*Statesarekeptinacircularlylinkedlistwithlast_cs
*pointingtotheendofthelist.Thelistiskeptinlru
*orderbymovingastatetotheheadofthelistwhenever
*itisreferenced.Sincethelistisshortand,
*empirically,theconnectionwewantisalmostalwaysnear
*thefront,welocatestatesvialinearsearch.Ifwe
*don'tfindastateforthedatagram,theoldeststateis
*(re-)used.*/
registerstructcstate*lcs;
registerstructcstate*lastcs=comp->last_cs;
do{
lcs=cs;
cs=cs->cs_next;
if(ip->ip_src.s_addr==cs->cs_ip.ip_src.s_addr
&&ip->ip_dst.s_addr==cs->cs_ip.ip_dst.s_addr
&&*(int*)th==((int*)&cs->cs_ip)[cs->cs_ip.ip_hl])
gotofound;
Jacobson[Page32]
RFC1144CompressingTCP/IPHeadersFebruary1990
}while(cs!=lastcs);
/*Didn'tfindit--re-useoldestcstate.Sendan
*uncompressedpacketthattellstheothersidewhat
*connectionnumberwe'reusingforthisconversation.Note
*thatsincethestatelistiscircular,theoldeststate
*pointstothenewestandweonlyneedtosetlast_csto
*updatethelrulinkage.*/
comp->last_cs=lcs;
hlen+=th->th_off;
hlen<<=2;
gotouncompressed;
found:
/*Foundit--movetothefrontontheconnectionlist.*/
if(lastcs==cs)
comp->last_cs=lcs;
else{
lcs->cs_next=cs->cs_next;
cs->cs_next=lastcs->cs_next;
lastcs->cs_next=cs;
}
}
/*
*Makesurethatonlywhatweexpecttochangechanged.Thefirst
*lineofthe`if'checkstheIPprotocolversion,headerlength&
*typeofservice.The2ndlinechecksthe"Don'tfragment"bit.
*The3rdlinechecksthetime-to-liveandprotocol(theprotocol
*checkisunnecessarybutcostless).The4thlinecheckstheTCP
*headerlength.The5thlinechecksIPoptions,ifany.The6th
*linechecksTCPoptions,ifany.Ifanyofthesethingsare
*differentbetweentheprevious¤tdatagram,wesendthe
*currentdatagram`uncompressed'.
*/
oth=(structtcphdr*)&((int*)&cs->cs_ip)[hlen];
deltaS=hlen;
hlen+=th->th_off;
hlen<<=2;
if(((u_short*)ip)[0]!=((u_short*)&cs->cs_ip)[0]
((u_short*)ip)[3]!=((u_short*)&cs->cs_ip)[3]
((u_short*)ip)[4]!=((u_short*)&cs->cs_ip)[4]
th->th_off!=oth->th_off
(deltaS>5&&BCMP(ip+1,&cs->cs_ip+1,(deltaS-5)<<2))
(th->th_off>5&&BCMP(th+1,oth+1,(th->th_off-5)<<2)))
gotouncompressed;
/*
*Figureoutwhichofthechangingfieldschanged.Thereceiver
Jacobson[Page33]
RFC1144CompressingTCP/IPHeadersFebruary1990
*expectschangesintheorder:urgent,window,ack,seq.
*/
if(th->th_flags&TH_URG){
deltaS=ntohs(th->th_urp);
ENCODEZ(deltaS);
changes=NEW_U;
}elseif(th->th_urp!=oth->th_urp)
/*
*argh!URGnotsetbuturpchanged--asensible
*implementationshouldneverdothisbutRFC793doesn't
*prohibitthechangesowehavetodealwithit.
*/
gotouncompressed;
if(deltaS=(u_short)(ntohs(th->th_win)-ntohs(oth->th_win))){
ENCODE(deltaS);
changes=NEW_W;
}
if(deltaA=ntohl(th->th_ack)-ntohl(oth->th_ack)){
if(deltaA>0xffff)
gotouncompressed;
ENCODE(deltaA);
changes=NEW_A;
}
if(deltaS=ntohl(th->th_seq)-ntohl(oth->th_seq)){
if(deltaS>0xffff)
gotouncompressed;
ENCODE(deltaS);
changes=NEW_S;
}
/*
*Lookforthespecial-caseencodings.
*/
switch(changes){
case0:
/*
*Nothingchanged.Ifthispacketcontainsdataandthelast
*onedidn't,thisisprobablyadatapacketfollowingan
*ack(normalonaninteractiveconnection)andwesendit
*compressed.Otherwiseit'sprobablyaretransmit,
*retransmittedackorwindowprobe.Sendituncompressed
*incasetheothersidemissedthecompressedversion.
*/
if(ip->ip_len!=cs->cs_ip.ip_len&&
ntohs(cs->cs_ip.ip_len)==hlen)
break;
/*(fallthrough)*/
caseSPECIAL_I:
Jacobson[Page34]
RFC1144CompressingTCP/IPHeadersFebruary1990
caseSPECIAL_D:
/*
*Actualchangesmatchoneofourspecialcaseencodings--
*sendpacketuncompressed.
*/
gotouncompressed;
caseNEW_SNEW_A:
if(deltaS==deltaA&&
deltaS==ntohs(cs->cs_ip.ip_len)-hlen){
/*specialcaseforechoedterminaltraffic*/
changes=SPECIAL_I;
cp=new_seq;
}
break;
caseNEW_S:
if(deltaS==ntohs(cs->cs_ip.ip_len)-hlen){
/*specialcasefordataxfer*/
changes=SPECIAL_D;
cp=new_seq;
}
break;
}
deltaS=ntohs(ip->ip_id)-ntohs(cs->cs_ip.ip_id);
if(deltaS!=1){
ENCODEZ(deltaS);
changes=NEW_I;
}
if(th->th_flags&TH_PUSH)
changes=TCP_PUSH_BIT;
/*
*GraBThecksumbeforeweoverwriteitbelow.Thenupdateour
*statewiththispacket'sheader.
*/
deltaA=ntohs(th->th_sum);
BCOPY(ip,&cs->cs_ip,hlen);
/*
*Wewanttousetheoriginalpacketasourcompressedpacket.(cp-
*new_seq)isthenumberofbytesweneedforcompressedsequence
*numbers.Inadditionweneedonebyteforthechangemask,one
*fortheconnectionidandtwoforthetcpchecksum.So,(cp-
*new_seq)+4bytesofheaderareneeded.hlenishowmanybytes
*oftheoriginalpackettotosssosubtractthetwotogetthenew
*packetsize.
*/
deltaS=cp-new_seq;
cp=(u_char*)ip;
if(compress_cid==0comp->last_xmit!=cs->cs_id){
comp->last_xmit=cs->cs_id;
Jacobson[Page35]
RFC1144CompressingTCP/IPHeadersFebruary1990
hlen-=deltaS+4;
cp+=hlen;
*cp++=changesNEW_C;
*cp++=cs->cs_id;
}else{
hlen-=deltaS+3;
cp+=hlen;
*cp++=changes;
}
m->m_len-=hlen;
m->m_off+=hlen;
*cp++=deltaA>>8;
*cp++=deltaA;
BCOPY(new_seq,cp,deltaS);
return(TYPE_COMPRESSED_TCP);
uncompressed:
/*
*Updateconnectionstatecs&senduncompressedpacket
*('uncompressed'meansaregularip/tcppacketbutwiththe
*'conversationid'wehopetouseonfuturecompressedpacketsin
*theprotocolfield).
*/
BCOPY(ip,&cs->cs_ip,hlen);
ip->ip_p=cs->cs_id;
comp->last_xmit=cs->cs_id;
return(TYPE_UNCOMPRESSED_TCP);
}
Jacobson[Page36]
RFC1144CompressingTCP/IPHeadersFebruary1990
A.3解壓縮
該子程序對一個收到的數據包進行解壓。調用參數是一個指向待解壓數據包的指針,數
據包的長度和類型,以及一個指向輸入串行線路的壓縮狀態結構的指針。假如正確則返回指
向結果數據包的指針,假如輸入數據包中有錯誤,返回0,假如數據包類型為COMPRESSED_TCP
或UNCOMPRESSED_TCP,壓縮狀態將被更新。
新的數據包“取代式”地構建得到。這意味著在bufp前必須由128字節的空間以重新構
建IP和TCP頭部。重新構建后的數據包將在32位邊界處對齊。
u_char*
sl_uncompress_tcp(bufp,len,type,comp)
u_char*bufp;
intlen;
u_inttype;
structslcompress*comp;
{
registeru_char*cp;
registeru_inthlen,changes;
registerstructtcphdr*th;
registerstructcstate*cs;
registerstructip*ip;
switch(type){
caseTYPE_ERROR:
default:
gotobad;
caseTYPE_IP:
return(bufp);
caseTYPE_UNCOMPRESSED_TCP:
/*
*Locatethesavedstateforthisconnection.Ifthestate
*indexislegal,clearthe'discard'flag.
*/
ip=(structip*)bufp;
if(ip->ip_p>=MAX_STATES)
gotobad;
cs=&comp->rstate[comp->last_recv=ip->ip_p];
comp->flags&=~SLF_TOSS;
/*
*RestoretheIPprotocolfieldthensaveacopyofthis
*packetheader.(Thechecksumiszeroedinthecopysowe
*don'thavetozeroiteachtimeweprocessacompressed
Jacobson[Page37]
RFC1144CompressingTCP/IPHeadersFebruary1990
*packet.
*/
ip->ip_p=IPPROTO_TCP;
hlen=ip->ip_hl;
hlen+=((structtcphdr*)&((int*)ip)[hlen])->th_off;
hlen<<=2;
BCOPY(ip,&cs->cs_ip,hlen);
cs->cs_ip.ip_sum=0;
cs->cs_hlen=hlen;
return(bufp);
caseTYPE_COMPRESSED_TCP:
break;
}
/*We'vegotacompressedpacket.*/
cp=bufp;
changes=*cp++;
if(changes&NEW_C){
/*
*Makesurethestateindexisinrange,thengrabthe
*state.Ifwehaveagoodstateindex,clearthe'discard'
*flag.
*/
if(*cp>=MAX_STATES)
gotobad;
comp->flags&=~SLF_TOSS;
comp->last_recv=*cp++;
}else{
/*
*Thispackethasanimplicitstateindex.Ifwe'vehada
*lineerrorsincethelasttimewegotanexplicitstate
*index,wehavetotossthepacket.
*/
if(comp->flags&SLF_TOSS)
return((u_char*)0);
}
/*
*FindthestatethenfillintheTCPchecksumandPUSHbit.
*/
cs=&comp->rstate[comp->last_recv];
hlen=cs->cs_ip.ip_hl<<2;
th=(structtcphdr*)&((u_char*)&cs->cs_ip)[hlen];
th->th_sum=htons((*cp<<8)cp[1]);
cp+=2;
if(changes&TCP_PUSH_BIT)
th->th_flags=TH_PUSH;
else
th->th_flags&=~TH_PUSH;
/*
Jacobson[Page38]
RFC1144CompressingTCP/IPHeadersFebruary1990
*Fixupthestate'sack,seq,urgandwinfieldsbasedonthe
*changemask.
*/
switch(changes&SPECIALS_MASK){
caseSPECIAL_I:
{
registeru_inti=ntohs(cs->cs_ip.ip_len)-cs->cs_hlen;
th->th_ack=htonl(ntohl(th->th_ack)+i);
th->th_seq=htonl(ntohl(th->th_seq)+i);
}
break;
caseSPECIAL_D:
th->th_seq=htonl(ntohl(th->th_seq)+ntohs(cs->cs_ip.ip_len)
-cs->cs_hlen);
break;
default:
if(changes&NEW_U){
th->th_flags=TH_URG;
DECODEU(th->th_urp)
}else
th->th_flags&=~TH_URG;
if(changes&NEW_W)
DECODES(th->th_win)
if(changes&NEW_A)
DECODEL(th->th_ack)
if(changes&NEW_S)
DECODEL(th->th_seq)
break;
}
/*UpdatetheIPID*/
if(changes&NEW_I)
DECODES(cs->cs_ip.ip_id)
else
cs->cs_ip.ip_id=htons(ntohs(cs->cs_ip.ip_id)+1);
/*
*Atthispoint,cppointstothefirstbyteofdatainthepacket.
*Ifwe'renotalignedona4-byteboundary,copythedatadownso
*theIP&TCPheaderswillbealigned.Thenbackupcpbythe
*TCP/IPheaderlengthtomakeroomforthereconstructedheader(we
*assumethepacketwewerehandedhasenoughspacetoprepend128
*bytesofheader).Adjustthelenthtoaccountforthenewheader
*&fillintheIPtotallength.
*/
len-=(cp-bufp);
if(len<0)
/*
*wemusthavedroppedsomecharacters(crcshoulddetect
*thisbuttheoldslipframingwon't)
Jacobson[Page39]
RFC1144CompressingTCP/IPHeadersFebruary1990
*/
gotobad;
if((int)cp&3){
if(len>0)
OVBCOPY(cp,(int)cp&~3,len);
cp=(u_char*)((int)cp&~3);
}
cp-=cs->cs_hlen;
len+=cs->cs_hlen;
cs->cs_ip.ip_len=htons(len);
BCOPY(&cs->cs_ip,cp,cs->cs_hlen);
/*recomputetheipheaderchecksum*/
{
registeru_short*bp=(u_short*)cp;
for(changes=0;hlen>0;hlen-=2)
changes+=*bp++;
changes=(changes&0xffff)+(changes>>16);
changes=(changes&0xffff)+(changes>>16);
((structip*)cp)->ip_sum=~changes;
}
return(cp);
bad:
comp->flags=SLF_TOSS;
return((u_char*)0);
}
Jacobson[Page40]
RFC1144CompressingTCP/IPHeadersFebruary1990
A.4初始化
本子程序對某條串行鏈路的傳輸方和接收方的狀態結構都進行初始化。它在每一條
鏈路建立時被調用。
void
sl_compress_init(comp)
structslcompress*comp;
{
registeru_inti;
registerstructcstate*tstate=comp->tstate;
/*
*Cleanoutanyjunkleftfromthelasttimelinewasused.
*/
bzero((char*)comp,sizeof(*comp));
/*
*Linkthetransmitstatesintoacircularlist.
*/
for(i=MAX_STATES-1;i>0;--i){
tstate[i].cs_id=i;
tstate[i].cs_next=&tstate[i-1];
}
tstate[0].cs_next=&tstate[MAX_STATES-1];
tstate[0].cs_id=0;
comp->last_cs=&tstate[0];
/*
*Makesurewedon'taccidentallydoCIDcompression
*(assumesMAX_STATES<255).
*/
comp->last_recv=255;
comp->last_xmit=255;
}
A.5BerkeleyUnix的依靠關系
注重:假如在你想把該例子代碼運行于不是4BSD(BerkleyUnix)的系統上時才有用。
這里的代碼使用了規范的BerkeleyUnix頭文件(位于/usr/include/netinet)來定義IP
和TCP頭部。結構標志(structuretags)遵循RFC的協議,即使你沒有訪問4BSD系統,
也應該是能看懂(注47)。
注48.假如不能看懂,這些頭文件(和所有的Berkeley網絡代碼)可以通過匿名ftp從主
機ucbarpa.berkeley.edu獲取,文件分別為pub/4.3/tcp.tar以及pub/4.3/inet.tar.
Jacobson[Page41]
RFC1144CompressingTCP/IPHeadersFebruary1990
宏BCOPY(src,dst,amt)調用來從src拷貝amt個字節到dst。在BSD中,它被轉化為
bcopy調用。假如你不幸運行于System-VUnix環境下,它將被轉化為memcpy調用。宏
OVBCOPY(src,dst,amt)用來在當src和dst覆蓋(overlap)時的拷貝(也就是說,在做
4-字節對齊的拷貝時)。在BSD內核中,它轉化為一個ovbcopy調用。因為AT&T修補了
memcpy的定義,可能應該轉化為一個System-V下的copy循環。
宏BCMP(src,dst,amt)調用來比較src和dst的amt個字節是否相等。在BSD中,它被
轉化為bcmp調用。在System-V中,它被轉化為memcmp調用,你也可以自己寫一個子程序
來完成這些比較。子程序在src和dst所有的字節都相等時返回0,否則返回非0值。
子程序ntohl(dat)把(4個字節)的long數據從網絡字節序(networkbyteorder)
轉化為主機字節序(hostbyteorder)。在一個合理的cpu中,這可能是“什么都不做的”
宏定義:
#definentohl(dat)(dat)
在一個Vax或者IBMPC(或者任何Intel字節序的系統)中,你將必須定義一個宏或者子
程序來重新安排這些字節。
子程序ntohs(dat)與ntohl相似,但是轉化的是(2個字節的)short而不是long。子程
序htonl(dat)和htons(dat)完成long和short的相反的轉換(主機字節序到網絡字節序)。
結構mbuf在調用sl_compress_tcp時使用,因為該子程序在假如輸入數據包是壓縮數
據包時需要修改startaddress和length。在BSD中,mbuf是內核的緩沖治理結構(buffer
managementstructure)。假如是其它的系統,下面的定義應該足夠了:
structmbuf{
u_char*m_off;/*pointertostartofdata*/
intm_len;/*lengthofdata*/
};
#definemtod(m,t)((t)(m->m_off))
Jacobson[Page42]
RFC1144CompressingTCP/IPHeadersFebruary1990
附錄B與過去錯誤的兼容性
在與現代的PPP串行鏈路協議(參考文獻[9])聯合使用時,頭部壓縮自動進行對用戶
來說是不可見的。不幸的是,很多站點還有使用參考文獻[12]中定義的SLIP的用戶,該協議
沒有顧及到不同的協議類型來區分頭部經過壓縮的數據包和IP數據包,也沒有顧及到版本
號,也沒有顧及到用來自動協商頭部壓縮的選項。
作者已經使用下面的技巧來答應頭部經過壓縮的SLIP與現存的服務器和客戶端實現互
操作。注重這是用來與過去錯誤兼容的手段,思維正確(rightthinking)的人應該把它視
為具有攻擊性的。在這里提供出來只是為了減小在運行SLIP時用戶等待vendors釋放PPP
的痛苦。
B.1沒有“type”字節照樣存活
選用A.1中希奇的數據包類型號是為了答應在不想或者不可能增加一個顯式type
字節時鏈路上發送數據包的類型。注重IP數據包的第一個字節的頭4位總是包含“4”(即
IP協議版本號)。同時壓縮數據包頭部第一個字節的最高有效位(themostsignificant
bit)總是被忽略。使用A.1中的數據包類型,則type可以使用下面的代碼編碼到輸出數據
包的那幾個最高有效位中:
p->dat[0]=sl_compress_tcp(p,comp);
在接收方的解碼為
if(p->dat[0]&0x80)
type=TYPE_COMPRESSED_TCP;
elseif(p->dat[0]>=0x70){
type=TYPE_UNCOMPRESSED_TCP;
p->dat[0]&=~0x30;
}else
type=TYPE_IP;
status=sl_uncompress_tcp(p,type,comp);
B.2向后兼容SLIP服務器
參考文獻[12]中描述的SLIP不包括能用來自動協商頭部壓縮的機制。答應這種SLIP
的用戶使用頭部壓縮是件好事,但是,假如這兩種類型的SLIP用戶使用同一個服務器,手
動配置每一個連接的兩端使之能夠使用本壓縮將是一件很煩人的事情。下面的過程用來避免
手動配置。
因為有兩種類型的撥號客戶端(dial-inclients)即使用壓縮的和不使用壓縮的,而同
一個服務器要為這兩種類型的客戶端服務。很明顯服務器將要重新配置每一個新的客戶端會
話,但客戶端就是更改也很少改變配置。假如必須手動配置,它應該在不經常改動的一方進
行——即客戶端。這就暗示著服務器應該以某種方式從客戶端得知是否使用頭部壓縮。假設
由于對稱性(也就是說,假如使用則應該在兩個方向上都使用壓縮)服務器可以通過來自客
戶端的壓縮數據包來暗示著它可以向客戶端發送壓縮數據包。這就導致了下面的算法:
每一條鏈路用兩位來控制頭部壓縮:allowed和on。假如設置了on位,發送的是壓縮數
據包,否則不發送壓縮數據包。假如設置了allowed位,可以接收壓縮數據包,假如收到的
UNCOMPRESSED_TCP數據包沒有設置on位,則設置on位(注49)。假如收到壓縮數據包并且
沒有設置allowed位,數據包將被忽略。
客戶端配置為兩位都設置(假如設置了on則總是設置allowed),服務器開始每一個會話
時設置allowed位,清除on位。客戶端來的第一個壓縮數據包(一定是一個
UNCOMPRESSED_TCP數據包)將為服務器打開壓縮功能。
注49:因為參考文獻[12]中的幀不包括錯誤檢測,必須注重千萬別把服務器的壓縮開關設
置為false。在壓縮被使能之前,UNCOMPRESSED_TCP數據包應該檢查連續性(例如,IP檢驗
和的正確性)。COMPRESSED_TCP數據包的到達不應該用來使能壓縮。
Jacobson[Page44]
RFC1144CompressingTCP/IPHeadersFebruary1990
C更主動的壓縮
如3.2.2中指出的那樣,壓縮頭部中存在很輕易檢測到的特征,表明可以進行進一步
的壓縮。這有價值么?
壓縮后的數據包的頭部僅有7個比特(注50)。幀必須至少為一個比特(已表明“type”),
更可能的情況是2到3個字節。在大多數有趣的場合,將至少有一個字節的數據。最后,端
到端的檢查——TCPchecksum——必須未加修改地加以傳遞(注51)。
幀,數據和checksum將保留即使頭部完全地壓縮,所以數據包的平均大小最多從4個字
節降到三個字節加一個比特——大約把延遲性能提高25%(注52)。這個提高可能看起來很
大,在一條2400bps的鏈路上,它意味著擊鍵回顯的響應時間為25毫秒而不是29毫秒。
在當前人類進化階段,這個差別根本無法感覺出來(detectable)。
但是,作者坦率地承認把該壓縮方案曲解為一種非常非凡情形數據獲取問題:我們有漂
浮與200KV之上的工具和控制包,通過遙測系統與地面通信。由于很多原因(多路復用通信,
流水線操作,錯誤恢復,精心檢測過的實現的可用性等等),跟這個包使用TCP/IP通信是很方
便的。但是,因為遙感鏈路的基本用處就是獲取數據,設計成為上傳信道<下傳信道容量的
0.5%。為了滿足應用延遲性要求,數據包為100個字節,并且,因為TCP每隔一個數據包確
認一次,相關的上傳信道的確認(ack)帶寬為a/200,其中a為確認數據包的總長度。假如
使用本文檔的方案,最小的ack為4個字節,意味著上傳鏈路的帶寬未下傳鏈路帶寬的2%。
注50:已經對幾百萬個固定流量負載的數據包進行測試(也就是說,統計了以年之內從我家
到工作地之間的流量)表明80%的數據包使用兩種非凡情形編碼之一,這樣,唯一的頭部就是
changemask。
注51.假如某人試圖向你出售壓縮TCP檢驗和的方案,一定不要買。(Justsay“no”)。某
些可憐的傻瓜仍然要繼續傷心的經歷來展示端到端的討論是真理。更壞的是,因為這個傻瓜
正在搗亂你的端到端的錯誤檢查,你可能為這個教訓付出學費,他們一點也不聰明。得到兩
個字節時間的延遲但丟失內心的平靜有什么好處?
注52.再次注重在討論時我們必須關心交互延遲時間:批量數據傳輸的性能主要由發送數
據的時間決定,包含成百上千個字節數據的數據報文中三個字節和四個字節頭部的差異實際
上沒有差別。
Jacobson[Page45]
RFC1144CompressingTCP/IPHeadersFebruary1990
這是不可能的,所以我們使用注15中描述的方案:假如幀中第一個比特位為1,意味著壓縮
數據包頭部與上一次的相同。否則接下去的兩個比特位將給出3.2中描述的類型之一。因為
鏈路有很多轉發錯誤(forwarderror),流量僅做一次跳躍(hop),TCPchecksum已經被
相同頭部的數據包類型壓縮出去(慚愧!)(注53),所以這些數據包的頭部總長度就是一個
比特。在幾個月的操作中,超個99%的40個字節的TCP/IP頭部都壓縮降為一個比特(注54)。
D安全方面的考慮
本文檔不討論安全方面的問題。
E作者地址
Address:VanJacobson
RealTimeSystemsGroup
MailStop46A
LawrenceBerkeleyLaboratory
Berkeley,CA94720
Phone:Useemail(authorignoreshisphone)
EMail:van@helios.ee.lbl.gov
注53:檢驗和在解壓器方已經重新產生,當然,“丟棄”(“toss”)邏輯被設計成更加主動以
防止錯誤的傳播。
注54:我們已經聽到建議,認為出于實時的需要要求放棄TCP/IP而贊成使用具有更小頭部
的輕權協議(light-weight'protocol)。很難想象頭部平均只有一個比特的協議。
新聞熱點
疑難解答