計算機網路再次整理————tcp周邊[八]

敖毛毛發表於2022-02-12

前言

tcp的包的格式可以看我以前的計算機網路整理,下面這些周邊只是為了開發時候我們能用到一些理論知識。

正文

首先要介紹的就是域名,為啥有域名這東西呢?單純站在網路的角度上講這屬於應用層的東西了。

如果站在萬物互聯的角度上講,把網際網路看做是一臺大型電腦的話,那麼域名就相當於控制程式碼了。

控制程式碼這東西解釋一下,控制程式碼(Handle)是一個是用來標識物件或者專案的識別符號,可以用來描述窗體、檔案等,值得注意的是控制程式碼不能是常量。

Windows之所以要設立控制程式碼,根本上源於記憶體管理機制的問題,即虛擬地址。簡而言之資料的地址需要變動,變動以後就需要有人來記錄、管理變動,因此係統用控制程式碼來記載資料地址的變更。在程式設計中,控制程式碼是一種特殊的智慧指標,當一個應用程式要引用其他系統(如資料庫、作業系統)所管理的記憶體塊或物件時,就要使用控制程式碼

比如我們要訪問某個服務,但是伺服器的ip地址是可以變動的,那麼如果是這樣的話,那麼就有一個問題,那就是這個ip要一直屬於你,但是這是不可能的。

那麼就有了域名這東西,ip再怎麼變化,同一個域名代表著某種服務,域名不變服務就不變了。

那麼我們知道源計算機到目標計算機的是通過ip協議的,也就是說傳輸跟域名是無關的,那麼域名如何應對變化的ip呢?

那麼就有人想出了DNS這東西,Domain name system(域名系統),它作為將域名和IP地址相互對映的一個分散式資料庫,能夠使人更方便地訪問網際網路。DNS使用UDP埠53。當前,對於每一級域名長度的限制是63個字元,域名總長度則不能超過253個字元。

大概是這麼一個東西,使用的是udp,那麼我們瀏覽器訪問域名,實際上內部會幫助我們將域名轉換為ip然後傳送出去,那麼域名標誌就在我們應用層才知道是啥,比如說http將域名放在header 裡面的host位置上。

上面是dns抓包。這裡可以看到我配置了DNS 服務配置是192.168.0.1,也就是我的閘道器。

那麼如果192.168.0.1 如果無法解析出域名,那麼就會傳到下一級DNS伺服器了。

static void Main(string[] args)
{
	var addresses = Dns.GetHostEntry("www.baidu.com").AddressList;
	foreach (var item in addresses)
	{
		Console.WriteLine($"ip地址:{item.ToString()}");
	}
	Console.ReadLine();
}

我們程式碼中獲取也是相當簡單。

然後這裡可以看到,我們一個域名會返回AddressList,也就是多個地址。

這個是一個常規現象,怎麼說呢,可能多個ip配置一個域名,可以做到負載均衡的作用。

那麼多個ip配置一個域名,那麼訪問域名訪問的是哪個ip呢?

在域名解析過程中,通過層層解析,必將域名對應到 IP(逐級授權、中間 CNAME 層層轉發,此處按下不表)對於對應到的IP,可能1個,也可能13個(不建議超過13個),DNS 會一股腦將這(些)個 IP 給解析客戶端(如瀏覽器、作業系統的解析服務)最終的應用或系統解析服務,從中隨機挑選一個(如果是定製的應用,還可以通過後續的應用層訪問對獲取到的 IP 做 HA 或權重)

也就是說你用哪個ip是應用程式自己決定的,當然有些庫是幫你輪詢了,也有些庫只選用第一個,但這不是DNS幫你乾的事情,DNS 只會返回你在服務商配置的資訊。

一些大型網站或CDN服務商為了實現負載均衡,他們的DNS伺服器會動態改變多個IP地址的順序,使得每個IP地址都有機會成為解析結果中的第一個IP地址。

既然域名能解析成ip,那麼ip是否能反向解析為域名呢?也是可以的。

https://baike.baidu.com/item/域名反向解析/9327917

平時也沒過反向解析這東西,上面也是寫道是郵件識別安全問題,不做過多的評論,總之是可以的。

那麼對於我們的應用來說,現在ip修改的問題解決了,那麼是否直接一個socket,然後通訊編寫我們自己的應用協議就行呢?

因為我們的應用各不相同,那麼就有不同的場景,也就有了不同的需求,那麼可以通過對socket的配置來滿足我們的需求。

static void Main(string[] args)
{
	var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
	socket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.ReceiveBuffer, 4098*2);
}

比如tcp是流,我們可以設定輸入緩衝區的大小,當然也可以設定輸出緩衝區的大小了。

但是值得注意的是,這個設定作業系統可能並不會100%執行,比如說我設定了兩倍,那麼作業系統不一定按照兩倍,可能是1點多倍,這個作業系統有自己的演算法的。

那麼是否配置是否隻影響效能呢?答案其實不是的,還可能短時間無法重啟問題。

這就是著名的 time-wait 問題。

這上面標紅的部分就是time-wait。

為啥有time-wait呢? time-wait 一定是在首先傳送斷開的一方。

下面把首先傳送埠的比作是A,後面斷開的比作是B。

為什麼有time-wait呢? 因為A斷開的一方沒有time-wait,也就是說傳送最後的ack這個socket銷燬了,那麼可能傳送的ack沒有到達B(丟失)。B就不知道A收到了FIN訊號,那麼B就會進行重試,但是A的socket消失了,然後B永遠收不到Ack,無法正常關閉。

那麼這個time-wait有什麼問題呢? 比如我們的服務應用當機了,如果socket 在time-wait 那麼我們指定socket bind 某個指定的埠了,那麼如果該socket處於time-wait狀態,那麼服務重啟就會報告被埠占用情況。

那麼有沒有辦法解決呢?

這個是允許繫結time-wait socket使用的埠。

Q:編寫 TCP/SOCK_STREAM 服務程式時,SO_REUSEADDR到底什麼意思?

A:這個套接字選項通知核心,如果埠忙,但TCP狀態位於 TIME_WAIT ,可以重用埠。如果埠忙,而TCP狀態位於其他狀態,重用埠時依舊得到一個錯誤資訊,指明"地址已經使用中"。如果你的服務程式停止後想立即重啟,而新套接字依舊使用同一埠,此時SO_REUSEADDR 選項非常有用。必須意識到,此時任何非期望資料到達,都可能導致服務程式反應混亂,不過這只是一種可能,事實上很不可能。

前面幾篇文章中提及到nagle這個詞,nagle 是一個演算法,主要是優化小資料網路流量問題。

nagle 是在什麼條件下誕生的呢? 我們指定tcp 是建立在ip協議的基礎上,ip協議和tcp協議都是佔用位元組的,如果你傳輸一個位元組就要給你傳送一次的話,也就是說協議的大小要大於資料的大小,那麼可以想象流量多麼浪費。

那麼nagle 就是這樣的,比如要傳送nagle,n、a、g、l,這個一個位元組一個位元組傳送的話,那麼首先n傳入到快取中去,然後傳送,這個時候等n傳輸的ack回來後,那麼此時快取中可能就有了agl,那麼就會把agl全部傳送出去。

也就是說要等上一個訊息的ack確定對方收到後,才開始傳送下一條訊息。如果設定socket 為TCP_NODELAY為true,那麼不會等上一個ack收到就會傳送新的資料。

那麼nagle 演算法是不是百分百適用呢? 答案肯定是否定的,沒有任何一種演算法是100%的。

如果我們在傳輸大檔案的時候,很快就會填充掉io的快取,如果使用nagle 演算法,那麼要等待ack回來後才能傳送,就有點影響效率了。

Nagle演算法的規則(可參考tcp_output.c檔案裡tcp_nagle_check函式註釋):

(1)如果包長度達到MSS,則允許傳送;

(2)如果該包含有FIN,則允許傳送;

(3)設定了TCP_NODELAY選項,則允許傳送;

(4)未設定TCP_CORK選項時,若所有發出去的小資料包(包長度小於MSS)均被確認,則允許傳送;

(5)上述條件都未滿足,但發生了超時(一般為200ms),則立即傳送。

那麼可能就有人問了,那麼tcp 都沒有ack確認後再進行傳送,那麼對方收到的包的順序不就是亂的啊,怎麼能保證資料流順序正常呢?

這個問題的其實吧,tcp 的包裡面的資料是有編號的,並不是說傳送方發完訊息,然後傳送方收到了接收方收到了訊息(ack),然後保證資料順序的。

那這就太侷限了,其實tcp保證順序是有編號的,這就要從tcp格式說起了,以前整理的也有,後面再次整理的時候會補充。

socket的配置還有很多,比如TCP_CORK啊,可以百度看看。

上面就是為了說明一下tcp是一個非常複雜的協議, 我們在做應用的時候需要了解一些配置,或許有利於效能以及穩定性。下一節,多程式的tcp服務。

相關文章