我們已經講解了物理層、連接層和網絡層。最開始的連接層協議種類繁多(Ethernet、Wifi、ARP等等)。到了網絡層,我們只剩下一個IP協議(IPv4和IPv6是替代關系)。進入到傳輸層(transport layer),協議的種類又開始繁多起來(比如TCP、UDP、SCTP等)。這就好像下面的大樹,根部(連接層)分叉很多,然后統一到一個樹干(網絡層),到了樹冠(傳輸層)部分又開始開始分叉,而每個樹枝上長出更多的樹葉(應用層)。我們在網絡層已經看到,通過樹干的統一,我們實現了一個覆蓋全球的互聯網絡(Internet)。然而,我們可能出於不同的目的利用這張“網”,隨之使用的方式也有所區分。不同的傳輸層協議(以及更多的應用層協議)正是我們使用“網”的不同方式的體現。
網絡分層的“藝術”觀點
傳輸層最重要的協議為TCP協議和UDP協議。這兩者使用“網”的方式走了兩個極端。兩個協議的對比非常有趣。TCP協議復雜,但傳輸可靠。UDP協議簡單,但傳輸不可靠。其他的各個傳輸層協議在某種程度上都是這兩個協議的折中。我們先來看傳輸層協議中比較簡單的UDP協議。我們將參考許多之前文章的內容(協議森林01, 03, 05)。
UDP協議簡介
UDP(User Datagram Protocol)傳輸與IP傳輸非常類似。你可以將UDP協議看作IP協議暴露在傳輸層的一個接口。UDP協議同樣以數據包(datagram)的方式傳輸,它的傳輸方式也是"Best Effort"的,所以UDP協議也是不可靠的(unreliable)。那么,我們為什么不直接使用IP協議而要額外增加一個UDP協議呢? 一個重要的原因是IP協議中並沒有端口(port)的概念。IP協議進行的是IP地址到IP地址的傳輸,這意味者兩台計算機之間的對話。但每台計算機中需要有多個通信通道,並將多個通信通道分配給不同的進程使用(關於進程,可以參考Linux進程基礎)。一個端口就代表了這樣的一個通信通道。正如我們在郵局和郵差中提到的收信人的概念一樣。UDP協議實現了端口,從而讓數據包可以在送到IP地址的基礎上,進一步可以送到某個端口。
UDP:依然不是那么“可靠”
盡管UDP協議非常簡單,但它的產生晚於更加復雜的TCP協議。早期的網絡開發者開發出IP協議和TCP協議分別位於網絡層和傳輸層,所有的通信都要先經過TCP封裝,再經過IP封裝(應用層->TCP->IP)。開發者將TCP/IP視為相互合作的套裝。但很快,網絡開發者發現,IP協議的功能和TCP協議的功能是相互獨立的。對於一些簡單的通信,我們只需要“Best Effort”式的IP傳輸就可以了,而不需要TCP協議復雜的建立連接的方式(特別是在早期網絡環境中,如果過多的建立TCP連接,會造成很大的網絡負擔,而UDP協議可以相對快速的處理這些簡單通信)。UDP協議隨之被開發出來,作為IP協議在傳輸層的"傀儡"。這樣,網絡通信可以通過應用層->UDP->IP的封裝方式,繞過TCP協議。由於UDP協議本身異常簡單,實際上只為IP傳輸起到了橋梁的作用。我們將在TCP協議的講解中看到更多TCP協議和UDP協議的對比。
IP和他的傀儡UDP
UDP的數據包同樣分為頭部(header)和數據(payload)兩部分。UDP是傳輸層(transport layer)協議,這意味着UDP的數據包需要經過IP協議的封裝(encapsulation),然后通過IP協議傳輸到目的電腦。隨后UDP包在目的電腦拆封,並將信息送到相應端口的緩存中。
UDP協議的頭部
來自wikipedia
上面的source port和destination port分別為UDP包的出發端口和目的地端口。Length為整個UDP包的長度。
checksum的算法與IP協議的header checksum算法相類似。然而,UDP的checksum所校驗的序列包括了整個UDP數據包,以及封裝的IP頭部的一些信息(主要為出發地IP和目的地IP)。這樣,checksum就可以校驗IP:端口的正確性了。在IPv4中,checksum可以為0,意味着不使用checksum。IPv6要求必須進行checksum校驗。
端口與socket
端口(port)是伴隨着傳輸層誕生的概念。它可以將網絡層的IP通信分送到各個通信通道。UDP協議和TCP協議盡管在工作方式上有很大的不同,但它們都建立了從一個端口到另一個端口的通信。
IP:端口
隨着我們進入傳輸層,我們也可以調用操作系統中的API,來構建socket。Socket是操作系統提供的一個編程接口,它用來代表某個網絡通信。應用程序通過socket來調用系統內核中處理網絡協議的模塊,而這些內核模塊會負責具體的網絡協議的實施。這樣,我們可以讓內核來接收網絡協議的細節,而我們只需要提供所要傳輸的內容就可以了,內核會幫我們控制格式,並進一步向底層封裝。因此,在實際應用中,我們並不需要知道具體怎么構成一個UDP包,而只需要提供相關信息(比如IP地址,比如端口號,比如所要傳輸的信息),操作系統內核會在傳輸之前會根據我們提供的相關信息構成一個合格的UDP包(以及下層的包和幀)。socket是一個比較大的課題,在協議森林系列中不會過多深入。
(在原始Python服務器我們討論了如何使用socket建立一個TCP連接,可以作為一個參考)
端口是傳輸層帶來的最重要的概念。我們進一步了解了UDP協議。如果已經掌握了IP協議,那么UDP協議就沒有任何困難可言,它只是IP協議暴露在傳輸層上的接口。
UDP套接口是無連接的、不可靠的數據報協議;既然他不可靠為什么還要用呢?其一:當應用程序使用廣播或多播時只能使用UDP協議;其二:由於他是無連接的,所以速度快。因為UDP套接口是無連接的,如果一方的數據報丟失,那另一方將無限等待,解決辦法是設置一個超時。
建立UDP套接口時socket函數的第二個參數應該是SOCK_DGRAM,說明是建立一個UDP套接口;由於UDP是無連接的,所以服務器端並不需要listen或accept函數。
使用UDP套接字編程可以實現基於TCP/IP協議的面向無連接的通信,它分為服務器端和客戶端兩部分,其主要實現過程如圖3.1所示。
圖3.1 UDP客戶/服務器的套接字函數