一:背景
1. 講故事
這段時間分析了幾個和網路故障有關的.NET程式
之後,真的越來越體會到計算機基礎課的重要,比如 計算機網路
課,如果沒有對 tcpip協議
的深刻理解,解決這些問題真的很難,因為你只能在高層做黑盒測試,你無法看到 tcp 層面的握手和psh通訊。
這篇我們透過兩個小例子來理解一下 tcp 協議在故障分析中的作用。
二:tcp協議的兩個小例子
1. 程式突然大量超時
這個故事起源於一位朋友遇到的問題:
起初程式跑的一直都是好好的,但會有偶發性突然無法訪問,奇怪的是在故障時手工訪問域名時又是正常的,後面又莫名奇怪的好了,請問這是怎麼回事?
這種問題朋友雖然抓了dump,但在dump中尋找問題很難,因為大機率是在 http 通訊中出了問題,需要用類似 wireshark 去做流量監控,最後發現的原因是代理伺服器偶發的抽風,導致 C# 的 HttpClient 無法訪問。
為了方便演示,這裡用一段簡單的測試程式碼。
- WebAPI 程式碼
建立一個 WebApi 骨架程式碼,然後部署 Windows 虛擬機器上。
[HttpGet]
public IEnumerable<WeatherForecast> Get()
{
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateTime.Now.AddDays(index),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = Summaries[Random.Shared.Next(Summaries.Length)]
})
.ToArray();
}
並且在 appsetttings.json 中配置對外埠為 80。
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"Kestrel": {
"Endpoints": {
"Http": {
"Url": "http://0.0.0.0:80"
}
}
}
}
- Client 的 HttpClient
這裡面我用 hosts 做了虛擬機器 192.168.25.133 myproxy.com
的對映,然後透過域名的方式訪問。
internal class Program
{
public static HttpClient client = new HttpClient(new HttpClientHandler()
{
Proxy = new WebProxy("http://myproxy.com")
});
static async Task Main(string[] args)
{
for (int i = 0; i < 100000; i++)
{
try
{
// 傳送 GET 請求
HttpResponseMessage response = await client.GetAsync("http://youtube.com/WeatherForecast");
// 檢查響應狀態碼
response.EnsureSuccessStatusCode();
// 讀取響應內容
string responseBody = await response.Content.ReadAsStringAsync();
// 輸出響應內容
Console.WriteLine(responseBody);
await Task.Delay(1000);
}
catch (HttpRequestException e)
{
Console.WriteLine($"{DateTime.Now} HTTP 請求異常:{e.Message} {e.GetType().Name}");
}
}
}
}
開啟 wireshark 進行流量監聽,將程式執行起來,發現一切都是那麼太平,截圖如下:
由於某些原因,代理伺服器出了問題,這裡用 關閉的方式來模擬,再次觀察 wireshark 可以發現,沒有收到伺服器對154號包
的響應,client 這邊根據 RTO=1s
進行重試。
2. DNS解析到的IP無法訪問
有些朋友程式出現了卡死,原因在於設定了很長的 Timeout
,這種 Timeout 挺有意思,域名能夠透過 DNS 解析到 IP,但 IP 無法被訪問到,導致 client 這邊在不斷的重試,直到 timeout 的時限到時丟擲異常。
接下來還是用 HttpClient 做一個小例子,直接訪問 youtube.com ,參考如下程式碼:
static async Task Main(string[] args)
{
HttpClient client = new HttpClient();
for (int i = 0; i < 100000; i++)
{
try
{
// 傳送 GET 請求
HttpResponseMessage response = await client.GetAsync("http://youtube.com");
// 檢查響應狀態碼
response.EnsureSuccessStatusCode();
// 讀取響應內容
string responseBody = await response.Content.ReadAsStringAsync();
// 輸出響應內容
Console.WriteLine(responseBody);
await Task.Delay(1000);
}
catch (HttpRequestException e)
{
Console.WriteLine($"{DateTime.Now} HTTP 請求異常:{e.Message} {e.GetType().Name}");
}
}
}
開啟 wireshark 啟動監控,然後將程式執行起來,截圖如下:
從卦中可以看到 client 發起了一個 DNS 查詢,DNS伺服器查詢到 youtube.com 所對應的 IP 是 104.244.46.85,接下來應該就是 client 對這個 ip 發起 握手請求,截圖如下:
從圖中資訊看,真的很尬尷,有如下兩點資訊:
-
client 發起了 SYN 請求,結果沒人鳥它,沒人鳥主要是因為路徑上的防火牆把這個
SYN ACK
給沒收了。 -
client 端按照 1s,2s,4s,8s 的RTO計時器超時進行重試,直到 HttpClient 等不及拋 TimeoutException 異常。
三:總結
人是活在錯綜複雜的關係網裡,同樣程式也是,要想解決更多的.NET程式故障,對 tcp/ip 體系知識的瞭解也同樣必不可少。