UNP_Chapter02_TransportLayer_筆記總結

            ***PAY A TRIBUTE TO W.Richard Stevens***

Chapter01: 傳輸層協議: TCP, UDP, SCTP

2.1 簡介

在這章我們主要探討TCP/IP族中的協議,從編程者的角度去分析和理解如何去使用這些協議並且提供更爲詳細的描述.
這一張我們主要集中在傳輸層協議上: TCP, UDP和流控制傳輸協議(SCTP). 大部分的CS應用都是使用TCP和UDP. SCTP是新出來的一種傳出層協議主要負責進行互聯網中電話信號的傳輸.這些傳輸層的協議用的就是網絡層的IP協議,也就是Ipv4或者Ipv6. 但是也是能夠繞過傳輸層只進行IP層的使用,這就是傳說中的raw socket,但是很少用了.
UDP是一種簡單的不可靠的數據段協議,但是TCP是一種複雜的可靠的字節流協議. SCTP也是可靠的傳輸層協議,但是它是有信息界限的. 我們需要知道這些傳輸層協議是怎麼應用的應用程序中的,所以我們需要知道如何去把握這些協議.
我們只有熟悉了TCP的特徵,才能寫出健壯的客戶端和服務器端. 同樣的,也只有知道這些特徵,我們才能很好的debug客戶端和服務器端用牛逼工具netstat. 在這章中我們會涉及: TCP的三次握手, TCP的連接終止序列, TCP的TIME_WAIT狀態, SCTP的四次握手, 以及SCTP的鏈接終止, 還會涉及到SCTP, TCP和UDP的socket層的緩衝等.

2.2 宏圖

這裏寫圖片描述
大家不要被協議族的名字所迷惑: TCP/IP, 其實這裏的協議遠遠超過TCP和IP協議. 我們看最頂端的應用層,從最右面到最左面,最右面的五個應用工具是使用的IPv6, 左面的工具使用的是IPv4. 這個tcpdump是相當重要的分析工具, 它使用BSD包過濾器(BPF)或者數據鏈路接口(DLPI)直接與數據鏈路層進行了交互. 在這幅圖的九個應用程序下面可以看到有一條虛線標記爲了API, 這就是傳說中的socket或者XTI. 當然BPF和DLPI就不用socket進行交互了.
從圖中也能看出,traceroute是使用兩個socket進行交互,一個是IP, 另一個是ICMP.
IPv4和IPv6是什麼就不囉嗦了.
TCP: 是一種面向連接的可靠協議, 全雙工字節流協議. TCPsocket就是stream socket. TCP關係ack(Acknowledgment),超時,重傳等. 大部分的應用程序都會使用TCP, 並且TCP是支持IPv4和IPv6的.
UDP: User datagram protocl, 無連接協議,它的socket就是datagram socket. 對是否能完整或成功到達目的地是沒有保證的. 同樣也能支持IPv4和IPv6.
SCTP: 流控制傳輸協議. 是一種面向連接的全雙工可靠協議. 它是多宿主的,也就是在連接雙方可以有多個IP對應一個端口進行通訊. SCTP不進支持IPv4和IPv6, 並且可能在同一個連接中能支持它們一起.
ICMP:網絡消息控制協議. ICMP在主機和路由器之間處理錯誤和控制信息, 這些信息通常都是由TCP/IP網絡軟件自己生成的, 不是用戶程序, 儘管我們用ping, traceroute等程序. 我們也會指定ICMPv4來區分ICMPv6.
IGMP: 網絡組管理協議. 這是個多路協議,在IPv4中是可以選擇的.
ARP: 地址解析協議. ARP將一個IPv4地址映射成一個硬件地址(比如說以太網地址), ARP一般用於以太網,令牌環網(token ring), 和FDDI. 在p2p網絡中是用不到的.
RARP: 地址泛解析協議. RARP協議將硬件地址轉換成IPv4地址. 又是用於無盤節點的引導.那什麼是無盤節點的引導呢? 一臺無盤機器是一臺沒有任何諸如硬盤、軟盤驅動器或CD-ROM等常見引導設備的PC。無盤節點引導自網絡,並需要一臺服務器來提供當作本地硬盤來使用的存儲空間。從現在開始,服務器將稱爲“master”,而無盤機器將稱爲“slave”(從名字中能看出來什麼?:)。slave節點需要一塊支持PXE引導或Etherboot的網絡適配器。現在大多數的卡都支持PXE,很多主機集成的適配器也同樣可以。diskless node booting配置大家可以看這篇配置文章來對PXE引導有深刻的理解.
ICMPv6綜合了ICMPv4, IGMP和ARP的功能.
***BPF***BSD包過濾協議. 這個接口提供了數據鏈路層的通路. 這個協議一般在BSD引導的內核中.
DLPI: 數據鏈路提供接口. 這個接口也是數據鏈路層的通路. 通常由SVR4內核的系統提供.
這些不同的協議都有一個甚至多個RFC(Request for Comments)文件提供正式的說明支持. 我們通常稱同時支持IPv4, IPv6的主機叫IPv4/IPv6主機或者雙棧主機. 在4.4BSD中實現了的TCP/IP在TCPv2中詳細定義.

2.3 UDP

UDP是一種簡單的傳輸層的協議. 具體的RFC定義是RFC768. 應用層將數據將數據寫入到socket中,這個socket將會進行封裝(encapsulated)成UDP數據段,然後再傳遞到網絡層,封裝成IP數據段,最後送往目的地. 在整個過程中,並不能保證每次都成功到達終點,也不能保證到達後的順序正確,也不能保證保證UDP數據段只發送一次.
在我們的UDP編程中涉及到的問題就是很不可靠. 如果數據段到達了目的地但是checksum檢測到錯誤,或者數據段在網絡中被扔掉了,都不會傳送到目的端的socket中,並且源端也不會自動的重傳.但是如果我們想知道到底數據段傳送到沒時,我們就可以在我們的應用程序中加入比如ack, 超時檢測,重傳等機制.
每個UDP都會將發送端寫的數據長度都隨着數據一起傳到接收端的應用程序. 我們之前說到TCP是流協議,沒有邊界,但是UDP是有邊界的.
我們也知道UDP是面向無連接的, 也就說它並不需要和服務端保持持久的連接. 比如說一個客戶端給服務端發送了一個數據段後下一個數據段有可能通過相同的socket傳輸給了另外一個server. 當然服務端也可能是收到很多數據段信息,但是沒條都是來自於不同的客戶端.

2.4 TCP

TCP提供的服務是不同於UDP的. TCP定義在RFC793. 更新版本是RFC1323, RFC2581, RFC2988和RFC3390. 從這裏也能看出TCP是更爲複雜的一種協議. TCP是面向連接的協議,當於服務器端建立起連接後,會進行信息交互後才斷開.
並且TCP也是一種可靠的傳輸協議. 每次向服務端發送數據後都會等待ACK的返回進行確認,如果ACK沒有收到,那麼TCP在等待一段時間後會選擇重傳. 在差不多4-10(依據不同實現而不同)分鐘的不斷重傳還沒到達後就會放棄.
雖然TCP是可靠協議,但是也不是100%可靠的,因爲TCP是沒法保證數據肯定被另一端收到的,它只是用這些重傳等機制來提高準確性.
TCP使用算法動態的估算RTT(round-trip time)在服務端和客戶端的傳輸中. 通過RTT, 就能知道等待一個ACK需要多久. 因爲不同的網絡環境中的RTT會不同. 比如說在LAN中,RTT應該就是毫秒級的但是在WAN中就是秒級的. TCP會在指定的連接中持續的估算RTT.
TCP會爲傳輸的每個字節關聯一個序列號,這樣是爲了排序. 比如說, 假設應用程序往TCP socket中寫了2048字節的數據, TCP就會發送兩個分節(segments),分節就是TCP的傳輸單元,UDP是數據段(datagram). 序號分別是1-1024和1025-2048. 如果到達傳輸層是亂序了的,那麼接收端就會通過這些序列號進行排序然後再傳輸給應用程序. 通過這個序列號還能進行去重,因爲如果TCP因爲網絡負載或延遲等原因誤以爲丟失了而重發了分節,但是在接收端會根據序列號將後到的扔掉.
TCP還有流量控制(flow control)的功能.TCP能總是告訴對等方一次最多能接受多大的數據.這就是通告窗口(advertised window). 在任一時刻,接收窗口就是接收buffer當前的可用容量, 這樣就保證了發送端不會在接收buffer中溢出. 這個windows大小是動態變化的,如果發送端發送了數據到接收buffer,那麼這個size就會降低,當應用程序讀取了數據,這個size就會增大. 當這個buffer滿了,size就成了0, 那麼收端就不會再接收對等端發送過來的數據而是等待應用層讀取數據.
TCP也是全雙工的(full-duplex). 也就是發送端也會是接收端. 這就是說TCP必須要維護好發送和接收的sequence數, window大小爲兩端. 但是當建立一個全雙工連接後可以改變成單工. UDP也可以使全雙工.

2.5 SCTP

Stream Control Transmission Protocol. SCPT提供的服務類似於UDP和TCP提供的服務. SCTP在RFC2960中定義,更新在RFC3309, SCTP的簡介在RFC3286中. SCTP在客戶端和服務器端建立了關聯(association). SCTP也給應用層提供了可靠的,序列號,流量控制全雙工等類似於TCP的服務. 這個關聯這個詞取代之前我們提到的連接(connection)是爲了避免一個隱含之意(connotation):一個連接(connection)表示的僅僅是IP地址之間的連接. 關聯(association)是可以在兩個系統之間建立連接的,也就是說在兩個宿主之間可能是多個IP互連.
與TCP不同的是SCTP是消息導向的. 它提供各個記錄的按序遞送服務. 和UDP類似,由發送端寫入的每條記錄的長度隨數據一同傳給接收端應用.
SCTP能夠在鏈接的雙端提供多個流, 每個流各自可靠的按序傳輸. 當在一個流上信息丟失時並不會影響到其他流的信息傳輸,這一點不同於TCP, 在一個流中一旦某個信息傳輸出錯,就會阻塞後面的直到這個修復.
SCTP還提供了多宿主的特點,是的單個的SCTP端點能夠支持多個IP地址. 這個特點能夠增強網絡中故障的健壯性. 一個端點可以有多個冗餘的網絡連接, 每個網絡又可能有各自接入到因特網的基礎設施連接. 當該端點和另一個端點建立連接後,如果某個網絡或者跨越因特網的通路發生故障,SCTP就能夠切換到和這個連接的另一個端點進行連接.
類似的健壯性在路由協議的支持下也是能用TCP實現的. 比如BGP是一中路由協議. iBGP表示在一個域內的BGP連接, 這是路由器之間的協議. iBGP就經常用兩個路由器的虛擬地址進行TCP的連接,這樣只要路由器之間有路由連接就能保證健壯性,但是如果不用虛擬地址而用成了路由器的物理地址,那麼就無法保證健壯性了,一旦斷開就是gg的節奏. SCTP不進適用在路由器間,它是支持多個主機的, 並且允許不同的宿主連接到不同的服務提供商. 這是TCP結合路由辦不到的.

2.6 TCP連接的建立和終止

通過學習這一節能夠使我們更清楚的使用connect, accept和close等函數,並且當我們使用netstat進行檢測的時候也能進行很好的拍錯,所以這一節很重要,也是面試中出現頻率極高的.
我們先說建立連接的三次握手:
1. 服務器端必須準備好接收即將到來的連接. 通常調用socket, bind和Listen函數. 這也就是常說的passive open.
2. 客戶端發佈一個active open通過調用函數connect, 這使得客戶端的TCP發送了一個同步分節(synchronize segment) SYN, 這就是告訴了服務器我客戶端在連接這個路上發送的初始化數據的序列號.同常來說是沒有應用層的數據在這個SYN中包含着的, 一般就是IP頭, TCP頭和可能的可選TCP.
3. 當服務器端收到後,必須返回acknowledge(ACK)來承認客戶端的SYN收到並且服務器端也要返回自己SYN其中包含了這個數據的序列號. 也就是說服務器發送了ACK和SYN.
4. 客戶端發送ACK就可以了.
這裏寫圖片描述
這就是聞名已久的三次握手.注意序列號的值.
建立TCP連接就和我們的打電話一樣一樣的,我們來類比一下:
socket函數就好比是有電話可用, bind函數就是告訴別人你的電話號碼, 這樣他們就能呼叫你. listen就是打開函數的鈴聲,這樣當有一個外來的電話到來時,就可以聽到. connect函數就是讓我們輸入某個人的電話號碼並且撥打. accept發生在被呼叫的人應答電話之時. 由accept返回客戶的標識(客戶的IP和端口號) 類似於讓電話機的呼叫者ID功能部件顯示呼叫者的信息. 然後不同的是,accept只有在我們接起電話的時候才能顯示呼叫着的ID, 但是我們電話顯示功能在我們沒有應答的時候也會顯示出來. 如果使用DNS, 它就提供一種類似電話薄的功能, getaddrinfo就類似於在電話薄中查找某個人的信息, getnameinfo就類似於有一本按照電話號碼而不是名字排序的電話薄.
每個SYN都是可以包含TCP選項的:
·MSS選項. 擁有了這個選項,發送SYN的TCP端就會告知對等端它的最大分節的大小(maximum segment size)MSS, 也就是在本連接中它願意接受的每個分節的最大字節. 所以說接收方也要根據發送方的MSS來調整它即將發出的分節大小.這個選項是TCP_MAXSEG選項.
·窗口大小選項. TCP能夠通告對方的最大窗口大小是65535, 因爲在TCP的頭部的相應位置只有16bits. 但是當今的網絡已普及高速網絡或者是長延遲路徑(衛星鏈路)要求更大的窗口來儘可能的實現最大吞吐量. 這個選項就要求TCP頭部能夠擴大window size, 也就是左移0-14位.這樣window size能夠達到G級別(65535*2^14). 雙方終端系統都要支持這個選項. 這個變量是用SO_RCVBUF定義的.
爲實現與早期沒有實現這個選項的系統的互操作. TCP可以作爲主動打開的部分內容隨它的SYN發送這個選項信息. 但是只有在對端也隨它的SYN發送這個選項的時候,才能進行擴大window size. 類似的,服務器端只有接收到客戶端發過來的帶有option的SYN才能進行發送這個選項.
·時間戳選項. 這個選項就是爲了防止在高速網絡下防止數據被舊的,延遲了的重複了的數據而破壞了,這是個新的選項.
相對於連接時的三次握手,關閉時需要進行四次握手:
1. 首先是應用層調用close方法, 我們成這一端執行主動關閉. 這一端的TCP發送了一個FIN分節. 就是意味着結束傳輸數據.
2. 接收到FIN的另一端就是消極關閉端. 收到FIN後,會將這個FIN分節以end-of-file的數據形式放在內核buffer的隊列末尾,也是要同樣傳輸給應用層的. 因爲FIN在接收端表示的就是不會再有數據傳輸上去了.
3. 一段時間後,應用程序讀到了end-of-file後,就會關閉他的socket. 這也就造成TCP發送了一個FIN分節給active close端. (再發送FIN之前已經將收到的FIN返回了一個ACK)
4. active close端確認收到FIN.
通常情況下因爲雙方都是需要進行ACK確認的, 所以說傳輸了四個分節,但是也是有特殊情況的,比如第一次傳輸的FIN有可能攜帶有數據,第二次分開傳輸的ACK和FIN又可能合在一起進行傳輸.
FIN和SYN一樣,都是佔用了一個字節的序列號空間. 因此每個ACK都是收到的FIN+1.
我們看看這個TCP報文頭:
這裏寫圖片描述
順便看一下close過程圖:
這裏寫圖片描述
在從passive close端向active close端發送FIN和ACK的這個階段可能是發送數據的. 這就是我們常說的半關閉狀態. 我們會面會詳解這個shutdown函數.
雙方能發送FIN是因爲應用層調用了close函數,但是在現實中,當一箇中斷過來,或者調動了exit,或者main函數return了等將進程結束的方式都會使得所有打開的文件描述符關閉,也就會想所有連接這的TCP另一端發送FIN.
一般情況下都是客戶端充當active close的,但是在HTTP/1.0中是server端的active close.
這裏我們呈上一個很重要的TCP的11種狀態機的圖:
這裏寫圖片描述
我們還有些情況在這個圖中沒有顯示出來,一種是同時發送SYN,另一種是同時發送FIN的. 當我們用netstat檢測服務器端和客戶端的狀態的時候,這些狀態機都會顯示出來.
我們來一張更加詳細加上方法和狀態機的圖:
這裏寫圖片描述
交互雙方的MSS不同時可以的. 一旦建立起連接, C行程了requst給S. 如果我們假設一次請求數據大小正好小於1460,然後服務器端收到請求然後一般都能在200ms之內處理髮送出(加入這次相應和ACK也是下於536)reply和對客戶端request的ACK.(注意他們是在一個segment中). 這就是叫做piggybacking. 中文意思就是說扛着傳輸. 當piggybacking時間過於長的時候,比如1s, 客戶端總是會先收到確認然後隨後跟着reply數據. 我們稍後討論主動關閉方的最後一個狀態TIME_WAIT狀態.
可以看出單一發送一個請求和收到一個相應就需要這麼複雜,花費8個分節. 如果用UDP的話就是簡單的兩個數據段. 但是UDP就會將TCP的好多機制都扔掉, TCP提供的一個很重要的機制就是擁塞控制,這個必須由UDP應用層來控制.在網絡應用中好多應用程序是用UDP寫的,因爲它們傳輸不需要消耗太大,並且速度快.

2.7 TIME_WAIT

這個是一個很重要的狀態,在你寫CS服務器的時候肯定會看到,這也是我在面試的時候碰見的一個問題,當時我就沒答上來,重視起來~.
當我們都正常關閉後,主動方會進入到TIME_WAIT狀態持續2MSL(Maximum segment lifetime).
TCP的每個實現都是需要選擇一個MSL值的. 在RFC1122中建議是2分鐘,但是在BSD網絡系統中是30s, 也就是說MSL一般就是在1~4分鐘不等. MSL就是說IP數據段在網絡中能存活的最大時間,也就是說超過這個時候就死了. 我們經常聽到網絡總的詞彙是跳數, 也就是TTL, 每經過一個路由器TTL+1,如果到255都沒打到指定目的地,那麼路由器就會扔掉. 這個還並不是真正意義上的時間. 真正的時間就是MSL.
一個包的丟失經常就是路由器的發生故障的問題, 比如說路由器崩了或者是兩個路由器之間的鏈路速率下降有了很長的延遲, 路由環(a給b, b又給a)等這些情況,都需要路由協議耗費幾秒甚至幾十秒來尋找另外一條可行的路線. 那麼當主動關閉方發送了ack後,由於上面提到情況,那麼超時後,服務器沒收到ack,服務器就又發送了個FIN,現在網絡好了,也就是說兩個ack都到了,但是舊的ack是不能要的,如果剛收到新的ack後服務器知道能關了,這時又是同一個IP同一個端口建立了鏈接,此時之前遺留在網絡中的ack也來了,這就混亂了,所以爲了避免錯誤,就保持那個狀態2MSL, 這樣在網絡中的ack絕壁跪了. 這樣設置還有一個原因就是主動關閉方必須保持TIME_WAIT狀態,這樣才能收到服務端重新發的FIN,重新發送ACK, 如果不維持這個狀態它就會給服務端迴應RST標誌位. 服務器端會將這個標誌位全部當做是err異常拋出.
那麼爲什麼BSD設置30秒都沒問題呢?是因爲BSD是根據檢測新建立的連接發送的SYN的序列號是否比之前的大這種方式.

2.8 SCTP的association

SCTP的四次握手:
這裏寫圖片描述
1. S端必須時刻準備着進來的association. 和TCP同樣的會調用socket, bind和listen函數.
2. C通過調用connect或者是發送一個信息來隱式的打開association. 這一步造就了C的SCTP發送了一個INIT的信息來告訴服務器端客戶端的IP列表, 初始化序列號,用來識別在這次association中的所有包的標籤, C請求的外出流的數目,和客戶能夠支撐的進入流的數目.
3. 服務器以返回INIT-ACK確認收到客戶端的INIT. 其中包含了: 服務器端的IP列表, 初始化序列號,初始化標籤,服務端請求的外出流數目,服務端能支持的進入流的數目還有一個狀態緩存. 狀態cookie包含了確認本次關聯正常的所有所需的狀態,它是數字化簽名過的,以確保其有效性.
4. C以一個COOKIE-ECHO作爲迴應S的cookie, 這條消息可能在同一包中還捆綁了用戶信息.
5. 服務器以一個COOKIE-ACK進行確認用戶回射的cookie是正確的, 該消息也可能在同一個包中捆綁了用戶消息,就此關聯建立.
因爲SCTP建立連接最少要發送四個分節,所以我們稱這個過程爲SCTP的四次握手. 在這個過程中,首先是發送了INIT,在這個表中攜帶有一個驗證標籤Ta和一個序列號j. Ta必須在整個SCTP關聯的生命週期中在對等方發出的每個包中都得出現,也就是會伴隨着整個連接狀態中的對等方發送包. J就是用戶數據的塊兒的其實序列. 同樣的,在INIT的接收端要發送一個INIT-ACK, 這個包含着它創建的驗證標籤Tz, 還有序列K, 還有Ta標籤,還有cookie C. 這個C中包含着客戶端與服務器建立連接所有的狀態信息,這樣的話,在服務器端的棧中就不需要維護這些連接狀態信息了. 下次客戶端發送COOKIE-ECHO和Tz標籤,最後服務端發送COOKIE-ACK攜帶Ta標籤.
SCTP的雙方必須選定一個主要目的地址,當網絡中沒有出現故障的時候,這個地址就是數據傳輸的默認目的地. SCTP中維護的這個cookie也是防止DoS攻擊的有效手段(denial-of-service). 我們在後面也會討論的. TCP也效仿這個進行DoS防止, 但是TCP必須將cookie的數據壓倒sequence 中,因爲沒有給它多餘的空間放這個,所以就只能有32bits. 但是SCTP有專門的地方放cookie並且進行了加密.
下面我們討論一下SCTP的關閉過程:
這裏寫圖片描述
首先說明SCTP中並不支持類似於TCP的半關閉情況,只要有一段發起shutdown請求,那麼對等方就不會再給它的對等方發送用戶數據. 當收到SHUTDOWM請求後,會把queue中的數據送到用戶層處理一下,如果沒了,那麼就發送SHUTDONW-ACK, 最後主動方發送SHUTDOWN-COMPLETE就直接結束了. 這裏並不存在像TCP一樣的TIME_WAIT狀態,因爲它在每次的傳輸中都維護這Ta和Tz,這樣的話,有old包來的時候就能驗證出標籤不對.
那麼它的狀態是什麼樣的呢?
這裏寫圖片描述
我們能看到在四次握手中客戶端發送COOKIE-ECHO的時候順便帶上了發送的數據塊,在服務器端迴應的COOKIE-ACK中也是帶上了迴應的數據塊兒. 一般情況下COOKIE-ECHO攜帶的數據是一個或多個當應用程序調用一對多的接口時. 在每個數據塊兒中有塊兒大小,類型,flags, 這樣以塊的形式,也是很方便與在傳輸中多個的綁定.
SCTP中參數和塊這兩個特點促進了SCTP可選特點的發展. SCTP可以彙報未知的參數和未知的塊兒,在參數和塊兒的最高兩位就是告訴SCTP接收端如何去處理未知的情況. 現在SCTP主要的研究方向是:
1. 動態對地址進行擴展,也就是允許相互合作中的SCTP在終端能動態刪除或者增加IP.
2. 還有一種就是非完全可靠的擴展,在端點的應用層底下對數據的重傳進行限制,就是說如果這個數據太老了不需要重傳了的時候,就會直接跳過這個數據的重新傳輸, 這也就造就了一些不可靠因素.

2.9 端口號

我們所說的應用程序其實每個應用程序就是一個進程,那麼多個進程訪問服務器的進程時,服務器是怎麼識別訪問的是哪個進程呢?那麼針對不同的進程定義了一系列的端口號,針對某些特殊的應用程序(進程)有着固定的端口號.比如,FTP這個進程就給所有TCP/IP實現分配了21, TFTP針對傳輸層的UDP實現分配了69這個端口號. 當然當服務器傳回數據的時候也是需要給到客戶端的應用進程的,也是要通過端口號的,那麼這些端口號就是臨時的端口號,並且是傳輸層自動分配的,其實在client請求服務器的時候就分配了,客戶端應用進程是不需要關心自己的這個端口的,這是經過傳輸層的時候自動分配的.
IANA(Internet Assigned Numbers Authority)就是維護端口號列表的. 端口號主要分成以下三類:
1. 0到1023進行知名端口號的定義這些由IANA進行分配管理, 我們總會聽到TCP的端口號是多少多少,UDP是多少,其實端口號是應用層和傳輸層鏈接的口, 所以當我們定義Web服務器是80端口的時候,那麼它所用到的傳輸層協議即TCP和UDP也就是80了. 但是在80分配的時候SCTP並沒有出現,但是新的規定TCP所支持的所有端口號在SCTP上都能夠使用.
2. 1024到49151. 這些端口並不在IANA管轄, 但是IANA等級並且列出了它們的使用情況. 比如說6000到6063就分配在了X Window服務器上. 同時也是爲TCP和UDP分配了的.
3. 49152到65535, IANA完全沒有理會這些端口號,這也就是臨時端口的使用.
但是以上針對不同系統又有着不同的定義:
這裏寫圖片描述
我們可以看出,在Unix系統中有着保留端口號的概念, 這些端口號和IANA管理分配的一樣, 這些端口號針對不同的應用進程要修改的時候只能root權限去更改.
在早期的4.3BSD中定義1024到5000是臨時端口號,但是我們現在併發進程何止幾千,所以現在系統實現都是使用IANA定義的標準,使用後面的爲臨時端口號.
在某些情況下客戶端也會需要reserved端口的作爲客戶端和服務器端的認證. 比如說rlogin和rsh客戶端.這些客戶端調用rresvport(3)函數創建TCP socket 並且分配一個513-1023未用的端口號(從後往前分分配). 那麼什麼是rsh呢? Rsh 是遠程外殼(remote shell) 的縮寫(外殼是操作系統的一種命令接口)。運行於遠程計算機上的rshd 後臺程序,接受rsh 命令,驗證用戶名和主機名信息,並執行該命令。當用戶不願或不需要與遠程計算機建立遠程會話時,可以使用rsh 工具執行輸入的命令。Rsh 工具允許用戶在遠程計算機上執行單條命令,而無需在該遠程計算機上進行登錄. 那麼什麼是rlogin? 遠程登錄(rlogin)是一個 UNIX 命令,它允許授權用戶進入網絡中的其它 UNIX 機器並且就像用戶在現場操作一樣。一旦進入主機,用戶可以操作主機允許的任何事情,比如:讀文件、編輯文件或刪除文件等。Rlogin:遠程登錄命令 rlogin:Remote Login in Unix systems. 我們看看官方對rresvport的定義:
這裏寫圖片描述
我們肯定都見過socket pair,這對於TCP來說是四元組: 本地IP, 本地端口,遠端IP,遠端端口號. 但是對於SCTP來說,如果兩端都不是多宿主的情況下,和TCP是一樣的,但是如果只要有一端是多宿主,那麼有可能就包含多個四元組了,雖然本地IP很多,但是本地端口是統一的,雖然遠端IP很多,遠端端口也是統一的.

2.10 TCP端口號和多併發服務器

接下來我們引進一個多併發服務器器是如何識別不同訪問的以及簡單的是如何處理請求的:(我們現在的多併發服務器不會使用這種方式處理,因爲用進程處理太浪費了,比如現在流行的高併發HTTP服務器Nginx用到就是epoll技術).:
這裏寫圖片描述
加入說我們現在在freeBSD上有個服務器,它是多宿主的,也就是說有多個IP,或者是多個本地(網絡)接口(local interface). 我們多宿主服務器的IP是12.106.32.254和192.168.42.1, 然後我們分析一下{*:21, *:*}的意思. ,前面的一對是我們的本地的接口和針對等待接收客戶端請求的服務器應用的端口號, 後面一對錶示的是訪問過來的客戶端的IP地址和客戶端的端口號,這兩這樣寫表示的就是說我對我服務器本地的所有接口都開放(也就是所有網卡都等待),這就像我們之前寫的一個程序在地址結構體中指定INADDR_ANY. 然後我服務端應用程序用到的是21端口號,我接收任何客戶端的IP地址和任何端口號. 這個通配符(wildcard)類型在我們做網絡監控的時候netstat會看到的.
這裏寫圖片描述
我們客戶端206.168.112.219端口是1500的應用進程與12.106.32.254端口是21的服務器進程進行了連接請求connect. 這個查看客戶端的netstat會發現的. 這幅圖中的server端還是處於listen的socket階段,因爲還沒有真正的建立連接,只是將連接請求放在了listen隊列中了.
這裏寫圖片描述
在這個圖中我們會發現建立了鏈接,並且服務器進程是通過創建了一個子進程而處理的這個請求. 當它們建立了真正的鏈接後就是connect socket了. 然後服務器端的網絡監測的時候就會發現填滿了.當我們在客戶端又有一個請求時,它們的進程不一樣所以又不一樣的端口號:
這裏寫圖片描述
服務器主進程監聽輪訓到時識別到雖然IP相同但是不同的客戶端進程,所以會再創建出一個子進程單獨去處理這個請求.
這就是簡單的多併發模型,只不過現在沒有服務器這樣用.

2.11 buffer大小和限制

這裏我們引入一些限制IP數據報(datagram)的大小設置限制:
1. IPv4數據報的帶下是65536字節,其中包括IPv4的頭. 這是因爲在IPv4的長度字段中只有16bit.
2. IPv6數據報的最大大小是65575字節, 包括40字節的IPv6頭部. 這是因爲payload(淨荷)段只有16bit,那不是65535字節嗎?說明IPv6的這個長度域中能顯示的大小是不包含頭部的. IPv6有一個超大淨荷選項, 這個選項可以將淨荷域的長度拓展到32bit, 不過這個選項需要MTU(Maximum transmission unit)超過65535的數據鏈路層支持.
3. 許多網絡有一個可由硬件支持的MTU. 比如說以太網的MTU是1500字節. 另外有些鏈路協議如(PPP點對點鏈路)其MTU能夠自己配置. IPv4要求的最小鏈路MTU是68字節. 這就允許最大的IPv4頭部(20子節的固定頭部和30字節的可選部分)拼接最小的片段(IPv4首部中片段偏移以8個字節爲單位).IPv6要求的最小鏈路MTU是1280字節,IPv6可以運行在比這個值還小的鏈路上,不過需要特定於鏈路的切片和分組功能,以是的這些鏈路看起來至少爲1280子節的MTU.
4. 兩個主機路徑之間的最小MTU就叫做path MTU. 如今以太網的MTU是1500字節,就是路徑MTU. 路徑MTU沒有必須在兩個主機之間相同,因爲路由往往就不是對稱的. 也即是路由器A到B和B到A的路徑MTU可以不同.
5. 當IP數據報從接口上傳送出去後如果超過了鏈路MTU,那麼IPv4和IPv6的分片機制就用上了. 這些分片在到達目的地之前一般不會重組的. IPv4主機對其產生的數據報進行分片, IPv4路由器則對其轉發的數據報進行分片. (注意路由器也是一個主機,裏面也是unix系統). (IP與數據鏈路層之間就是所謂的接口層. )但是IPv6值有IPv6主機會進行分片,IPv6路由器是不會對要轉發的IPv6數據報分片的. 這裏注意,如果是路由器自己產生的的IPv6數據報,路由器是會分片的,只是不會分片來自其他主機的,自己只是做個轉發而已. 比如說網絡管理員能夠配置路由器支持Telnet協議,那麼IPv6就可能由路由器的telnet服務器功能.
你可能發現在IPv4頭部有處理分片的字段,但是在IPv6中沒有這樣單獨的字段,這是因爲分片是例外情況而不是規則部分,在IPv6的可選字段中有分片的選項.
一些通常用作路由器的防火牆可能重組分組了的分片,以便查看整個IP數據包的內容,這樣做使得防火牆上不必引入額外的複雜性能就能防止某些攻擊,還要求防火牆設備是進出網絡唯一路徑上的設備.
6. 如果在IPv4的分片設置域中設置了DF標誌位(don’t fragement), 表示不管是主機還是路由器都別給我分片這個數據報,但是如果超過了MTU, 那麼就返回ICMPv4告訴不可達,要分片而不是設置成DF的錯誤信息.
如果是IPv6, 因爲路由器不執行轉發報文的分片,所以其實內部隱士的有個DF位,當IPv6路由器檢測到超了鏈路上的MTU, 就會生成ICMPv6,說包太大的錯誤.
IPv4的DF位和IPv6的隱藏DF位可用於path MTU的發現, 例如,如果基於IPv4的TCP使用該技術, 那麼它將在所發送的所有數據報中設置DF位, 如果某個路徑返回ICMP錯誤信息, TCP就減少每個數據報的數量並重新傳. path MTU發現對於IPv4是可選的, 然後IPv6的所有實現要麼支持它,要麼必須使用最小的MTU發送數據報. 但是當今的路由器好多是不給你返回ICMP信息的,所以使用TCP來進行path MTU發現是有問題的,IETF正在努力發現不依賴ICMP錯誤信息而進行path MTU發現.
7. IPv4和IPv6都定義了最小重組buffer大小,它是IPv4和IPv6任何實現都支持的最小數據報大小. 這個值對於IPv4是576字節,對於IPv6是1500字節. 對於IPv4來說我們並不能保證每個主機都支持大於577字節的數據報,所以好多基於IPv4的應用DNS, RIP, TFTP, BOOTP, SNMP設置防止超過576字節的數據報的產生.
8. 對於TCP來說有個MSS(maximum segment size), 這個就是在SYN的數據中攜帶着告訴對方你給我發的時候我能接收的最大segment就是這麼大. 這主要就是爲了告訴對等方我這裏的最大重組buffer是多大, 也是爲了避免分片的發生. 這個MSS的值就是接口MTU減去IP固定大小和TCP頭部,所以對應以太網上IPv4是1460(1500-20-20), IPv6是1440(1500-20-40).TCP頭都是20.
9. 在TCP的MSS字段中有16bits,所以最大值也是65535,這對於IPv4來說是可以的,因爲IPv4的數據報中的最大TCP segment是65535-20-20, 然後對於具有超大淨荷的IPv6來說就得用其他的技術了, 65535只有在設置了最大淨荷的時候才使用這個值,否則就是65535-20, 但是實際的MTU是超過65535的, 那麼TCP使用最大淨荷選項的話,那麼它所發送數據的大小限制就是接口的MTU.
10. SCTP基於對端發現的所有地址的最小path MTU保持一個分片點,這個最小MTU用於把較大的用戶信息分割成較小的能夠以單個IP數據報發送的若干片段. SCTP_MAXSEG socket選項可以影響這個值, 允許用戶請求一個最小的分片點.
上面敘述了這麼多原理,那麼我們現在用一個圖來分析我們發送數據的過程以及其中涉及到的buffer情況(重點):
這裏寫圖片描述
每個TCP都有發送緩衝區,我們可以通過SO_SNDBUF來進行其大小的設定, 當應用層調用write函數的時候,內核將應用層buffer中的數據全部拷貝到socket send buffer中. 如果socket send buffer滿了或者不夠一次性拷貝來自應用層buffer的數據,那麼進程就會睡眠,這裏我們用到的是阻塞socket,我們以後會介紹到非阻塞socket. kernel將不會返回給用戶層直到將應用層緩衝區中的全部數據全部拷貝到socket send buffer中,所以說我們應用層如果收到write的返回,並不是說明數據傳送到了服務端了.
TCP從socket send buffer中取到數據然後依據TCP的傳輸規則將數據傳輸到服務端. 服務端必須確認收到,我們客戶端之後收到ACK後纔會將socket send buffer中存放的重發的數據丟棄掉.TCP必須保持一個數據的拷貝直到收到相應的ACK.
TCP發送數據到IP層用MSS大小或者是更小的塊兒,注意這個MSS是沒有包含TCP頭的, 然後加上爲每個segment加上TCP的頭,這個MSS就是通過SYN中的MSS值確定的,如果沒有指定的會話就是默認的536. 然後IP再加上它自己的頭,通過檢索路由表找到目的地址對應的接口,然後將datagram傳輸到合適的數據鏈路上. IP可能在傳輸到數據鏈路的時候切片,但是我們之前說過了,MSS的目的就是爲了避免這一點和實現最小MTU discovery. 每個數據鏈路都是一個輸出queue, 如果這個queue滿了,包就會被丟掉,然後返回錯誤信息給IP然後給了TCP,然後TCP就會安排重傳,我們用戶層是完全不會知道的.
我們看完了TCP的輸出情況,我們再看看UDP的輸出情況.
這裏寫圖片描述
應用進程調用sendto將用戶進程buffer中的數據寫入到內核的”socket send buffer”中,這裏的內核buffer其實不存在的,但是有個宏SO_SNDBUF這個可以改,看見是buf的大小,其實這個只是表示從用戶進程拷貝到內核的最大上限. 如果用戶進程寫入一個UDP的datagram比這個SO_SNDBUF大的話,EMSGSIZE將會返回. 因爲UDP是不可靠的傳輸,所以這些buffer是不需要的. 接着UDP會加上它8字節的頭部傳給IP層,IP的在加上相應的頭,然後通過路由函數決定去到哪個接口,然後將datagram添加到數據鏈路的queue中, 如果UDP發送一個超大的datagram是比TCP被切片的可能性更大的, 因爲TCP在send socket buffer那裏已經將應用層的數據限制了一部分了. 但是UDP並沒有這樣的機制.
當我們write函數成功返回的時候表示的也不是數據成功抵到對面了,而是說應用層的數據已經成功加入到datalink的隊列中了.
接着我們看看SCTP的輸出是什麼樣的形式?
這裏寫圖片描述
我們可以看出,完全是和TCP一樣的,只不過MSS等信息等存放在了cookie中了, 當我們收到write的成功返回時,表示的就是用戶進程中的最後一個字節已經拷貝到了socket send buffer中了,用戶進程的buffer能夠重用了. 然後和TCP不同的之處就是SCTP的socket send buffer中的數據必須等待SACK的到來才能移除.

2.12 網絡服務的標準

這裏我們列出幾個絕大多數TCP/IP實現的服務的標準和端口,你會發現,IANA盡然爲這些簡單的服務提供知名端口號,真是….不錯:
這裏寫圖片描述
在Unix中這些服務都是通過inetd這個守護進程大管家進行統一管理的,但是對於公司的大型服務器不能這樣幹,一般都是一臺服務器上跑一個單獨的程序進程都不夠. 那麼簡單科普一下Unix的inetd: inetd是監視一些網絡請求的守護進程,其根據網絡請求來調用相應的服務進程來處理連接請求。它可以爲多種服務管理連接,當 inetd 接到連接時,它能夠確定連接所需的程序,啓動相應的進程,並把 socket 交給它 (服務 socket 會作爲程序的標準輸入、 輸出和錯誤輸出描述符). 使用 inetd 來運行那些負載不重的服務有助於降低系統負載,因爲它不需要爲每個服務都啓動獨立的服務程序.
通過測試,google.com這個域內並沒有配置daytime和echo服務當今的服務器也一般不會提供這種服務了,因爲這是很容易被Dos攻擊的入口, 我們一般telent google的Web服務器都是telnet google.com 80, 其實在我們的/etc/services文件中就定義了IANA給我們分配的知名服務的端口號map表,我們可以直接使用:
這裏寫圖片描述

2.13 網絡應用中的協議使用情況

我們先強調一點,ICMP是網絡層的協議,和IP是同一層的協議:
這裏寫圖片描述
traceroute應用是通過自己發送UDP包然後返回的ICMP進行分析的,不一定網絡層必須要經過傳輸層的傳輸,ICMP的另一個應用程序ping就是中介通過IMCP返回的,traceroute也就只是自己產生了發送的udp包,返回也是直接ICMP返回的.


聯繫方式: [email protected]

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章