轉(zhuǎn)載自:http://blog.csdn.net/leehark/article/details/7654183
PseudoTcp - 建立UDP之上的TCP(1):連接和關(guān)閉
mail:lihe21327 [at] gmail [dot] com
最近閱讀了Libjingle的PseudoTcp.LibJingle很是下功夫做P2P了,在UDP之上做了可靠的傳輸協(xié)議PseudoTcp.
了解PseudoTcp之前,我們需要了解一些TCP的特性。
根據(jù)《TCP/IP詳解》卷1,可以總結(jié)如下:
1.TCP是面相連接的,他需要3次握手和4次終止過程。
2.TCP支持Nangle算法和經(jīng)受時(shí)延的確認(rèn)來控制報(bào)文段數(shù)目。
3.TCP含有滑動窗口來控制接收方的流量。
4.TCP支持超時(shí)與重傳。
5.TCP支持擁塞避免算法。
6.TCP具有堅(jiān)持定時(shí)器和保活定時(shí)器
7.TCP要支持路徑MTU發(fā)現(xiàn)、長肥管道、時(shí)間戳選項(xiàng)。
那我們一起剖析一下PseudoTcp實(shí)現(xiàn)了上面哪些功能。
PseudoTcp(以后簡稱PTCP吧)的格式:

通過結(jié)構(gòu)Segment 定義此報(bào)文頭部:
struct Segment {
uint32 conv, seq, ack;
uint8 flags;
uint16 wnd;
const char * data;
uint32 len;
uint32 tsval, tsecr;
};
各個(gè)字段的含義如下:
A)Conversation Number : 流水號,是用來標(biāo)識此次連接。即TCP里所謂的本地IP:本地端口-遠(yuǎn)程IP:遠(yuǎn)程端口,4組合為一個(gè)流水號。因?yàn)?/span>PTCP是UDP之上的(當(dāng)然也可以是其他協(xié)議之上),如果socket沒有綁定到本地端口,可能獲取的不是需要的數(shù)據(jù)。如果獲取的Conv Number不一樣,接收方會發(fā)送RST(不過PTCP里已經(jīng)注釋了此段代碼)。此外,PTCP并不關(guān)心他的傳輸層是有一個(gè)連接還是多個(gè)連接,她只關(guān)心CONV Number是否一致。
B)Seq Number:32位序號,即此數(shù)據(jù)表示的序列,不一定從0開始
C)Ack Number:32確認(rèn)序列號。確認(rèn)已經(jīng)獲取到的數(shù)據(jù)序列加1,即下一個(gè)需要接受的序列號。
D)Control:現(xiàn)未使用
E)URG:緊急指針,1bit
F)ACK:確認(rèn)序列號有效,1bit
G)PSH:接收方盡可能將這個(gè)報(bào)文送給應(yīng)用層,1bit
H)RST:重置連接
I)FIN:表示發(fā)送完所有數(shù)據(jù),斷開連接。
J)Window:窗口大小
K)TimeStamp Sending:本端發(fā)送包時(shí)間(采用以本端的時(shí)間計(jì)算方式)
L)TimeStamp Receiving:對方最近接收包時(shí)間(采用以對方的時(shí)間計(jì)算方式)
M)Data:數(shù)據(jù)
注:上面的E-I的含義,在實(shí)現(xiàn)上完全不同。下面會提到。
PTCP的狀態(tài):
TCP_LISTEN:監(jiān)聽
TCP_SYN_SENT:SYN包已經(jīng)發(fā)送
TCP_SYN_RECEIVED:已經(jīng)接收SYN包
TCP_ESTABLISHED:已經(jīng)建立連接
TCP_CLOSED:已經(jīng)關(guān)閉連接
PTCP的狀態(tài)轉(zhuǎn)移相對TCP來說簡單多了,TCP如下:

3路握手:
TCP建議連接時(shí)需要來回總共有3個(gè)TCP包來做握手,即
A)SYN[A]:
B)ACK[B],SYN[A+1]
C)ACK[B+1]
PTCP握手過程如下:
當(dāng)開始時(shí)兩端都處于TCP_LISTEN狀態(tài)。
當(dāng)C端發(fā)送SYN包到S端時(shí),C端處于TCP_SYN_SENT狀態(tài)
當(dāng)S端處于TCP_LISTEN時(shí)收到SYN包,S端轉(zhuǎn)為TCP_SYN_RECEIVED
當(dāng)S端處于TCP_SYN_RECEIVED時(shí),發(fā)送ACK時(shí)狀態(tài)不變
當(dāng)C端處于TCP_SYN_SENT時(shí),收到ACK,則轉(zhuǎn)為TCP_ESTABLISHED
當(dāng)S端處于TCP_SYN_RECEIVED,收到非控制包時(shí)轉(zhuǎn)為TCP_ESTABLISHED
這里解釋一下控制包:上面PTCP協(xié)議頭結(jié)構(gòu)里的第13個(gè)字節(jié)處(即URG,ACK等在的字節(jié))其實(shí)只取3個(gè)值之一:
0:數(shù)據(jù)包
0x02:CTL包,當(dāng)握手時(shí)使用。
0x04:RST包。現(xiàn)在發(fā)此段包的代碼被注釋掉。
所以控制包,指的是握手時(shí)才會發(fā)送,握手完之后都屬于數(shù)據(jù)包。
可見PTCP的握手過程和TCP的握手過程有微小的差異。當(dāng)C端轉(zhuǎn)為TCP_ESTABLISHED后,等到有數(shù)據(jù)才會發(fā)送給S端(而不是立即),S端直到只有等到有數(shù)據(jù)的包時(shí),才把狀態(tài)改為TCP_ESTABLISHED。而TCP是,如果沒有數(shù)據(jù)會立即發(fā)送,S端只要收到ACK就改為ESTABLISHED狀態(tài)。
連接建立時(shí)超時(shí):
當(dāng)C端發(fā)送完SYN包之后,一直沒有響應(yīng)時(shí),沒過3S,C端會發(fā)送一個(gè)SYN請求。直到發(fā)送30次之后,還沒有收到回包,則停止發(fā)送并關(guān)閉連接。即等待時(shí)間為3*30=90S,而大多數(shù)TCP實(shí)現(xiàn)的超時(shí)時(shí)間為75S。
最大報(bào)文段長度(MSS):
TCP默認(rèn)MSS為536,即取MTU為576( X.25 Networks),包括20個(gè)字節(jié)的IP頭和20個(gè)字節(jié)的TCP頭。
對于PTCP,默認(rèn)MTU取為65536,即UDP容納的最大長度,那么MSS取值為65536-116。
116的計(jì)算來自:
PACKET_OVERHEAD = HEADER_SIZE + UDP_HEADER_SIZE + IP_HEADER_SIZE + JINGLE_HEADER_SIZE
JINGLE_HEADER_SIZE用于Relay包,具體需要了解STUN協(xié)議和TURN協(xié)議。
MTU的發(fā)現(xiàn)完全由調(diào)用方來決定,PTCP只提供了接口來更新MTU。
在Libjingle里,對于win32,枚舉下面數(shù)組PACKET_MAXIMUMS,然后通過WinPing來發(fā)現(xiàn)此次PTCP連接的MTU。如果沒有獲取到MTU,默認(rèn)取值為1280(此時(shí)MSS為1280-116=1164)。
為什么MTU默認(rèn)取值為1280呢,有什么數(shù)據(jù)依據(jù)呢?
// Standard MTUs
const uint16 PACKET_MAXIMUMS[] = {
65535, // Theoretical maximum, Hyperchannel
32000, // Nothing
17914, // 16Mb IBM Token Ring
8166, // IEEE 802.4
//4464, // IEEE 802.5 (4Mb max)
4352, // FDDI
//2048, // Wideband Network
2002, // IEEE 802.5 (4Mb recommended)
//1536, // Expermental Ethernet Networks
//1500, // Ethernet, Point-to-Point (default)
1492, // IEEE 802.3
1006, // SLIP, ARPANET
//576, // X.25 Networks
//544, // DEC IP Portal
//512, // NETBIOS
508, // IEEE 802/Source-Rt Bridge, ARCNET
296, // Point-to-Point (low delay)
//68, // Official minimum
0, // End of list marker
};
PTCP的關(guān)閉。
TCP的關(guān)閉時(shí)由4步驟完成。

1. FIN[A]
2. ACK[A+1]
3. FIN[B]
4. ACK[B+1]
然而,有時(shí)候可以做到3步,即上面的2,3步可以合成在一個(gè)TCP包里發(fā)送。對于上面只完成前兩步的狀態(tài)成為半關(guān)閉狀態(tài),此時(shí)發(fā)送FIN[A]的端表示自己不再有多余的數(shù)據(jù)要發(fā)送,但還能接收數(shù)據(jù)。
當(dāng)調(diào)用PTCP的Close方法時(shí),此端丟棄對方發(fā)過來的數(shù)據(jù),只做應(yīng)答,即只發(fā)送對方發(fā)來數(shù)據(jù)的ACK。并且等到此方數(shù)據(jù)都發(fā)送完,需關(guān)閉整個(gè)連接。以此看來,PTCP沒有半關(guān)閉狀態(tài),并且PTCP也只是用來支持P2P用的,不需要半關(guān)閉狀態(tài)。
2MSL等待狀態(tài)
MSL是指一個(gè)數(shù)據(jù)包在網(wǎng)絡(luò)上存在的最長時(shí)間。而2MSL是指當(dāng)主動關(guān)閉方發(fā)送被動關(guān)閉方發(fā)送的FIN對應(yīng)的ACK時(shí),如果這個(gè)ACK被丟失了,則被動關(guān)閉方超時(shí)重發(fā)最后的FIN,此時(shí)主動關(guān)閉方再次發(fā)ACK,當(dāng)主動關(guān)閉方發(fā)送第一個(gè)FIN對應(yīng)的ACK到,拿到最后的FIN之間的時(shí)間段最長為2MSL。那為什么主動關(guān)閉方處于2MSL等待狀態(tài)呢?是因?yàn)椋绻鲃雨P(guān)閉方發(fā)送了第一個(gè)FIN對應(yīng)的ACK之后,放棄了此連接,那么下一個(gè)新建的連接有可能復(fù)用此連接(即同一個(gè)插口對),此時(shí)新建的連接有可能因?yàn)樯弦粋€(gè)丟失的ACK,而收到重發(fā)的FIN,導(dǎo)致連接被關(guān)閉。
然而PTCP不存在半關(guān)閉的概念,故2MSL等待狀態(tài)也隨之沒有。此外,PTCP是用來做P2P的,兩者之間的連接時(shí)雙方協(xié)商定義的,并且PTCP在頭部給予了Conversation number的概念,以便協(xié)商中防止產(chǎn)生同一個(gè)連接的產(chǎn)生。
復(fù)位報(bào)文段
當(dāng)TCP存在如下情況時(shí)會產(chǎn)生復(fù)位報(bào)文段。
A.當(dāng)服務(wù)器沒有開啟指定的連接端口時(shí),對于UDP來說產(chǎn)生端口不可達(dá),而TCP產(chǎn)生RST報(bào)文
B.當(dāng)一端產(chǎn)生異常終止時(shí),會發(fā)送RST報(bào)文。即當(dāng)設(shè)置SO_LINGER套接口選項(xiàng)時(shí),close套接口會產(chǎn)生RST報(bào)文。
C.檢測到半打開連接,當(dāng)接收方異常終止重啟后接收對方在舊的連接上傳過來的數(shù)據(jù)時(shí),會發(fā)送RST報(bào)文。
對于PTCP來說,現(xiàn)在沒有一個(gè)地方會發(fā)送RST報(bào)文(之前有過的被注釋了,當(dāng)收到不是當(dāng)前的CONV時(shí)會發(fā)送RST),但如果一旦收到了RST報(bào)文,則立即關(guān)閉此連接。
同時(shí)打開
TCP的同時(shí)打開情景是如下:當(dāng)C用端口7777連接S的端口8888,同時(shí)S用端口8888連接C的端口7777,此時(shí)包的順序如下:
1) SYN[A]
2) SYN[B]
3) ACK[A+1]
4) ACK[B+1]
顯然上面的握手從3次變?yōu)?/span>4次。
PTCP的同時(shí)打開,也類似如上,由4個(gè)包來完成握手。
1) C端發(fā)送SYN時(shí),狀態(tài)變?yōu)?/span>TCP_SYN_SENT
2) 同時(shí)S端發(fā)送SYN,S和C的狀態(tài)此時(shí)都為TCP_SYN_SENT
3) C,S同時(shí)向?qū)Ψ剑梢圆皇峭瑫r(shí))發(fā)送ACK,此時(shí)C,S狀態(tài)都變?yōu)?/span>TCP_ESTABLISHED。
同時(shí)關(guān)閉
TCP是支持C,S同時(shí)關(guān)閉的。
1)C,S同時(shí)發(fā)送FIN,狀態(tài)變?yōu)?/span>FIN_WAIT_1
2)C,S同時(shí)收到FIN,并發(fā)送ACK,狀態(tài)變?yōu)?/span>CLOSING
3)C,S同時(shí)收到ACK,兩個(gè)狀態(tài)都變?yōu)?/span>TIME_WAIT
對于PTCP,沒有像TCP,不存在FIN包,顯然對關(guān)閉狀態(tài)的維護(hù)不是很完美。也同樣,看不到同時(shí)關(guān)閉的情形,這些交給底層傳輸層(UDP)等之類來完成,由調(diào)用方來維護(hù)狀態(tài)。
為什么PTCP沒有提供FIN報(bào)文以及對應(yīng)的狀態(tài)呢?
TCP選項(xiàng)
TCP保留40個(gè)字節(jié)傳輸其他選項(xiàng),主要有窗口擴(kuò)大因子,時(shí)間戳選項(xiàng),MSS長度等。
PTCP也通過一種方式來增加其他選項(xiàng),如MSS和窗口擴(kuò)大因子。當(dāng)傳輸?shù)氖强刂瓢矣袛?shù)據(jù)內(nèi)容時(shí),如果第一個(gè)字節(jié)為CTL_CONNECT,則會調(diào)用方法parseOptions來解析是否含有MSS,窗口擴(kuò)大因子等等選項(xiàng)。這些選項(xiàng)的實(shí)現(xiàn)細(xì)節(jié)后續(xù)會提及(時(shí)間戳選項(xiàng)直接在報(bào)文頭里有,固這個(gè)選項(xiàng)很重要,后續(xù)會提到此選項(xiàng)的作用)。