1. 數據傳輸的目的
前一篇文章中我們講解了網絡傳輸的流程,那么網絡傳輸的目的是什么呢?難道我們只是將數據從一臺主機傳輸到另一臺主機嗎?
當然不是的!因為數據是給人用的。比如:聊天是人在聊天,下載是人在下載,瀏覽網頁是人在瀏覽? 但是人是怎么看到聊天信息的呢?怎么執行下載任務呢?怎么瀏覽網頁信息呢?是通過啟動的 qq,迅雷,瀏覽器。 而啟動的 qq,迅雷,瀏覽器都是進程。換句話說,進程是人在系統中的代表,只要把數據給進程就相當于人就拿到了數據。 所以數據傳輸到主機是手段,將數據傳達給主機的進程才是目的。 但是系統中,同時會存在非常多的進程,當數據到達目標主機之后,怎么轉發給目標進程?這就要在網絡的背景下,在系統中標識主機的唯一性。
2. 認識端口號
端口號(port)是傳輸層協議的內容。
端口號是一個 2 字節 16 位的整數,用來標識一個進程,告訴操作系統,當前的這個數據要交給哪一個進程來處理。IP 地址 + 端口號能夠標識網絡上的某一臺主機的某一個進程。因此一個端口號只能被一個進程占用,但是一個進程可以綁定多個端口號。
?我們有這樣的問題:那為什么不使用PID來標識進程的唯一性呢?而是使用端口號來標識?
這是為了避免網絡與系統的耦合度過高,參考LWP和線程的關系。?
端口號范圍劃分:
? 0 - 1023: 知名端口號,HTTP、?FTP、SSH 等這些廣為使用的應用層協議,他們的端口號都是固定的。
?1024 - 65535: 操作系統動態分配的端口號,客戶端程序的端口號就是由操作系統從這個范圍分配的。
3.?認識Socket編程
現在我們知道,IP 地址用來標識互聯網中唯一的一臺主機,port 用來標識該主機上唯一的一個網絡進程,那么IP+Port 就能表示互聯網中唯一的一個進程。在通信的時候,本質是兩個互聯網進程代表人來進行通信,{srcIp, srcPort,dstIp,dstPort} 這樣的 4 元組就能標識互聯網中唯二的兩個進程。所以網絡通信的本質就是進程間通信。我們把 ip+port 叫做套接字 socket。
4. 傳輸層的典型代表
如果我們了解了系統,也了解了網絡協議棧,我們就會清楚,傳輸層是屬于內核的,那么我們要通過網絡協議棧進行通信,必定調用的是傳輸層提供的系統調用來進行的網絡通信。
下圖是對TCP協議和UDP協議的簡略介紹:?
5. 網絡字節序
我們已經知道,內存中的多字節數據相對于內存地址有大端和小端之分,磁盤文件中的多字節數據相對于文件中的偏移地址也有大端小端之分,網絡數據流同樣有大端小端之分。那么如何定義網絡數據流的地址呢?
發送主機通常將發送緩沖區中的數據按內存地址從低到高的順序發出,接收主機把從網絡上接到的字節依次保存在接收緩沖區中,也是按內存地址從低到高的順序保存。因此,網絡數據流的地址應這樣規定:先發出的數據是低地址,后發出的數據是高地址。
TCP/IP 協議規定:網絡數據流應采用大端字節序,即低地址高字節。不管這臺主機是大端機還是小端機,都會按照這個 TCP/IP 規定的網絡字節序來發送/接收數據。如果當前發送主機是小端,就需要先將數據轉成大端,否則就忽略, 直接發送即可。
為使網絡程序具有可移植性,使同樣的 C 代碼在大端和小端計算機上編譯后都能正常運行,可以調用以下庫函數做網絡字節序和主機字節序的轉換。
#include <arpa/inet.h>uint32_t htonl(uint32_t hostlong);
uint16_t htons(uint16_t hostshort);uint32_t ntohl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort);
?6. Socket編程接口
// 創建 socket 文件描述符 (TCP/UDP, 客戶端 + 服務器)
int socket(int domain, int type, int protocol);
//參數:
// domain:需要指定域/協議簇
// type:類型
// protocol:協議,一般置0即可
//返回值:
// 成功返回socket文件描述符,失敗則返回-1// 綁定端口號 (TCP/UDP, 服務器)
int bind(int socket, const struct sockaddr *address,socklen_t address_len);
//參數:
// socket:需要綁定的socket文件描述符
// address:綁定的結構
// address_len:綁定結構的大小
Socket API是一層抽象的網絡編程接口,適用于各種底層網絡協議,如 IPv4、IPv6。以及后面要講的 UNIX Domain Socket。然而,各種網絡協議的地址格式并不相同。
?
IPv4 和 IPv6 的地址格式定義在 netinet/in.h 中,IPv4 地址用 sockaddr_in 結構體表示,包括 16 位地址類型,16 位端口號和32 位 IP 地址。IPv4、IPv6 地址類型分別定義為常數 AF_INET、AF_INET6。這樣,只要取得某種 sockaddr 結構體的首地址,不需要知道具體是哪種類型的 sockaddr 結構體,就可以根據地址類型字段確定結構體中的內容。socket API 可以都用 struct sockaddr * 類型表示,在使用的時候需要強制轉化成 sockaddr_in。這樣的好處是程序的通用性可以接收 IPv4、IPv6以及 UNIX Domain Socket 各種類型的 sockaddr 結構體指針做為參數。