📚?博主的專欄
🐧?Linux???|?? 🖥??C++???|?? 📊?數據結構??|?💡C++ 算法?| 🅒?C 語言? |?🌐?計算機網絡
這是博主計算機網絡的第一篇文章,本文由于是基礎概念了解,引用了大量課件內容
下篇文章:Socket編程UDP
通過計算機網絡的背景,我們可以知道:
計算機是人的工具, 人要協同工作, 注定了網絡的產生是必然的
初識協議
? "協議" 是一種約定。
? 打電話約定電話鈴響的次數的約定
計算機之間的傳輸媒介是光信號和電信號。通過 "頻率" 和 "強弱" 來表示 0 和 1 這樣的信息。要想傳遞各種不同的信息, 就需要約定好雙方的數據格式。
思考: 只要通信的兩臺主機, 約定好協議就可以了么?
? 定好協議, 但是你用頻率表示 01, 我用強弱表示 01, 就好比我用中國話, 你用葡萄牙語一樣, 雖然大家可能遵守的一套通信規則, 但是語言不同, 即是訂好了基本的協議,也是無法正常通信的。
所以, 完善的協議, 需要更多更細致的規定, 并讓參與的人都要遵守。
- 計算機生產廠商有很多;
- 計算機操作系統, 也有很多;
- 計算機網絡硬件設備, 還是有很多;?
- 如何讓這些不同廠商之間生產的計算機能夠相互順暢的通信?
????????就需要有人站出來, 約定一個共同的標準, 大家都來遵守, 這就是 網絡協議;
一般具有定制協議或者標準的資格的組織或者公司都必須是業界公認或者具有江湖地位的組織或者公司。
協議分層
? 協議本質也是軟件, 在設計上為了更好的進行模塊化, 解耦合, 也是被設計成為層狀結構的原因
軟件分層的好處

- 在這個例子中, 我們的"協議"只有兩層:語言層、 通信設備層。
- 但是實際的網絡通信協議, 設計的會更加復雜, 需要分更多的層
- 但是通過上面的簡單例子, 我們是能理解, 分層可以實現解耦合, 讓軟件維護的成本更低
OSI 七層模型
- OSI(Open System Interconnection, 開放系統互連) 七層網絡模型稱為開放式系統互聯參考模型, 是一個邏輯上的定義和規范;(定標準的一批人,不一定是實現標準的一批人)
- 把網絡從邏輯上分為了 7 層. 每一層都有相關、 相對應的物理設備, 比如路由器, 交換機;
- OSI 七層模型是一種框架性的設計方法, 其最主要的功能使就是幫助不同類型的主機實現數據傳輸;
- 它的最大優點是將服務、 接口和協議這三個概念明確地區分開來, 概念清楚,理論也比較完整. 通過七個層次化的結構模型使不同的系統不同的網絡之間實現可靠的通訊;
- 但是, 它既復雜又不實用; 所以我們按照 TCP/IP 四層模型來講解
- 其實在網絡角度, OSI 定的協議 7 層模型其實非常完善, 但是在實際操作的過程中, 會話層、 表示層是不可能接入到操作系統中的, 所以在工程實踐中, 最終落地的是 5 層協議。
計算機(語言也是)世界的規律就是,先描述再組織的設計,層狀結構
TCP/IP 五層(或四層)模型
TCP/IP 是一組協議的代名詞, 它還包括許多協議, 組成了 TCP/IP 協議簇。
TCP/IP 通訊協議采用了 5 層的層級結構, 每一層都呼叫它的下一層所提供的網絡來完成自己的需求。
- 物理層(解決無線通信的問題): 負責光/電信號的傳遞方式. 比如現在以太網通用的網線(雙絞線)、 早期以太網采用的的同軸電纜(現在主要用于有線電視)、 光纖, 現在的 wifi 無線網使用電磁波等都屬于物理層的概念。 物理層的能力決定了最大傳輸速率、 傳輸距離、 抗干擾性等。集線器(Hub)工作在物理層。
- 數據鏈路層: 負責設備之間的數據幀的傳送和識別. 例如網卡設備的驅動、 幀同步(就是說從網線上檢測到什么信號算作新幀的開始)、 沖突檢測(如果檢測到沖突就自動重發)、 數據差錯校驗等工作。?有以太網、 令牌環網, 無線 LAN 等標準。交換機(Switch)工作在數據鏈路層。
- 網絡層: 負責地址管理和路由選擇. 例如在 IP 協議中, 通過 IP 地址來標識一臺主機, 并通過路由表的方式規劃出兩臺主機之間的數據傳輸的線路(路由)。路由器(Router)工作在網路層。
- 傳輸層: 負責兩臺主機之間的數據傳輸. 如傳輸控制協議 (TCP), 能夠確保數據可靠的從源主機發送到目標主機。
- 應用層: 負責應用程序間溝通, 如簡單電子郵件傳輸(SMTP) 、 文件傳輸協議(FTP) 、 網絡遠程訪問協議(Telnet) 等. 我們的網絡編程主要就是針對應用層。
物理層我們考慮的比較少, 我們只考慮軟件相關的內容. 因此很多時候我們直接稱為TCP/IP 四層模型。一般而言:
????????? 對于一臺主機, 它的操作系統內核實現了從傳輸層到物理層的內容;
????????? 對于一臺路由器, 它實現了從網絡層到物理層;
????????? 對于一臺交換機, 它實現了從數據鏈路層到物理層;
????????? 對于集線器, 它只實現了物理層
但是并不絕對. 很多交換機也實現了網絡層的轉發; 很多路由器也實現了部分傳輸層的內容(比如端口轉發)。
為什么要有 TCP/IP 協議?
- 首先, 即便是單機, 你的計算機內部, 其實都是存在協議的, 比如: 其他設備和內存通信, 會有內存協議。 其他設備和磁盤通信, 會有磁盤相關的協議, 比如: SATA, IDE, SCSI 等。 只不過我們感知不到罷了。 而且這些協議都在本地主機各自的硬件中, 通信的成本低、 問題比較少。
- 其次, 網絡通信最大的特點就是主機之間變遠了。 任何通信特征的變化, 一定會帶來新的問題, 有問題就得解決問題, 所以需要新的協議。
發數據不是目的,只是手段、用數據才是目的
- 所以, 為什么要有 TCP/IP 協議?
????????????????本質就是通信主機距離變遠了
什么是 TCP/IP 協議?
? TCP/IP 協議的本質是一種解決方案
? TCP/IP 協議能分層, 前提是因為問題們本身能分層
TCP/IP 協議與操作系統的關系(宏觀上, 怎么實現的)
- 所有的主機上面安裝的操作系統可以不同、但是所有主機上面的協議棧必須按照標準進行相同的實現,這就是為什么不同的主機,可一互相通信的原因。
- 網卡就是底層硬件即物理層,數據鏈路層在驅動程序中、網絡層和傳輸層集成在內核當中。
- 傳輸層最著名的協議是TCP、網絡層最著名的協議是IP、傳輸層(TCP)和網絡層(IP)兩層必須實現在內核當中,無論OS再怎么不同,這部分大家必須遵守協議必須相同。
- 由于TCP/IP是核心,因此就將整個協議統稱為TCP/IP協議。
- 由于整個協議棧即涉及到硬件有涉及到OS,以及用戶,因此這個協議一定是IT各行各業都要進行支持和配合的。
所以究竟什么是協議?
OS 源代碼一般都是用 C/C++語言(偏向于C)寫的。
關于協議的樸素理解: 所謂協議, 就是通信雙方都認識的結構化的數據類型因為協議棧是分層的, 所以, 每層都有雙方都有協議, 同層之間, 互相可以認識對方的協議。
完整的報文分為:協議報文、有效載荷
網絡傳輸基本流程
局域網(以太網為例)通信原理
? 兩臺主機在同一個局域網, 是否能夠直接通信? 是的
? 原理類似上課、老師叫班上的張三(班上只有這個人叫這個名字)、所有人都能聽到,但是只有這個張三回應老師。并且他回應老師,所有人依然能聽到(但是他們不會回應),老師和張三在說話的時候,其他人都能聽到、但是其他人不做處理。教室就相當于一個局域網。老師是src,而張三就是dst其他人發現dst不是他們就不會處理。
? 每臺主機在局域網上, 要有唯一的標識來保證主機的唯一性: mac 地址
認識 MAC 地址(MAC 地址用來識別數據鏈路層中相連的節點)
? 長度為 48 位, 及 6 個字節。?一般用 16 進制數字加上冒號的形式來表示(例如:08:00:27:03:fb:19)
? 在網卡出廠時就確定了, 不能修改。mac 地址通常是唯一的。
(虛擬機中的 mac 地址不是真實的 mac 地址, 可能會沖突; 也有些網卡支持用戶配置 mac 地址)。
臨界資源:以太網
? 以太網中, 任何時刻, 只允許一臺機器向網絡中發送數據
? 如果有多臺同時發送, 會發生數據干擾, 我們稱之為數據碰撞
? 所有發送數據的主機要進行碰撞檢測和碰撞避免
? 沒有交換機的情況下, 一個以太網就是一個碰撞域
? 局域網通信的過程中, 主機對收到的報文確認是否是發給自己的, 是通過目標mac地址判定
? 這里可以試著從系統角度來理解局域網通信原理
初步明白了局域網通信原理, 再來看同一個網段內的兩臺主機進行發送消息的過程
從物理上:用戶A發送數據從上往下再通過物理層主機1的網卡,傳輸到主機2的網卡處,再從下往上傳輸給用戶B
從邏輯上:每一層直接通信(應用層和應用層直接通信)
而其中每層都有協議, 所以當我進行進行上述傳輸流程的時候, 要進行封裝和解包
封裝的時候,向下添加每層的報頭。每一層的報文就是,自己的報頭加上有效載荷。
解包的時候,每一層可以通過報頭判斷是否是同層級之間的傳輸,再將當前層對應報頭去掉,向上傳,也就是除了自己的報頭以外,剩余的報文,也就是該層的有效載荷
下面我們明確一下概念
? 報頭部分, 就是對應協議層的結構體字段, 我們一般叫做報頭
? 除了報頭, 剩下的叫做有效載荷
? 報文 = 報頭 + 有效載荷
如果從上到下來看整個報文的封裝和解包的過程,可以將這個結構看作是一個棧結構,但是每一層都只能看到報文的頭部,向下封裝的時候每一層需要加該層對應的報頭,類似于進棧,向上解包的過程就是去掉報頭,類似出棧。也是一個后進先出的過程,因此我們也將tcp/ip叫做網絡協議棧。
網絡協議的共性
1.數據在網絡中發送的時候,一定最終要在硬件上跑
2.除了應用層,每一層協議,都必須要解決一個問題,自己的有效載荷,應該要交給上層的那一種協議!!這就叫做分用。
然后, 我們在明確一下不同層的完整報文的叫法
? 不同的協議層對數據包有不同的稱謂,在傳輸層叫做段(segment),在網絡層叫做數據報 (datagram),在鏈路層叫做幀(frame).
? 應用層數據通過協議棧發到網絡上時,每層協議都要加上一個數據首部(header),稱為封裝(Encapsulation).
? 首部信息中包含了一些類似于首部有多長, 載荷(payload)有多長, 上層協議是什么等信息.
? 數據封裝成幀后發到傳輸介質上,到達目的主機后每層協議再剝掉相應的首部,根據首部中的 "上層協議字段" 將數據交給對應的上層協議處理
在網絡傳輸的過程中, 數據不是直接發送給對方主機的, 而是先要自頂向下將數據交付給下層協議, 最后由底層發送, 然后由對方主機的底層來進行接受, 再自底向上進行向上交付。
數據包封裝和分用
下圖為數據封裝的過程
下圖為數據分用的過程
學習任何協議, 都要先宏觀上建立這樣的認識:
1. 要學習的協議, 是如何做到解包的? 只有明確了解包, 封包也就能理解。
2. 要學習的協議, 是如何做到將自己的有效載荷, 交付給上層協議的?
網絡中的地址管理 - 認識 IP 地址
IP 協議有兩個版本, IPv4 和 IPv6. 凡是提到 IP 協議, 沒有特殊說明的,默認都是指 IPv4
? IP 地址是在 IP 協議中, 用來標識網絡中不同主機的地址;? 對于 IPv4 來說, IP 地址是一個 4 字節, 32 位的整數;
? 我們通常也使用 "點分十進制" 的字符串表示 IP 地址, 例如 192.168.0.1 ; 用點分割的每一個數字表示一個字節, 范圍是 0 - 255;
跨網段的主機的數據傳輸。數據從一臺計算機到另一臺計算機傳輸過程中要經過一個或多個路由器。
下面是一張示意圖
首先理解一下 IP 地址的意義
? 為什么要去目標主機, 先要走路由器?
? 目的 IP 的意義
路由器連接幾個局域網就有幾個網卡。
然后結合封裝與解包, 體現路由器解包和重新封裝的特點
結論:
- 網絡層(IP,包括網絡層)向上(包括網絡層)看到的所有報文都是一樣的,都至少是IP報文
- IP可以屏蔽底層網絡的差異,所有的網絡都是IP網絡
?對比 IP 地址和 Mac 地址的區別
? (公網)IP 地址在整個路由過程中, 一直不變(目前, 我們只能這樣說明, 后面在修正)
? Mac 地址一直在變
? 目的 IP 是一種長遠目標, Mac 是下一階段目標, 目的 IP 是路徑選擇的重要依據, mac 地址是局域網轉發的重要依據
為什么:IP地址是最終目標,MAC地址是為了到達最終目標,中途以及最后的目標,階段性的目標 。
提煉 IP 網絡的意義和網絡通信的宏觀流程
IP 網絡層存在的意義: 提供網絡虛擬層, 讓世界的所有網絡都是 IP 網絡, 屏蔽最底層網絡的差異
編程方面的鋪墊:
Socket 編程預備
1. 理解源 IP 地址和目的 IP 地址
IP 在網絡中, 用來標識主機的唯一性
? 注意: 后面我們會講 IP 的分類,后面會詳細闡述 IP 的特點
但是這里要思考一個問題: 數據傳輸到主機是目的嗎? 不是的。 因為數據是給人用的。 比如: 聊天是人在聊天, 下載是人在下載, 瀏覽網頁是人在瀏覽?
但是人是怎么看到聊天信息的呢? 怎么執行下載任務呢? 怎么瀏覽網頁信息呢? 通過啟動的 qq, 迅雷, 瀏覽器。而啟動的 qq, 迅雷, 瀏覽器都是進程。 換句話說, 進程是人在系統中的代表, 只要把數據給進程, 人就相當于就拿到了數據。
所以: 數據傳輸到主機不是目的, 而是手段。 到達主機內部, 在交給主機內的進程,才是目的。
但是系統中, 同時會存在非常多的進程, 當數據到達目標主機之后, 怎么轉發給目標進程? 這就要在網絡的背景下, 在系統中, 標識主機的唯一性
端口號port:是傳輸層協議的內容
網絡通信的本質就是進程間通信
? 端口號是一個 2 字節 16 位的整數;?
? 端口號用來標識一個進程, 告訴操作系統, 當前的這個數據要交給哪一個進程來處理;
? IP 地址(IPA +PORTA) + 端口號 能夠標識網絡上的 某一臺主機的某一個(唯一一個)進程 ;????????因此網絡間通信的本質就是進程間通信,而今天他們在進行通信的時候就看到了同一份資源,也就是網路
? 一個端口號只能被一個進程占用
任何通信就是兩個進程在進行通信。
通過端口號進行轉發給指定的進程,換句話說,我們未來寫代碼的時候,服務器啟動的時候,就一定要和指定的port(具有唯一性才是最關鍵的)進行關聯。
在系統中所有進程都有自己的pid,但是不是所有的進程都想進行網絡通信,因此只有需要通信的進程需要端口號,也就是有端口號的進程才會進行網絡通信。
我們將基于IP和PORT的網絡間的通信方式就叫做socket(套接字)通信
? 0 - 1023: 知名端口號, HTTP, FTP, SSH 等這些廣為使用的應用層協議, 他們的端口號都是固定的.
? 1024 - 65535: 操作系統動態分配的端口號. 客戶端程序的端口號, 就是由操作系統從這個范圍分配的
理解源端口號和目的端口號
傳輸層協議(TCP 和 UDP)的數據段中有兩個端口號, 分別叫做源端口號和目的端口號。就是在描述 "數據是誰發的, 要發給誰";
? 綜上, IP 地址用來標識互聯網中唯一的一臺主機, port 用來標識該主機上唯一的一個網絡進程
? IP+Port 就能表示互聯網中唯一的一個進程
? 所以, 通信的時候, 本質是兩個互聯網進程代表人來進行通信, {srcIp,srcPort, dstIp, dstPort}這樣的 4 元組就能標識互聯網中唯二的兩個進程
? 所以, 網絡通信的本質, 也是進程間通信
? 我們把 ip+port 叫做套接字 socket
傳輸層的典型代表
? 如果我們了解了系統, 也了解了網絡協議棧, 我們就會清楚, 傳輸層是屬于內核的, 那么我們要通過網絡協議棧進行通信, 必定調用的是傳輸層提供的系統調用, 來進行的網絡通信。
認識 TCP 協議
此處我們先對 TCP(Transmission Control Protocol 傳輸控制協議)有一個直觀的認識;后面我們再詳細討論 TCP 的一些細節問題.
? 傳輸層協議
? 有連接
? 可靠傳輸
? 面向字節流
認識 UDP 協議
此處我們也是對 UDP(User Datagram Protocol 用戶數據報協議)有一個直觀的認識; 后面再詳細討論.
? 傳輸層協議
? 無連接
? 不可靠傳輸
? 面向數據報
網絡字節序
發送主機通常將發送緩沖區中的數據按內存地址從低到高的順序發出;
? 接收主機把從網絡上接到的字節依次保存在接收緩沖區中,也是按內存地址從低到高的順序保存;
? 因此,網絡數據流的地址應這樣規定:先發出的數據是低地址,后發出的數據是高地址.
? TCP/IP 協議規定,網絡數據流應采用大端字節序,即低地址高字節.
? 不管這臺主機是大端機還是小端機, 都會按照這個 TCP/IP 規定的網絡字節序來發送/接收數據;
? 如果當前發送主機是小端, 就需要先將數據轉成大端; 否則就忽略, 直接發送即可
也就是說,接收方,只知道自己接收的是大端,因此要預先處理好
為使網絡程序具有可移植性,使同樣的 C 代碼在大端和小端計算機上編譯后都能正常運行,可以調用以下庫函數做網絡字節序和主機字節序的轉換
h-->host? ?n--->net,l ---> long(32位),short(16位) ---> 主機轉網絡、網絡轉主機。?
? 這些函數名很好記,h 表示 host,n 表示 network,l 表示 32 位? 長整數,s 表示 16 位短整數。
? 例如 htonl 表示將 32 位的長整數從主機字節序轉換為網絡字節序,例如將 IP 地址轉換后準備發送。
? 如果主機是小端字節序,這些函數將參數做相應的大小端轉換然后返回;
? 如果主機是大端字節序,這些函數不做轉換,將參數原封不動地返回。
socket 編程接口
socket()
:創建套接字
功能:創建一個套接字,用于后續的網絡通信。
語法:
int socket(int domain, int type, int protocol);
參數:
?domain
:協議族,如AF_INET
(IPv4)或AF_INET6
(IPv6)。
type
:套接字類型,如SOCK_STREAM
(TCP)或SOCK_DGRAM
(UDP)。
protocol
:協議,通常設置為0
,由type
決定。?返回值:成功返回套接字描述符,失敗返回
-1
。
bind()
:綁定套接字到本地地址
功能:將套接字綁定到本地的 IP 地址和端口號。
語法:
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
參數:
sockfd
:由socket()
創建的套接字描述符。
addr
:指向sockaddr
結構的指針,包含本地地址信息。
addrlen
:addr
的長度。返回值:成功返回
0
,失敗返回-1
。
listen()
:監聽連接請求
功能:將套接字置于監聽狀態,等待客戶端的連接請求。
語法:
int listen(int sockfd, int backlog);
參數:
sockfd
:由socket()
創建的套接字描述符。
backlog
:未完成連接隊列的最大長度。返回值:成功返回
0
,失敗返回-1
。
accept()
:接受客戶端連接
功能:接受一個客戶端的連接請求,創建一個新的套接字用于與該客戶端通信。
語法:
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
參數:
sockfd
:由socket()
創建的套接字描述符。
addr
:指向sockaddr
結構的指針,用于存儲客戶端的地址信息。
addrlen
:addr
的長度。返回值:成功返回新的套接字描述符,失敗返回
-1
。
recv()
或 read()
:接收數據
功能:從客戶端接收數據。
語法:
recv()
:ssize_t recv(int sockfd, void *buf, size_t len, int flags);
read()
:ssize_t read(int fd, void *buf, size_t count);
參數:
sockfd
:套接字描述符。
buf
:存儲接收到的數據的緩沖區。
len
或count
:緩沖區的大小。
flags
:控制接收行為的標志,通常為0
。返回值:成功返回接收到的字節數,失敗返回
-1
,連接關閉返回0
。
send()
或 write()
:發送數據
功能:向客戶端發送數據。
語法:
send()
:ssize_t send(int sockfd, const void *buf, size_t len, int flags);
write()
:ssize_t write(int fd, const void *buf, size_t count);
參數:
sockfd
:套接字描述符。
buf
:要發送的數據緩沖區。
len
或count
:要發送的數據長度。
flags
:控制發送行為的標志,通常為0
。返回值:成功返回發送的字節數,失敗返回
-1
。
close()
:關閉套接字
功能:關閉套接字,釋放資源。
語法:
int close(int sockfd);
參數:
sockfd
,套接字描述符。返回值:成功返回
0
,失敗返回-1
。
客戶端
/ / 創建 socket 文件描述符 (TCP/UDP, 客戶端 + 服務器) int socket(int domain, int type, int protocol);
// 綁定端口號 (TCP/UDP, 服務器)
int bind(int socket, const struct sockaddr *address, socklen_t address_len);
// 開始監聽 socket (TCP, 服務器) int listen(int socket, int backlog);
// 接收請求 (TCP, 服務器)
int accept(int socket, struct sockaddr* address, socklen_t* address_len);
// 建立連接 (TCP, 客戶端)
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen
sockaddr結構
socket API 是一層抽象的網絡編程接口,適用于各種底層網絡協議,如 IPv4、 IPv6,以及后面要講的 UNIX Domain Socket. 然而, 各種網絡協議的地址格式并不相同
根據所傳的前16位地址中的標志位,判斷是要網絡通信還是本地通信
我們一般就使用sockaddr_in 結構,實際上,可以聯想到sockaddr就是基類,另外兩個也就是他的派生類,子類,也就是:多態
- 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 結構體指針做為參數;
結語:
? ? ? ?隨著這篇博客接近尾聲,我衷心希望我所分享的內容能為你帶來一些啟發和幫助。學習和理解的過程往往充滿挑戰,但正是這些挑戰讓我們不斷成長和進步。我在準備這篇文章時,也深刻體會到了學習與分享的樂趣。 ? ?
? ? ? ? ?在此,我要特別感謝每一位閱讀到這里的你。是你的關注和支持,給予了我持續寫作和分享的動力。我深知,無論我在某個領域有多少見解,都離不開大家的鼓勵與指正。因此,如果你在閱讀過程中有任何疑問、建議或是發現了文章中的不足之處,都歡迎你慷慨賜教。
? ? ? ? 你的每一條反饋都是我前進路上的寶貴財富。同時,我也非常期待能夠得到你的點贊、收藏,關注這將是對我莫大的支持和鼓勵。當然,我更期待的是能夠持續為你帶來有價值的內容。