TCP/IP

TCP 傻瓜視窗症候群 (Silly Window Syndrome, SWS)

TCP 流量控制 (TCP Flow Control) 一文,
介紹了 滑動視窗 (Sliding Window) 的概念,
說明透過 回饋 (feedback) 機制,調整視窗大小,改善傳輸效率。
 
然而某些狀況下,滑動視窗的運作,會產生嚴重的問題,
稱為 — — 傻瓜視窗症候群 (Silly Window Syndrome, SWS)
本篇即是介紹這種症狀 😂,並說明其解法。

 


 

傻瓜視窗症候群
(Silly Window Syndrome, SWS)

 
一家餐廳要賺錢,換桌率 很重要,
最賭爛就是,客人進食 或 廚師上菜 有夠慢 🙄,
更可怕的是兩者同時發生…
turnover
 
維護應用層資料的 — — 傳輸控制協定 (Transmission Control Protocol, TCP)
由於 發送者 (Sender) 與 接收者 (Recipient) 傳輸、讀取的速率不相等,
接收端 會將資料暫存在 接收緩衝區 (Receiving Buffers)
並等待 應用層讀取後 (消化),再從 接收緩衝區 中清除。
 

餐桌,就如 接收緩衝區,

客人進食,為 應用層讀取 (消化),

廚師做菜,則為 傳送端 產生資料。

 
這種「接收端 之 應用程序 讀取 (消化)」 or 「傳送端 產生資料」 速度很慢,
造成的網路效率低弱,就稱為 — — 傻瓜視窗症候群 (Silly Window Syndrome, SWS),又稱 糊塗窗口綜合症。
 


 

傳送端 SWS

典型的 傳送端 SWS 為:
「傳送端 應用程式 產生資料速度很慢」,
最極端的狀況就是,一次只傳送 1 byte 的資料..
如許多 Telnet 鍵盤操作,會產生 1 byte 的資料並馬上送出。
 
可別忘記,TCP 每次 發送 與 接收 單位為: TCP 區段 (TCP Segment)
光加上 TCP 表頭 (Header) 可能就 40 bytes 了,
加上資料 1 byte,總共 41 bytes,
這種沒效率的狀況,又稱 小封包問題 (small packet problem)
encapsulation
就像花了三千買禮物給一位學妹,
只換得她一聲:「謝謝你 😍 你對我就像哥哥一樣好」。
 
兄弟,不值得啊… (過來人😭)
 
 

Nagle 演算法 (Nagle’s Algorithm)

TCP 僅是 應用層使用的服務,
因此,並沒有治本的方法:改善 傳送端 耗弱的資料產速。
 
John Nagle 提出了一個折衷方案:

起碼,盡量別讓那些低效的區段,
浪費頻寬,害人害己。

 
最簡單的 Nagle 演算法 (實務上,有非常多實作變化):

  1. 傳送端的應用程式的 第一筆資料,
    即使只有 1 byte,也照樣傳送
  2. 之後的新資料,皆至於 發送緩衝區 中累積、等待,
    直到 資料已累積到 最大區段長度 (Maximum Segment Size, MSS)
    或 接收端 回應 確認 (ACK),再將區段送出。

 
大致長這樣:

public Nagle(ApplicationData data) {

    策略 strategy;

    if (data.is第一筆資料())
        strategy = new 策略1();
    else
        strategy = new 策略2();

    strategy.處理資料(data);
}
public class 策略1 implements 策略 {

    @Override
    public void 處理資料(ApplicationData data) {
        存入發送緩衝區(data);
        送出區段(data);
    }
}
public class 策略2 implements 策略 {

     @Override
    public void 處理資料(ApplicationData data) {

        存入發送緩衝區(data);

        if (data.length >= 最大區段長度 || is接收端回應確認ACK())
            送出區段(data);
    }
}

 
最大區段長度 (Maximum Segment Size, MSS),雙方會在連線建立時決定,
且這條連線運作期間皆不會改變,若其中一方未定義,則使用預設值: 536 bytes
可別被他的名字唬了,它定義的是所能接收資料』的最大長度,而非區段!
 
Nagle 演算法,簡單又實用,
同時考慮到 應用程式資料產速 與 接收端回應速度,
然而,許多 TCP 實作為了減輕負載與傳輸量,使用 延遲確認 (Delayed ACK)
會使資料產速較低的傳送端,癡癡地等… 約 100~200 毫秒 (ms)
 
因此,若確保資料產速穩定 (尤其是 Web Server),
建議停用 Nagle 演算法
以避免 200 毫秒 延遲 (NGINX 預設即為關閉)。
 
大部分關閉方式,皆使用 TCP_NODELAY 這個選項,
以 C Socket 關閉方式為例:

 setsockopt(sockfd, IPPROTO_TCP, TCP_NODELAY, &optval, sizeof(optval));

 
Java Socket:

public void setTcpNoDelay(boolean on) throws SocketException {
    if (isClosed())
        throw new SocketException("Socket is closed");
    getImpl().setOption(SocketOptions.TCP_NODELAY, Boolean.valueOf(on));
}

 
NGINX:

http {
    tcp_nodelay on;
	
	...略
}

 


 

接收端 SWS

接收端 SWS 為:
「接收端 應用程式消耗資料太慢」,
使 接收視窗大小 (rwnd) 飽和,
而間接造成 小封包問題 (small packet problem)
 
常見的兩種解決方案:

  1. Clark 方案 (Clark’s Solution)
  2. 延遲確認 (Delayed ACK)

 
 

Clark 方案
(Clark’s Solution)

Clark 方案 其實已在 TCP 流量控制 (TCP Flow Control) 一文出現過,
接收端 接收到資料後,便送出 接收視窗大小 (rwnd) = 0 的回應,
直到 接收緩衝區 能容納一個 最大區段長度 (Maximum Segment Size, MSS) (或自定義大小),
再回應給 傳送端 != 0 的 rwnd。
 
傳送端 收到 rwnd = 0 時,會停止傳送 (而非收縮 發送視窗),
直到 接收端 送出 != 0 的 rwnd。
 
 

延遲確認 (Delayed ACK)

延遲確認 (Delayed ACK)
即所謂的 拖延戰術 😂。
 
當 接收端 收到一區段時,不立即回應,
而是『盡量』等到 接收緩衝區 有足夠空間後,再送出回應,
接收視窗大小 (rwnd) 自然就得到增加的機會。
 
盡量』 的延遲回應時間,
通常是 100~200 毫秒 (ms) ,至多為 500 毫秒 (ms),因設定而異。
 
因此,接收端 很有可能,
將多個回應與資料結合為一,並挾帶 (piggyback) 確認 (ACK),
如此便能減少網路中的封包數量,降低兩端主機的負載、傳輸量。
(別忘記: TCP 為 全雙工 (Full-Duplex, FDX))

 
聽起來好棒棒?
然而,除了上方提過與 Nagle 演算法 的共用問題,
某些時候,甚至會讓 傳送端 誤解,使其 重送 (retransmission) 沒收到確認的區段…
若在不當時機,使用 延遲確認 (Delayed ACK) 只會弊大於利。
 
儘管如此:

若 接收端 收到的區段順序正確,
且先前的區段皆已回應 + 已沒有資料要傳送,
仍應使用 延遲確認,直到有新的區段抵達 [註1] 或 超過『盡量』的時限 。

 
否則,皆應立即傳送 ACK 區段,
預防不必要的延遲與重送。
 
[註1]:
接收端 在任何時間下,
不該有兩個以上,正確順序的區段 未被回應。
 
 

作者: 鄭中勝
喜愛音樂,但不知為何總在打程式 ? 期許能重新審視、整理自身所學,幫助有需要的人。

在《TCP 傻瓜視窗症候群 (Silly Window Syndrome, SWS)》中有 2 則留言

  1. 之後的新資料,皆至於 發送緩出區 中累積、等待

    上面那行的發送緩”出”區 是 發送緩”衝”區吧 !?

發表迴響