對互動資料流的處理
TCP包含兩類資料流,互動資料流和成塊資料流。互動資料流的特點是每個報文資料位元組數比較小,大部分是10位元組一下,而成塊資料流的特點是大部分報文是滿長度的,一般能達到MSS。
本文先介紹一些TCP和PTCP對互動資料流的處理。
互動式輸入
Rlogin是典型的互動資料流應用,每一按鍵都會產生資料分組,使用戶端傳輸一個報文,接連總共產生4個報文:
a.C傳輸互動按鍵資料
b.S確認C的資料
c.S回顯C的按鍵
d.C確認S的回顯
上面的報文b,c可能會同時包含在一個報文段。而對於TCP報文-有40個位元組的頭部的協議報文來說每次只傳輸一個位元組是個極大的浪費,此外Rlogin這類應用會在短時間內按N個字元,按如上的方式,至少要傳輸3*N個報文。
經受時延的確認
經受時延的確認考慮了時間有關的細微之處,對於互動類應用,短時間內會產生多個報文。對於TCP,當接收資料時,並不立即發送確認,而先緩衝,延遲發送,以便在短時間如果有該方向的資料需要發送,則一同發送,這樣能減少ACK報文的個數,提高報文的利用率。TCP通常等待200ms後發送ACK。
對於PTCP來說,也支援延時確認,預設延時時間長度為100ms,可以通過選項OPT_ACKDELAY更改延時時間。不另外,如果出現連續兩個不含資料的ACK需要發送,則不會等到100ms,直接會發送ACK報文。PTCP發送ACK的時機如下:
A. 和SEND資料一起發送
B. 等到逾時(100ms後沒有資料時)時發送
C. 出錯時發送(如發現對方傳來的資料和預期的不一致,或者ACK被丟失)
雖然PTCP是等到100ms後發送ACK,但沒有提供任何定時器,只提供了下次需要被提醒的時間(通過方法GetNextClock),然後由業務層來實現定時器並通知到時(通過方法NotifyClock)。這樣,業務層就會有靈活的方式設定定時器,比如通過訊息迴圈,等待事件,完成連接埠等等。
Nagle演算法
Nagle演算法是為了避免在廣域網路上出現大量的TCP小分組報文段。該演算法要求一個TCP串連上最多隻有一個未被確認的小分組。當已經發送的一個分組沒有被確認前,該演算法積累所有需要發送的資料,等到未被確認的分組確認了,一同發送,這樣在短時間內出現的小分組合并成一個報文發送,提高了報文的利用率。這個演算法是自適應的,得到確認越快,則發送頻率越高。虛擬碼如下:
if there is new data to send
if the window size >= MSS and available data is >= MSS
send complete MSS segment now
else
if there is unconfirmed data still in the pipe
enqueue data in the buffer until an acknowledge is received
else
send data immediately
end if
end if
end if
PTCP也支援Nagle演算法,可以通過選項OPT_NODELAY開啟或者關閉。Nagle演算法的實現比較簡單,當嘗試發送資料時,發現如果有未確認的資料且等待發送的資料長度小於MSS,則延遲發送,如下:
void PseudoTcp::attemptSend(SendFlags sflags) { ...... // Nagle's algorithm. // If there is data already in-flight, and we haven't a full segment of // data ready to send then hold off until we get more to send, or the // in-flight data is acknowledged. if (m_use_nagling && (m_snd_nxt > m_snd_una) && (nAvailable < m_mss)) { return; } ...... }
視窗大小通告
TCP和PTCP都通過頭部的window欄位通告接收緩衝區的可用視窗大小。當用戶端收到伺服器的資料,並有等待發送的資料時(開啟Nagle演算法時會經常出現此情況),通告給伺服器的視窗大小總是小於接收緩衝區的大小,是因為,應用程式層還沒有拿取剛從服務擷取的資料之前,就會嘗試發送被緩衝的資料。
PTCP的實現如下:
當PTCP接收對方發送的資料時會調用NofifyPacket->parse->process,在Process先調用attemptSend發送緩衝的資料,然後通知應用程式層有可讀資料。
bool PseudoTcp::process(Segment& seg) {...... attemptSend(sflags); // If we have new data, notify the user if (bNewData && m_bReadEnable) { m_bReadEnable = false; if (m_notify) { m_notify->OnTcpReadable(this); } //notify(evRead); } return true;}