[英]Large TCP kernel buffering cause application to fail on FIN
我想重新打開之前被錯誤歸類為網絡工程問題的問題,經過更多測試,我認為這對程序員來說是一個真正的問題。
因此,我的應用程序從服務器流式傳輸 mp3 文件。 我無法修改服務器。 客戶端根據需要從服務器讀取數據(160kbits/s)並將其饋送到 DAC。 讓我們使用一個 3.5MB 的文件。
當服務器發送完最后一個字節后,它關閉連接,所以它發送一個 FIN,這似乎是正常的做法。
問題是內核,尤其是在 Windows 上,似乎存儲了 1 到 3 MB 的數據,我假設 TCP 窗口大小已經完全打開。
幾秒鍾后,服務器發送了整個 3.5 MB 和大約 3MB 的內核緩沖區。 此時服務器已經發送了 FIN,它在適當的時候是 ACK。
從客戶端的角度來看,它繼續以 20kB 的塊讀取數據,並將在接下來的 3MB/20 ~= 150 秒內讀取數據,然后再看到 EOF。
同時,服務器處於 FIN_WAIT_2(而不是我最初寫的 TIME_WAIT,感謝Steffen糾正我。現在,像 Windows 這樣的操作系統似乎有一個半關閉的套接字計時器,它從發送 FIN 開始並且小到 120s,無論實際的 TCPWindowsize BTW)。 當然在 120s 之后它認為它應該已經收到了客戶端的 FIN,所以它發送了一個 RST。 該 RST 導致所有客戶端的內核緩沖區被丟棄並且應用程序失敗。
由於需要代碼,這里是:
int sock = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in addr;
addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
addr.sin_family = AF_INET;
addr.sin_port = htons(80);
int res = connect(sock, (const struct sockaddr*) & addr, sizeof(addr));
char* get = "GET /data-3 HTTP/1.0\n\r"
"User-Agent: mine\n\r"
"Host: localhost\n\r"
"Connection: close\n\r"
"\n\r\n\r";
bytes = send(sock, get, strlen(get), 0);
printf("send %d\n", bytes);
char *buf = malloc(20000);
while (1) {
int n = recv(sock, buf, 20000, 0);
if (n == 0) {
printf(“normal eof at %d”, bytes);
close(sock);
break;
}
if (n < 0) {
printf(“error at %d”, bytes);
exit(1);
}
bytes += n;
Sleep(n*1000/(160000/8));
}
free(buf);
closesocket(sock);
它可以用任何 HTTP 服務器進行測試。
我知道有一些解決方案可以通過在服務器關閉套接字之前與服務器握手(但服務器只是一個 HTTP 服務器)但是當緩沖區大於使用它們的時間時,內核級別的緩沖會導致系統性故障。
客戶端在吸收數據方面是完全實時的。 擁有更大的客戶端緩沖區或根本沒有緩沖區不會改變這個問題,這對我來說似乎是系統設計缺陷,除非有可能在應用程序級別而不是整個操作系統控制內核緩沖區,或者檢測到 FIN 接收recv() 的 EOF 之前的客戶端級別。 我試圖改變 SO_RCVBUF 但它似乎並沒有從邏輯上影響這種內核緩沖級別。
這是一次成功和一次失敗的交換的截圖
success
3684 381.383533 192.168.6.15 192.168.6.194 TCP 54 [TCP Retransmission] 9000 → 52422 [FIN, ACK] Seq=9305427 Ack=54 Win=262656 Len=0
3685 381.387417 192.168.6.194 192.168.6.15 TCP 60 52422 → 9000 [ACK] Seq=54 Ack=9305428 Win=131328 Len=0
3686 381.387417 192.168.6.194 192.168.6.15 TCP 60 52422 → 9000 [FIN, ACK] Seq=54 Ack=9305428 Win=131328 Len=0
3687 381.387526 192.168.6.15 192.168.6.194 TCP 54 9000 → 52422 [ACK] Seq=9305428 Ack=55 Win=262656 Len=0
failed
5375 508.721495 192.168.6.15 192.168.6.194 TCP 54 [TCP Retransmission] 9000 → 52436 [FIN, ACK] Seq=5584802 Ack=54 Win=262656 Len=0
5376 508.724054 192.168.6.194 192.168.6.15 TCP 60 52436 → 9000 [ACK] Seq=54 Ack=5584803 Win=961024 Len=0
6039 628.728483 192.168.6.15 192.168.6.194 TCP 54 9000 → 52436 [RST, ACK] Seq=5584803 Ack=54 Win=0 Len=0
這是我認為的原因,非常感謝 Steffen 讓我走上正軌。
而已。 這可以用幾行代碼和每個 HTTP 服務器重現。 這可以爭論,但我認為這是一個系統性的操作系統問題。 現在,似乎可行的解決方案是將客戶端的接收緩沖區 (SO_RCVBUF) 強制降低到較低級別,這樣服務器幾乎沒有機會發送所有數據,並且數據在客戶端的內核緩沖區中停留的時間太長。 請注意,如果緩沖區為 20kB 並且客戶端以 1B/s 的速度使用它,這仍然會發生……因此我將其稱為系統故障。 現在我同意有些人會將其視為應用程序問題
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.