当初我要是这么学习计算机网络就好了「附图文解析」
文章目錄
- 1.概念篇
- 2. 網絡編程套接字
- 2.1 UDP
- 2.2 TCP
- 3. 理論知識「八股文」
- 3.1 應用層
- 3.1.2 DNS
- 3.1.3 瀏覽器中輸入url后按下回車發生了什么
- 3.1.4 NAT 技術
- 3.1.4.2 NAT IP轉換過程
- 3.1.4.3 NAPT
- 3.1.4.4 NAT 技術缺陷
- 3.1.4.4 NAT 和 代理服務器
- 3.2 傳輸層
- 3.2.1 UDP
- 3.2.1.1 UDP首部格式「使用注意」
- 3.2.1.2 UDP 校驗和
- 3.2.1.3 UDP特點
- 3.2.1.4 面向數據報
- 3.2.1.5 UDP緩沖區
- 3.2.1.6 基于UDP層的協議
- 3.2.2 TCP
- 3.2.2.1 TCP首部格式
- 3.2.2.2 確認應答
- 3.2.2.3 超時重傳
- 3.2.2.4 連接管理「面試問的最多」
- 3.2.2.5 滑動窗口
- 3.2.2.6 流量控制
- 3.2.2.7 擁塞控制
- 3.2.2.8 延遲應答
- 3.2.2.9 捎帶應答
- 3.2.2.10 面向字節流
- 3.2.2.11 沾包問題
- 3.2.2.12 TCP異常情況
- 3.2.2.13 TCP小結
- 3.2.2.14 基于TCP層的協議
- 3.2.3 TCP與UDP對比
- 3.3 網絡層
- 3.3.1 IP協議
- 3.3.4 網段劃分
- 3.3.5 IP地址的數量限制
- 3.3.6 私有IP地址和公網IP地址
- 3.3.7 路由
- 3.4 數據鏈路層
- 3.4.1 認識以太網
- 3.4.1.1 以太網幀格式
- 3.4.1.2 認識MAC地址
- 3.4.2 對比理解MAC地址和IP地址
- 3.4.3 認識MTU
- 3.4.3.1 MTU對IP協議的影響
- 3.4.3.1 MTU對UDP協議的影響
- 3.4.3.1 MTU對TCP協議的影響
- 3.4.4 ARP協議
- 3.4.4.1 ARP協議的作用
- 3.4.4.2 ARP協議工作流程
- 3.5 總結
1.概念篇
交換機: 一些學校,公司搭建的一個網絡的時候就會用到交換機。交換機就是把多個主機構成一個網絡。
集線器: 上古時期老設備,網線分叉,同一時刻只能有一根網線工作
路由器: 解決集線器同一時刻只能有一根網線工作的弊端,可以使得所有設備都有網絡「路由器不僅能組成一個局域網,同時也鏈接了兩個局域網的功能,讓局域網之間由交換數據的功能」
局域網: 局部組建的一種私有網絡。
廣域網: 通過路由器將多個局域網連接起來,在物理范圍上組建成很大范圍的網絡。廣域網內部的局域網都屬于其子網
局域網和廣域網: 沒有特定的概念,都是相對而言。「公司的局域網一定比家庭的廣域網范圍更大」
網絡通信: 進行網絡數據傳輸。更詳細點是:網絡主機中不同進程間機遇網絡傳輸數據
路由器上面有兩類網口
LAN(Local Area NetWork) 口:連接下級網絡設備「路由器,電腦,電視等」
WAN 口(Wide Area NetWork):連接上級路由器「光貓」
路由器和交換機有什么區別:
「上古時代的面試題角度」:實際使用角度來看,交換機和路由器已經沒區別了(路由器功能越來越強大)
「學校考試角度:」交換機負責二層轉發,功能是組建一個局域網(二層指的是數據鏈路層);路由器負責三層轉發,功能是連接兩個局域網(三層指的是網絡層)
IP地址
概念:4字節,定位主機的網絡地址網絡層「就像我們發送快遞一樣,需要知道對方的收貨地址才能把包裹送達」
格式:被分割為 4 個 8位二進制數,通常用點分進制表示。a.b.c.d 每個數據范圍[0, 255]
私有地址:互聯網上不使用,而被用在局域網絡中的地址
A類
- 第1字節為個網絡地址,其它3個為主機地址。第1個字節最高位為固定位為0
- 范圍:1.0.0.1-126.255.255.254
- 私有地址:10.0.0.0-10.255.255.255
- 保留地址:127.0.0.1-127.255.255.255『主要利用內部網絡通信性能高,方便測試一些網絡誠信通信使用』
B類
- 第1和第2字節為網絡地址,其它2個位主機地址。第1個字節前兩位固定為10
- 范圍:128.0.0.1-191.255.255.254
- 私有地址:172.16.0.0-172.31.255.255
- 保留地址:當IP是自動獲取但又沒有DHCP服務器,就從『169.254.0.0-169.254.255.255』中臨時獲得一個IP地址
C類
- 第1,第2,第3字節為網絡地址,剩下的一個是主機地址。第1個字節的前3位固定為110
- 范圍:192.0.0.1-192.168.255.254
- 私有地址:192.168.0.0-192.168.255.255
IP地址解決了網絡通信時定位網絡主機的問題,但是數據傳輸到主機后由哪個進城來管理這些數據呢?這就需要用到 端口號
MAC地址
6字節,識別數據鏈層中相連的的節點
在網卡出廠的時候就設置了的不能被修改。MAC地址是唯一的「虛擬機中的MAC地址并不是真正的MAC,也有些網卡支持用戶配置MAC地址」
端口號
概念:標記主機中發送數據,接收數據的進程
范圍:「0-65535」
注意事項:兩個不同的進程不能綁定同一個端口,但一個進程可以綁定多個端口號「兩個收貨地址不能同時接受同一個包裹,但一個收貨地址可以接受多個不同的包裹」
了解:一個進程啟動成功后,系統會隨機分配一個端口號「啟動端口」,程序代碼中需要綁定一個端口來進行收發數據。
有了IP地址,端口,就可以定位到網絡中唯一的一個進程。但存在一個問題:網絡通信是基于光電信號,高低電平轉換為二進制數據01傳輸的,我們如何知道對方送的什么數據呢?「圖片,視屏,文本,音頻對應的數據格式,編碼方式也都不同」此時就需要有一個 協議 來規定雙方發送接收數據餓。
認識協議
網絡協議是網絡通信 經過的所有設備 都要遵從的一組約定,規則。如怎么連接連接,怎么互相識別。只有遵從這個規定,多臺計算機之間才能互相通信交流。
三要素組成:
-
語法:數據與控制結構信息的格式「打電話約定雙方使用:普通話」
-
語義:需要發出何種控制信息,何種動作,何種響應「女朋友:喝奶茶;男朋友:走一起」
主要用來說明通信雙方應當怎么做。用于協調和差錯處理
-
時許:時間實現順序的詳細說明「打電話的時候,男生發起,聊天…,然后由女生掛斷」
主要定義了何時通信,先講什么,后講什么,講話速度。比如采用同步傳輸還是異步傳輸
知名協議的默認端口
系統端口范圍是「0,65535」,知名端口「0,1023」,這些端口都是預留給服務端程序來綁定廣泛使用的應用層協議。比如:
21:FTP
22:SSH
23:Telnet
80:HTTP
443:HTTPS
服務器也可以使用「1024,65535」范圍內的端口來定義知名端口
五元組
在網絡通信中用 五元組 來標示一個網絡通信
協議分層
把一個大的協議逐個拆分出來形成一個小協議
分層作用:類似于達到面向接口編程這樣的效果:定義好兩層之間的接口規范,讓雙方遵守這個規范來對接數據。便于日后的維護和更新。
OSI七層模型
只存在于教科書中「越往下越接近硬件設備,越往上越接近應用程序」
每一層都呼叫它的下一層來完成需求
| 應用層 | 應用程序間溝通,如簡單的電子郵件傳輸SMTP,文件傳輸FTP,網絡遠程訪問Telnet「網絡編程主要在應用層,拿到數據之后你要干啥…」 |
| 傳輸層 | 兩臺主機之間的數據傳輸。如TCP,UDP「端到端:消費者和商家只關注某個快遞是不是收到了」 |
| 網絡層 | 管理和路由選擇。在IP中識別主機,并通過路由表的方式規劃處兩臺主機之間數據傳輸路線「點到點:快遞公司,怎樣運輸才高效」 |
| 數據鏈路層 | 設備之間數據幀的傳送和識別「幀同步,沖突檢測,差錯校驗」 |
| 物理層 | 光電信號傳輸方式 |
傳輸層的端到端:只關注起點/終點,不關注中間過程
網絡層的點到點:傳輸過程中經歷的節點,需要關注中間過程的
網絡設備所在分層
主機:操作系統內核實現了從物理層到傳輸層「TCP/IP下四層」
路由器:實現了網絡層到物理層「下三層」
交換機:數據鏈路層到物理層「下兩層」
集線器:物理層
注意:這里說的是傳統意義上的交換機和路由器,也稱為二層交換機(工作在TCP/IP五層模型的下兩層)、三層路由器(工作在TCP/IP五層模型的下三層)。
隨著現在網絡設備技術的不斷發展,也出現了很多3層或4層交換機,4層路由器。我們以下說的網絡設 備都是傳統意義上的交換機和路由器。
封裝和分用
- 不同的協議層對數據包有不同的稱謂。傳輸層:段「segment」;網絡層:數據報「datagram」;鏈路層:幀「frame」
- 應用層數據通過協議棧發送出去的時候,每層協議都要加一個首部「header」,稱為封裝「Encapsulation」
- 首部:包含首部有多長,載荷多大,上層協議…
- 數據封裝成幀后發到傳輸介質上,到達目的主機后每層都會剝掉首部,根據首部中上層協議字段,將數據交給對應的上層處理
數據封裝圖
數據分用圖
封裝和分用4不僅僅存在于發送方和接收方,中間設備「路由器/交換機」也會針對數據進行封裝和分用
通常情況下:
交換機:只是封裝分用到數據鏈路層就結束
A 的數據發給 交換機,交換機 物理層再進一步處理交給 數據鏈路層,數據鏈路層就針對這里數據解析并重新打包
路由器:只是封裝分用到網絡層就結束
網絡層要根據這里的目的地址來規劃接下來的傳輸路線,規劃好了之后再重新交給數據鏈路層和物理層進行封裝分用
2. 網絡編程套接字
| 有鏈接「打電話」 | 無連接「發微信」 |
| 可靠傳輸「叮叮已讀」 | 不可靠傳輸「叮叮未讀」 |
| 面向字節流 | 面向數據報 |
| 全雙工 | 全雙工 |
全雙工: 一個socket既可以用來發送也可以用來接收
半雙工: 只能用來發送或者只能用來接收
2.1 UDP
DatagramSocket() 構造方法
UDP Socket 發送/接受數據
| DatagramSocket() | 創建一個套接字對象,綁定一個隨機端口 |
| DatagramSocket(int port) | 創建一個套接字對象,綁定一個指定端口 |
DatagramSocket() 方法
| void receive(DatagramPacket p) | 從此套接字p只接收數據,如果沒有收到數據就阻塞等待 |
| void send(DatagramPacket p) | 從此套接字p只接發數據,如果沒有發送數據就阻塞等待 |
| void close() | 關閉此數據報套接字 |
DatagramPacket() 構造方法
UDP Socket 發送/接受數據
| DatagramPacket(byte[] buf, int length) | DatagramPacket把接收指定長度length的數據保存在字節數組buf中 |
| DatagramPacket(byte[] buf, int length, SocketAddress address) | DatagramPacket把長度length的字節數組buf數據發送到address |
DatagramPacket() 方法
| InetAddress getAddress() | 從接受的數據報中獲取發送端 IP地址;從發送的數據報中獲取接收端 IP地址 |
| int getPort() | 從接受的數據報中獲取發送端 端口;從發送的數據報中獲取接收端 端口 |
| byte[] getData() | 獲取數據報中的數據 |
構造UDP發送數據報的時候,需要傳入SocketAddress,該對象可以使用 InetSocketAddress 來創建
InetSocketAddress
| InetSocketAddress(InetAddress, int port) | 創建一個 Socket 對象,包含 IP地址 和 端口 |
服務端
package net;import java.io.IOException; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.SocketException;public class UdpEchoServer {private DatagramSocket socket = null;// 此處指定的端口就是服務器自己的端口,ip 并沒有指定,相當于市 0.0.0.0(綁定到當前主機的所有網卡上)public UdpEchoServer(int port) throws SocketException {this.socket = new DatagramSocket(port);}public void start() throws IOException {while (true) {// 1.讀取客戶端發來的請求,客戶端發來請求之前這里的receive是阻塞的DatagramPacket requestPacket = new DatagramPacket(new byte[4096], 4096);socket.receive(requestPacket);// 把收到的數據進行提取String request = new String(requestPacket.getData(), 0, requestPacket.getLength());String response = process(request);// 2.處理請求DatagramPacket responsePacket = new DatagramPacket(response.getBytes(), response.getBytes().length, requestPacket.getSocketAddress());// 3.出列結果返回給客戶端socket.send(responsePacket);System.out.printf("[%s, %d]req:%s; resp:%s\n", requestPacket.getAddress(), requestPacket.getPort(), request, response);}}public String process(String request) {return request;}public static void main(String[] args) throws IOException {UdpEchoServer server = new UdpEchoServer(9090);server.start();} }客戶端
package net;import java.io.IOException; import java.net.*; import java.util.Scanner;public class UdpEchoClient {private DatagramSocket socket = null;private String serverIP;private int serverPort;/*此處指定的 ip 和 port 是服務器的 ip 和 port客戶端是不需要指定自己的 ip 和 端口客戶端 ip 就是本機 ip,客戶端的端口就是操作系統自動分配*/public UdpEchoClient(String ip, int port) throws SocketException {this.serverIP = ip;this.serverPort = port;/*此處構造這個對象的時候不需要填參數了:綁定這個指定的端口(客戶端是無需綁定端口的,端口系統給的)前面記錄的服務器 ip 和 port 是為了后面發送數據給服務器的準備工作*/socket = new DatagramSocket();}public void start() throws IOException {Scanner scanner = new Scanner(System.in);while (true) {// 1. 從控制臺讀取用戶輸入System.out.print("-> ");String request = scanner.nextLine();// 2. 把數據構成 UDP 數據報,發送給服務器if (request.equals("exit")) {break;}DatagramPacket requestPacket = new DatagramPacket(request.getBytes(), request.getBytes().length, InetAddress.getByName(serverIP), serverPort);socket.send(requestPacket);// 3. 從服務器讀取響應數據DatagramPacket responsePacket = new DatagramPacket(new byte[4096], 4096);socket.receive(responsePacket);// 4. 把響應數據進行解析并顯示String response = new String(responsePacket.getData(), 0, responsePacket.getLength());System.out.printf("req:%s; resp:%s\n", request, response);}}public static void main(String[] args) throws IOException {UdpEchoClient client = new UdpEchoClient("127.0.0.1", 9090);client.start();} }帶有 “翻譯功能” 的服務端
package net;import java.io.IOException; import java.net.SocketException; import java.util.HashMap;public class UdpDictServer extends UdpEchoServer {private HashMap<String, String> dict = new HashMap<>();public UdpDictServer(int port) throws SocketException {super(port);dict.put("cat", "小貓");dict.put("dog", "小狗");dict.put("pig", "小豬");dict.put("fuck", "臥槽");}@Overridepublic String process(String req) {return dict.getOrDefault(req, "沒有找到翻譯");}public static void main(String[] args) throws IOException {UdpDictServer server = new UdpDictServer(9090);server.start();} }2.2 TCP
客戶端
package net;import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.PrintWriter; import java.net.Socket; import java.util.Scanner;// 和UDP類似,只是多了個連接過程 public class TcpEchoClient {private Socket socket = null;public TcpEchoClient(String serverIP, int serverPort) throws IOException {// 客戶端何時和服務器建立連接:在實例化 Socket 的時候this.socket = new Socket(serverIP, serverPort);}public void start() {System.out.println("啟動客戶端");try (InputStream inputStream = socket.getInputStream(); OutputStream outputStream = socket.getOutputStream()) {Scanner scanner = new Scanner(System.in);Scanner respScanner = new Scanner(inputStream);while (true) {// 1. 從控制臺讀取用戶輸入System.out.print("-> ");String request = scanner.nextLine();// 2. 把用戶輸入的數據,構造請求,發送給服務器PrintWriter writer = new PrintWriter(outputStream);writer.println(request);writer.flush();// 3. 從服務器讀取響應String response = respScanner.nextLine();// 4. 把響應習顯示出來System.out.printf("req:%s, resp:%s\n", request, response);}} catch (IOException e) {e.printStackTrace();}}public static void main(String[] args) throws IOException {TcpEchoClient client = new TcpEchoClient("127.0.0.1", 9090);client.start();} }服務端
package net;import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.PrintWriter; import java.net.ServerSocket; import java.net.Socket; import java.util.Scanner;public class TcpEchoServer {private ServerSocket serverSocket = null;public TcpEchoServer(int port) throws IOException {this.serverSocket = new ServerSocket(port);}public void start() throws IOException {System.out.println("服務器啟動!");while (true) {// 需要建立好連接,再進行數據通信Socket clientSocket = serverSocket.accept();// 和客戶端進行通信了,通過這個方法來處理整個的連接過程processConnection(clientSocket);}}private void processConnection(Socket clientSocket){System.out.printf("[%s:%d] 客戶端建立連接\n", clientSocket.getInetAddress().toString(), clientSocket.getPort());/*需要和客戶端進行通信,和文件操作的字節流一模一樣通過 socket 對象拿到 輸入流 對象,對這個 輸入流 就相當于從網課讀數據通過 socket 對象拿到 輸出流 對象,對這個 輸出流 就相當于往網卡寫數據*/try (InputStream inputStream = clientSocket.getInputStream(); OutputStream outputStream = clientSocket.getOutputStream()) {Scanner scanner = new Scanner(inputStream);while (true) {// 1.根據請求并解析if (!scanner.hasNext()) {System.out.printf("[%s:%d] 客戶端退出鏈接\n", clientSocket.getInetAddress(), clientSocket.getPort());break;}String request = scanner.nextLine();// 2.根據請求計算響應String response = process(request);// 3.把響應寫入到客戶端PrintWriter writer = new PrintWriter(outputStream);writer.println(response);// 為了保證寫入的數據能夠及時返回給客戶端,手動加上一個刷新緩沖區的操作writer.flush();System.out.printf("[%s:%d]req:%s, resp:%s\n", clientSocket.getInetAddress(), clientSocket.getPort(), request, response);}} catch (IOException e) {e.printStackTrace();} finally {// 此處的 clientSocket 的關閉是非常有必要的try {clientSocket.close();} catch (IOException e) {e.printStackTrace();}}}public String process(String request) {return request;}public static void main(String[] args) throws IOException {TcpEchoServer server = new TcpEchoServer(9090);server.start();} }翻譯功能的客戶端
package net;import java.io.IOException; import java.util.HashMap;public class TcpDictEchoServer extends TcpEchoServer {private HashMap<String, String> dict = new HashMap<>();public TcpDictEchoServer(int port) throws IOException {super(port);dict.put("cat", "小貓");dict.put("dog", "小狗");dict.put("pig", "小豬");dict.put("fuck", "臥槽");}@Overridepublic String process(String request) {return dict.getOrDefault(request, "翻譯失敗");}public static void main(String[] args) throws IOException {TcpDictEchoServer server = new TcpDictEchoServer(9090);server.start();} }利用多線程解決普通客戶端一次只能鏈接一個用戶的BUG
package net;import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.PrintWriter; import java.net.ServerSocket; import java.net.Socket; import java.util.Scanner;public class TcpThreadEchoServer {private ServerSocket serverSocket = null;public TcpThreadEchoServer(int port) throws IOException {this.serverSocket = new ServerSocket(port);}public void start() throws IOException {System.out.println("服務器啟動!");while (true) {// 需要建立好連接,再進行數據通信Socket clientSocket = serverSocket.accept();// 和客戶端進行通信了,通過這個方法來處理整個的連接過程//「改動這里,把每次建立好的鏈接創建一個新的線程來處理」Thread t = new Thread(() -> {processConnection(clientSocket);});t.start();}}private void processConnection(Socket clientSocket){System.out.printf("[%s:%d] 客戶端建立連接\n", clientSocket.getInetAddress().toString(), clientSocket.getPort());/*需要和客戶端進行通信,和文件操作的字節流一模一樣通過 socket 對象拿到 輸入流 對象,對這個 輸入流 就相當于從網課讀數據通過 socket 對象拿到 輸出流 對象,對這個 輸出流 就相當于往網卡寫數據*/try (InputStream inputStream = clientSocket.getInputStream(); OutputStream outputStream = clientSocket.getOutputStream()) {Scanner scanner = new Scanner(inputStream);while (true) {// 1.根據請求并解析if (!scanner.hasNext()) {System.out.printf("[%s:%d] 客戶端退出鏈接\n", clientSocket.getInetAddress(), clientSocket.getPort());break;}String request = scanner.nextLine();// 2.根據請求計算響應String response = process(request);// 3.把響應寫入到客戶端PrintWriter writer = new PrintWriter(outputStream);writer.println(response);// 為了保證寫入的數據能夠及時返回給客戶端,手動加上一個刷新緩沖區的操作writer.flush();System.out.printf("[%s:%d]req:%s, resp:%s\n", clientSocket.getInetAddress(), clientSocket.getPort(), request, response);}} catch (IOException e) {e.printStackTrace();} finally {// 此處的 clientSocket 的關閉是非常有必要的try {clientSocket.close();} catch (IOException e) {e.printStackTrace();}}}private String process(String request) {return request;}public static void main(String[] args) throws IOException {TcpThreadEchoServer server = new TcpThreadEchoServer(9090);server.start();} }利用線程池進行優化
package net;import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.PrintWriter; import java.net.ServerSocket; import java.net.Socket; import java.util.Scanner; import java.util.concurrent.Executor; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors;public class TcpThreadPoolEchoServer {private ServerSocket serverSocket = null;public TcpThreadPoolEchoServer(int port) throws IOException {this.serverSocket = new ServerSocket(port);}public void start() throws IOException {System.out.println("服務器啟動!");ExecutorService pool = Executors.newCachedThreadPool();while (true) {// 需要建立好連接,再進行數據通信Socket clientSocket = serverSocket.accept();// 和客戶端進行通信了,通過這個方法來處理整個的連接過程//「把 processConnection 作為一個任務,交給線程池處理」pool.submit(() -> {processConnection(clientSocket);});}}private void processConnection(Socket clientSocket) {System.out.printf("[%s:%d] 客戶端建立連接\n", clientSocket.getInetAddress().toString(), clientSocket.getPort());/*需要和客戶端進行通信,和文件操作的字節流一模一樣通過 socket 對象拿到 輸入流 對象,對這個 輸入流 就相當于從網課讀數據通過 socket 對象拿到 輸出流 對象,對這個 輸出流 就相當于往網卡寫數據*/try (InputStream inputStream = clientSocket.getInputStream(); OutputStream outputStream = clientSocket.getOutputStream()) {Scanner scanner = new Scanner(inputStream);while (true) {// 1.根據請求并解析if (!scanner.hasNext()) {System.out.printf("[%s:%d] 客戶端退出鏈接\n", clientSocket.getInetAddress(), clientSocket.getPort());break;}String request = scanner.nextLine();// 2.根據請求計算響應String response = process(request);// 3.把響應寫入到客戶端PrintWriter writer = new PrintWriter(outputStream);writer.println(response);// 為了保證寫入的數據能夠及時返回給客戶端,手動加上一個刷新緩沖區的操作writer.flush();System.out.printf("[%s:%d]req:%s, resp:%s\n", clientSocket.getInetAddress(), clientSocket.getPort(), request, response);}} catch (IOException e) {e.printStackTrace();} finally {// 此處的 clientSocket 的關閉是非常有必要的try {clientSocket.close();} catch (IOException e) {e.printStackTrace();}}}private String process(String request) {return request;}public static void main(String[] args) throws IOException {TcpThreadPoolEchoServer server = new TcpThreadPoolEchoServer(9090);server.start();} }3. 理論知識「八股文」
3.1 應用層
3.1.2 DNS
DNS其實就是一個域名解析作用「比如https:www.baidu.com對應的IP地址是180.101.49.41」
這樣的點分十進制IP大家很難記住于是就起了個朗朗上口的 www.baidu.com 方便人們記憶,www.baidu.com就是一個域名對應的IP地址是180.101.49.41。把 IP 解析成對應域名 www.baidu.com 就是域名解析
DNS底層使用UDP解析
瀏覽器會緩存DNS結果
DNS是應用層協議
3.1.3 瀏覽器中輸入url后按下回車發生了什么
3.1.4 NAT 技術
我們也知道目前IPV4數量已經不夠用,雖然IPV6在我國正在大力推崇中,但是目前的主力軍依舊是IPV4+NAT
3.1.4.2 NAT IP轉換過程
NAT IP轉換過程
就是子網內部的設備訪問外網的時候,路由器會把子網內部的請求IP全局替換為路由器內部一個 全球IP地址 作為出口去訪問外網IP「圖中:10.0.0.10-----『202.244.174.37』----->163.221.120.9」
外網IP響應給路由器全局IP數據時候,路由器內有一個 路由表 就會把對應的數據響應給子網內部對應的私有IP「圖中:163.221.120.9----->『202.244.174.37』----->10.0.0.10」
當首次訪問外網IP「163.221.120.9」的時候就會自動生成這樣的一個映射關系,以便于后續直接使用。當結束連接的時候機會自動刪除
3.1.4.3 NAPT
問題來了:子網中多個主機「PCA/B/C」訪問同一個外網IP「163.22.120.9」,服務器響應數據卻發現目的IP都是相同的「202.244.173.37」,該如何區分子網內部的設備呢?
IP+Port
這種映射關系也是TCP初次建立連接的時候生成的,由NAT路由器自動維護,當斷開連接的時候就會自動刪除
3.1.4.4 NAT 技術缺陷
- 外界無法訪問內部「162.221.120.9無法訪問子網內的10.0.0.10/11/12這些設備,也就是為何你的電腦無法訪問我電腦上的127.0.0.1的原因」
- 路由表的維護,創建,銷毀也是有一定的開銷
- 通信過程中一旦有NAT設備異常,內網的設備連接都會斷開
3.1.4.4 NAT 和 代理服務器
**NAT:**路由器大多都具備NAT功能,完成子網內部設備和外界的溝通
**代理服務器:**看起來和NAT挺像,客戶端像代理服務器發送一個請求,代理服務器把請求發送給真正要訪問的服務器;服務器返回結果給代理服務器,代理服務器在返回給客戶端
NAT和代理服務器區別
| 應用 | 解決的IP不足 | 翻墻: 廣域網中的代理;負載均衡: 局域網中的代理 |
| 底層實現 | 工作在網絡層,實現IP地址的更換 | 工作在應用層 |
| 適用范圍 | 局域網的出口部署 | 局域網,廣域網都可以使用甚至跨網 |
| 部署 | 防火墻,路由器等硬件設備上 | 部署在服務器上的一個軟件程序 |
代理服務器又分為正向代理和反向代理
胖虎在宿舍不想去超市買辣條,于是乎…在 《小葵花大學666棟666小賣部》的QQ群里艾特李華幫忙買包辣條然后給他小費
李華完成了胖虎的任務并獲得了100元跑腿費「此時李華就是胖虎的正像代理」
后來胖虎一直讓李華帶零食,李華也開始偷了懶,抄起了Python,數據分析一頓操作猛如虎之后發現胖虎最愛大衛龍,可口可樂和樂事薯片。于是李華給了超市老板1元錢,從他那兒獲取批發商聯系方式,然后進了很多零食包括胖虎的最愛,自己在小葵花大學666棟當起了小老板,開啟了販賣零食的大學生活「此時李華就成了反向代理」
正向代理:敲一下,反饋一下這樣的請求。。。
翻向代理:相當于正向代理的緩存
3.2 傳輸層
3.2.1 UDP
3.2.1.1 UDP首部格式「使用注意」
64k對于當下是否滿足呢?
2字節=16位=216=65535byte=65535/1024=64k
非常小,如果傳輸的數據很大就需要其他方法。
- 是否可以擴展UDP:比如,把報頭改成使用4個字節「42億9千萬」來表示長度「改不了,改就需要改-系統內核」
- 千:thousand–>k
- 百萬:million–>M
- 十億:billion–>G
UDP首部有一個16位的最大數據長度,也就是說一次UDP傳輸最多有64K「包含首部」,傳輸數據超過64K,則我們需要在應用層進行手動分包,多次發送并在接收端手動拼裝。
五層模型中:程序員最關注的是應用層
下四層已經被操作系統/硬件/驅動實現好了,只要理解大概工作過程即可
對于應用層來說,不知要理解工作過程,更要能設計出一些 協議「設計應用協議就是Servlet約定前后端交互的接口」
3.2.1.2 UDP 校驗和
用來驗證數據是否正確的一種手段「不能保證數據100%正確,但是校驗和如果不正確則數據100%不正確」
背景:網絡傳輸過成中,可能會涉及到一定的干擾,就可能會破壞原有要傳輸的信息。光信號/電信號可能會受到一些 電磁場/高能粒子 的影響,可能會影響到地球上的通信「bit 翻轉」。
方法:crc,sha1,md5…
發送方和接收方利用同樣的算法計算校驗和
發送方式sum1,接受方是sum2.如果中途出現數據變動,則校驗和大概率不同
3.2.1.3 UDP特點
- 無鏈接:知道對方的 IP,port,但不需要建立連接就可以實現傳輸數據
- 不可靠:沒有確認應答機制、重傳機制,如果出現網絡狀況,UDP的傳輸層也不會給應用層任何錯誤信息
- 面向數據報:不能靈活控制數據報讀寫數據的大小和次數
3.2.1.4 面向數據報
應用層發送給UDP多長的數據,UDP原樣不變、發送給網絡層多長數據。既不拆分也不合并。
用UDP發送 1000 字節數據
發送方調用一次內核的 send,發送 1000 字節,接收方的內核就 receive 接受 1000 字節。而不會分成 100 次,每次發送 10 字節
3.2.1.5 UDP緩沖區
- UDP發送方沒有真正的緩沖區:調用 send,內核會把數據交給 網絡層協議,進行后續傳輸。
- UDP接收方有真正的緩沖區:但是這個緩沖區不能保證收到的數據報的發送和接收的順序一致,可能會出現錯亂。如果緩沖區滿了,則會丟掉后續的UDP數據報。
3.2.1.6 基于UDP層的協議
- NFS:網絡文件系統
- TFTP:簡單文件傳輸協議
- BOOTP:啟動協議「無盤啟動」
- DHCP:動態網絡IP分配協議
- DNS:域名解析協議
3.2.2 TCP
3.2.2.1 TCP首部格式
這 6 位用 0/1 表示
3.2.2.2 確認應答
每次客戶端發送數據給服務器都會SYN請求服務器建立連接,服務器收到響應都會ACK給客戶端確認應答
3.2.2.3 超時重傳
發送數據丟失的兩種情況:
處理丟包問題:
就按照最壞情況下作為發送方 “我” 數據丟失,如果指定時間后還沒有收到回信,我就再發一次
對于服務端發送給客戶端的數據丟了,服務端可以重發一次而不會對客戶端有影響;但是客戶端發送給服務端的數據丟失了,會進行大量的重發,服務端如何處理重復消息呢?
處理服務端數據重復
TCP會自動對消息進去重
發過去的數據會先放在接收方的消息緩沖區里「內核中的一個數據結構,可以視為阻塞隊列」。
任何一段消息都帶有ACK確認序號,如果新來的消息序號和阻塞隊列中的序號重復,TCP直接去重「多個消息只保留一份」。所以應用程序從接受緩沖區取數據的時候,肯定不是一個重復的數據。調用 socket api 得到的數據一定不重復。
有了以上的 確認應答,超時重傳應該可以保證TCP的數據萬無一失了吧?但最糟糕的問題來了:如果對于客戶端和服務端任何一方而言,重傳數據也丟失了該怎么辦呢?
處理重傳數據丟失問題
重傳不會無休止的進行,嘗試一定次數后就會放棄「如果重傳的數據也丟失了就認為能夠恢復鏈接的概率很低,重傳次數再多也是浪費資源」
重傳時間間隔也不相同,每次重傳時間間隔都會變長
假設丟包概率是10%,則兩次數據包都丟失的概率就是10% * 10% =1%
我們有了嘗試一定次數和時間間隔來解決丟包難題,次數我們很容易規定,可以假設超過16次就認為傳輸失敗,可以關閉連接。但是這個 重傳時間間隔 該如何確定呢?
確定重傳時間間隔
最理想的情況下是能夠早找一個 最短回復時間,在這個時間內,數據的響應一定能返回
但是這個時間的長短是由網絡環境決定的,各有差異
如果設置的超時時間太長,則會影響整個過程的傳輸效率
如果設置的超市時間太短,則會頻繁的發送數據包,造成資源浪費
因此,TCP為了保證在任何環境下都能保持較高性能的通信效率,因此會動態計算這個 超市時間
Linux「Unix,Windows」也都是超時以 500ms 為一個單位進行超時控制,每次判定超時重傳的時間間隔是 500ms的整數倍
第一次:500ms,第二次:2*500ms,第三次:3*500ms…
如果累積到一定次數之后就會認為當前網絡環境已經無法恢復,就會強制關閉連接
3.2.2.4 連接管理「面試問的最多」
正常情況下TCP要經歷三次握手建立連接,四次揮手斷開連接
三次握手
三次握手的原始連接
三次握手后的連接優化
因為對于服務端發給客戶端的 ACK+SYN 可以合并在一起發送。
public TcpEchoClient(String serverIP, int serverPort) throws IOException {// 客戶端何時和服務器建立連接:在實例化 Socket 的時候this.socket = new Socket(serverIP, serverPort); }還記得這段代碼嗎?TCP的客戶端什么時候建立連接呢?是在實例化 socket 對象的時候,自動連接。ACK和SYN操作都是操作系統同一時機內核完成的。因此對于服務端而言:可以把ACK的確認和SYN的請求建立通過一次網絡請求執行完畢而不是通過兩次網絡請求「這樣做有利于節約網絡帶寬」
分兩條發送后,分別進行封裝和分用,實際上這兩條數據正好可以合并一起就沒必要分開了
四次揮手
對于建立連接來說,中間的兩次ACK+SYN可以合二為一,斷開連接也可以合二為一嗎?
抓蛇先抓七寸:TCP什么時候斷開連接呢?「也就是說什么時候觸發FIN呢?」
當客戶端觸發 FIN 之后,服務器只要收到 FIN 就會立馬返回 ACK「內核完成的」
當服務器的代碼中運行到 socket.close() 操作的時候,就會觸發 FIN
這兩個操作在不同的時機,中間有一定的間隔。
兩個重要的狀態
CLOSE_WAIT
TIME_WAIT
這四次揮手過程中的任意一個包也是會丟的。
第一組 FIN 或者 ACK 丟了。此時 A 都沒有收到 B 的 ACK,A 就會重傳 FIN
第二組 FIN 或者 ACK 丟了。此時 B 都沒有收到 A 的ACK,B 就會重傳 FIN
如果 A 收到了 FIN 之后,立即發送 ACK,并且釋放鏈接「變成CLOSE狀態」,此時就會出現無法處理重傳 FIN 的 ACK 情況,此時就僵硬了。
等一段時間之后,確保當前 FIN 不被重傳了,然后才真的釋放鏈接。
所以當 A「客戶端這邊」發送完 FIN 之后,不要立馬釋放,先等一等。等一段時間之后,確保當前 FIN 不被重傳才會真正釋放鏈接
TIME_WAIT等待的時間叫做 2MSL「MSL就是網絡上兩點之間傳輸消耗的最大時間」
3.2.2.5 滑動窗口
TCP最原始的發送數據:發一個,確認一個這樣的機制。等到ACK之后才能發送下一個數據,這樣的話后續的數據大量會阻塞等待。
滑動窗口就是為了解決這個問題,減少等待ACK時間「其實就是將多段等待時間重疊在一起了」
假設一次發送長度為N的數據,然后等待一波ACK,此時這里的N就稱為“窗口大小”
N越大,傳輸的速度越高,但是N也不能無限大,如果N無限大,此時確認應答就沒有意義了,可靠性就形同虛設了
- 上圖的窗口大小就是 3000字節「3個字段」
- 發送前 3 個字段的時候無需等待,直接發送
- 發送第四個字段的時候等待需要阻塞等待第一個ACK「下一個是1001」才能繼續發送,依此類推
- 操作系統內核為了維護這個滑動窗口,需要開辟 發送緩沖區 來記錄當前數據還有哪些沒有應答;只有確認應答過的數據才能從緩沖區刪除掉
遇到丟包問題怎么辦?
丟包問題分為兩種,一種是確認應答ACk丟了,一種是數據丟了。我們需要分開討論分析。
ACK丟了
這種情況下,丟ACK并不要緊,可以通過后續ACK來確認
數據丟了
當某一個數據包丟失的時候,發送端會一直發送此數據端的ACK,比如ACK1001。
如果客戶端主機收到了 3次 同樣的ACK,就認定次數據包已經丟失了,會根據ACK的提示發送對應的數據段。
當服務端主機收到了所需要的ACK的時候,則會返回客戶端最后一次 沒有丟包發送過來的數據的ACK「此處就是ACK6001」
因為2000-6000的數據已經被收到了,就被放到了操作系統內核的 接受緩沖區 了。
這樣就構成了 滑動窗口下的快重傳
3.2.2.6 流量控制
也是在保證可靠性,對滑動窗口進行了制約。滑動窗口越大,就認為傳輸速率越高。但也并不是越大越好,接收方頂不住消息之后,額外發出的數據大概率是要丟包的,就會觸發超時重傳機制。所以一定是最合適的才是最好的。發送方和接收方速率理論上匹配最好。
主要是根據接收方處理數據的能力,來制約滑動窗口大小。發送方的話動窗口大小是變化的「不是固定的」
接收方處理數據的速率主要取決于應用程序,調用 socket api 讀取數據的速率
如何衡量接收方的處理數據的速度
主要就是看接收方的應用程序調用 socket api 的讀操作「read() 快不快」
刨根問底就是判斷:通過接受緩沖區中剩余空間的大小
假設接受緩沖區一共是 4k。 當前使用了3k,還剩1k。此時接收方就會返回 ACK 的時候告知發送方說:我這個接受緩沖區還有 1k 空間;接下來會發現發送的時候就可以按照 1k 這樣的窗口來發送數據…
在考慮一個極端情況:如果發送接收方緩沖區滿了,發送方就不再發送數據
這時候接收方會在窗口滿的時候發送一個 ACK 告知發送方窗口滿了,然后發送方停止發送數據。由于發送方停止發送數據導致的接收方不會對發送方有任何響應。
所以這個時候發送方會 定期 發送一個 探測報文,接收方收到這個 探測報文段 之后就會把自己當前窗口大小響應給發送方「這個接收方有種需要發送方敲打的味道」
這個窗口只能存放65535個字節嗎?
TCP首部40字節選項中還包含了一個擴大因子 M,實際窗口大小是 左移M位
3.2.2.7 擁塞控制
和流量控制差不多,都是用來限制發送方傳輸速率的機制。防止發的太快處理不了。
- 流量控制是根據接收方的處理速率來進行衡量的
- 擁塞控制是根據發送方到接收方這一些列通信鏈路的處理速率來衡量的。
雖然兩臺電腦處理和發送都很快,但是如果中間某個節點「路由器等網絡設備」出問題,不能快速的轉發
相比于流量控制,擁塞控制是更復雜的
流量控制:只考慮接收方和發送方
擁塞控制:考慮的是整個鏈路上有多少個設備,這些設備路徑都是什么情況會很復雜「由于這個中間路徑非常復雜,擁塞控制解決方案是把中間整個鏈路視為一個整體,通過 不斷試錯 的方式來找到一個合適的發送窗口大小。不斷的嘗試不同的窗口大小,在保證可靠性的前提下提高發送速率」
擁塞控制如何控制擁塞窗口的?
擁塞控制會設置出一個 擁塞窗口 這樣的指標,通過擁塞窗口來影響滑動窗口的窗口大小
擁塞控制也是動態變化的,剛開始用一個比較小的值「讓發送方發的慢點」如果通信非常順利,也沒有丟包就會逐漸放大窗口,加快發送速度的同時密切監視丟包情況,如果嫁到一定程度了,發生了丟包,說明當前接收方頂不住了;就立即減小窗口大小,讓速度再慢下來,如果不丟包,再逐漸加速。反復重復以上步驟就會逐漸穩定在一個合適的速率
擁塞控制和流量控制都能影響滑動窗口,到底誰起決定作用
誰小誰說了算
擁塞窗口的變化規律
3.2.2.8 延遲應答
提升傳輸效率,考慮是否能子啊保證可靠性的前提下繼續把滑動窗口調大一點「流量控制的延伸」
流程簡述
- 假設接收方窗口大小為3M「3000字節」,如果接受了某次收到了2.5M數據就,如果立即返回,發送給發送方的窗口的大小為0.5M
- 但實際情況是可能處理數據速度很快,不到50ms就把2.5M數據處理掉了,這種情況下接收端還遠遠沒有達到自己的極限,因此可以把窗口調大一些
- 接收端等待一會兒再應答,比如過200ms再返回。就會返回一個3M的窗口大小。
記住:窗口越大,網絡傳輸速率就越大,但是一定要在保證可靠性的前提下才可以調大窗口
那么所有的包都可以延遲應答嗎?
當然不是,有數量和時間限制
數量:每隔 N 個包就應答一次
時間:超過最大延遲時間就應答一次
具體的數量和時間:不同操作系統都是不一樣的。一般 數量N取2,最大延遲時間取200ms
3.2.2.9 捎帶應答
在延遲應答的基礎之上做了延伸
最典型的就是:一問一答
客戶端發送一個請求,服務器就會響應一個客戶端的請求
這倆操作是不同的時機,既然是不同實際也就不應該合并成一個數據報
但是TCP中的捎帶應答機制導致B對A的回復并不是立即的,而是等待一段時間之后在發送。在等待的這段時間內,就導致了發送的時間可能就和應用程序返回A響應的時間就是同一時機了,也就可以合并了
把兩個個TCP數據報合并成一個數據報,節約資源與時間,減少封裝和分用
TCP的四次揮手有沒有可能變為三次揮手?
B發給A的ACK是在內核中完成的、FIN是應用程序代碼調用 close() 完成的,這倆操作看似是不同時機,但是如果有了捎帶應答機制結果就不一樣了。如果恰好觸發了捎帶應答,則會是 ACK+FIN 合二為一發送過去,此時的話就會是三次揮手
程序不一定100%觸發捎帶應答,如果設定延遲應答時間為200ms,如果200ms內恰好出發了捎帶應答,則會執行到 close
3.2.2.10 面向字節流
創建一個 socket 的同時內核就會創建一個 接收/發送 緩沖區
發送數據
- 調用 write 寫的時候,數據先會被發送到 發送緩沖區
- 如果發送的數據過長,就被拆分成很多段小的數據包;如果發送的數據過短,就會等待發送緩沖區數據長度差不多了一并發送
接收數據
- 接收數據的時候,網卡驅動程序先從內核中接受緩沖區讀取數據
- 然后調用 read 拿 接收緩沖區 的數據
由于有緩沖區的存在,TCP程序的讀和寫不是一一對應
- 寫100字節:可以一次 write(new byte[1000]). 也可以循環 1000次 write(new byte[1])
- 讀100字節:可以一次 read(new byte[1000]). 也可以循環 1000次 read(new byte[1])
3.2.2.11 沾包問題
多個TCP數據報到達的時候,如果不顯示的約定應用層數據的包和包之間邊界,就很容易對數據產生混淆
這種情況就是 沾包問題,多個數據包混在一起
沾包問題并不是TCP獨有的問題,任何的 面向字節流 傳輸機制都會涉及到這個沾包問題「讀寫普通文件也是面向字節流的」
解決方案:給每個數據包結尾片接一個特殊符號表示結束
一個簡單協議就是用 ; 來分隔
在HTTP「應用層協議」中如何解決沾包問題呢?
不帶body帶head的GET請求
不帶head帶body的POST請求
在瀏覽器檢查中,**Request Headers中的每一欄會以換行來區分,但在請求中以 \n來結束 **
GET /index.html/HTTP/1.1\nHOST127.0.0.1:8080\nUser-agent:xxx\nReferer:HTTP://www.baidu.com\n\n
如果接收方的接受緩沖區里有多條 HTTP GET 請求,就可以根據這個空行來區分多個HTTP請求了
HTTP沒有body的時候以 空行結尾
POST /index.html/HTTP/1.1\nHOST127.0.0.1:8080\nContent-Type:text/html\nContent-Length:3277\n\…
如果接受方的接受緩沖區有很多條 HTTP POST 請求,還是先找到空行
在空行之前能夠找到 Content-Length:3277,再從 Content-Length:3277 往后找 3277 個這么長的數據也就到達了邊界
3.2.2.12 TCP異常情況
建立好通信的雙方,在通信過程中突然有一方遇到了突發狀況。
1.進程終止
A,B其中某個進程突然終止「崩潰或者被強制關閉」
如果直接關閉進程,看起來是猝不及防,但實際上操作系統早有準備「也就是每次打開任務管理器的時候,CPU占用資源瞬間高漲的一部分原因」
殺死某個進程,操作系統回釋放這個進程的相關資源「TCP這里依賴 socket 文件,操作系統就會自動關閉這個 socket 文件。這個自動關閉的過程就相當于 socket.close()『觸發了四次揮手』」
2.機器重啟
按照操作系統既定的流程重啟
就會由操作系統先把當前所有的應用程序,強制殺死「殺死進程就和上面的進程終止一樣了,釋放 socket 文件,發送 FIN」
『單純的四次揮手』
3.斷電/斷網
這個情況才算 偷襲成功
接收方掉電
此時A不會收到B發送的ACK,接下來就會觸發超時重傳,重傳一定次數之后認為連接不可恢復『嘗試重新建立連接』,最終只能放棄鏈接『A就會釋放自己所有保存連接的信息』
『就會放棄四次揮手斷開連接』
發送方掉電
A發完第一條消息之后,B響應對應的ACK,但是A沒有發送斷開連接的請求導致B就會一直在等待A的請求
解決方案就是:TCP連接雙方會周期性的給對方發送一個不包含業務數據的 探測報文,這個探測報文不傳遞實際的數據,只是用來檢查對方是否正常工作。
3.2.2.13 TCP小結
優先保證可靠性,再進一步提高效率
**可靠性:**確認應答,超時重傳,連接管理,流量控制,擁塞控制,TCP異常情況
**效率:**滑動窗口,延遲應答,捎帶應答
**編碼注意事項:**沾包問題
3.2.2.14 基于TCP層的協議
HTTP,HTTPS,SSH,FTP,SMTP,Telnet
3.2.3 TCP與UDP對比
TCP優勢:可靠性
**UDP優勢:**效率更高
經典面試題:如何用UDP保證可靠傳輸
「抄TCP的作業」
- 引入序列號,保證數據的順序
- 引入確認應答,保證收到數據
- 引入超時重傳,保證收到數據
- …
3.3 網絡層
3.3.1 IP協議
網絡層里面最核心的協議叫做 IP協議,分為兩個版本:IPV4 和 IPV6
-
4位版本號:對于IPV4來說就是4
-
4位首部長度:類似于TCP。IP協議包頭也是變長的,單位是4字節。4bit最大是15,所以IP頭部最大長度就是4*15=60字節。
-
服務類型:3位優先權已被棄用,1位保留字必須為0,4位TOS字段分別代表:最小延時,最大吞吐量,最高可靠性,最小成本「對于SSH/Telnet這樣的程序最小延遲比較用重要;對于FTP,最大吞吐量比較重要」。這4個特性互斥的,用的時候對應的比特位設置為1,其余必須為0
-
16位總長度:IP數據報整體占多少個字節
- 16位–>64K,難道說一個 IP 數據包最大只能表示 64K 嗎?「是,又不完全是」
- 因為在IP里,協議內部實現了數據報的拆分「當超過64K,IP協議就會自動的對這個大的包進行拆分,拆成多個小的包,保證每個小的包不會超過64K」
-
16位標識,3位標志,13位片偏移都是為了拆分
- 16位唯一的標識主機發送的報文。如果IP報文在數據鏈路層被分片了,那么每一個片里面的這個id都都是相同的
- 3位標志:第1位保留;第二位:值為1則是禁止分片;值為0則是允許分片;第3位:只有最后一個分片值為1、其余均為0、用來設置結束標志
- 13位片偏移:是分片相對于原始IP報文開始處的偏移. 其實就是在表 示當前分片在原報文中處在哪個位置。實際偏移的字節數是這個值 * 8 得到的。因此,除了最后一個報文之外, 其他報文的長度必須是8的整數倍(否則報文就不連續)
-
8位生存時間:數據報到達目的地的最大跳數。一般是64,每經歷一次轉發TTL就會-1,直到0了還沒有收到,那么就丟棄。主要為了防止循環路由出現
-
8位協議:表示傳輸層使用的哪個協議,TCP/UDP 會有不同的值,為了在分用的時候能夠讓網絡層把數據提交給正確的傳輸層協議來處理
-
16位首部校驗和:使用CRC來校驗頭部是否損壞
-
32位源地址:發送端IP地址
-
32位目的地址:接收段IP地址
UDP首部長度固定為8字節,IP首部長度固定為20字節
16位表示,3位標志,13位片偏移如何拆分64K的
生存時間
3.3.4 網段劃分
IP地址分為兩部分,網絡號和主機號
- 網絡號:保證兩個相連接的網段具有不同的身份標識
- 主機號:同一網段內,主機具有相同的網絡號,但是必須有不同的主機號
- 不同的子網其實就是把網絡號相同的主機連接在一起
- 如果在子網中新增一臺主機,則這臺主機的網絡號和子網中網絡號相同,但是主機號不能和子網中其它主機號相同
通過合理的設置網絡號和主機號,就可以保證網絡中的主機IP地址不會重復
本機網絡詳情
子網掩碼:和IP地址一樣,也是一個32位整數,由網絡號+主機號組成。通過 點分十進制 的形式劃分為 4部分,每部分1個字節長度 來表達
子網掩碼網絡號:用二進制1來表示,1的數目代表網絡號的長度
子網掩碼主機號:用二進制0來表示,0的數目代表住幾號的長度
網絡號:子網掩碼和IP地址進行按位與運算
假設有一個IP地址:191.100.0.0,子網掩碼為:255.255.128.0來劃分子網
- 191.100.0.0
- 255.255.128.0
B類子網掩碼本來是255.255.0.0,所以此子網掩碼網絡號向主機號借了一位即17位,因此可以劃分21個子網,但實際使用0個「去掉全0全1」,這個網段可以容納215個主機
- 網絡號為:16位網絡號+16位主機號
計算方式
網絡號:IP地址與子網掩碼按位與計算
主機號:IP地址與取反后的子網掩碼按位與計算
| IP地址 | 180.210.242.131 | 10110100.11010010.11110010.10000011 |
| 子網掩碼 | 255.255.248.0 | 11111111.11111111.11111000.00000000 |
| 網絡號 | 180.210.240.0 | 10110100.11010010.11110000.00000000 |
| 主機號 | 0.0.2.131 | 00000000.00000000.00000010.10000011 |
幾個特殊的IP地址
- 如果主機號為0:網絡號
- 如果主機號為1:通常表示的是這個局域網的 網關「局域網的出入口,通常也就是路由器的LAN口IP」
- 如果主機號全1:廣播這個IP,往這個IP上發送數據,局域網中的所有設備都能收到
子網內部的一些設備
那么問題來了,手動管理子網內的IP地址是非常麻煩的
3.3.5 IP地址的數量限制
IPV4協議,是使用4個字節來表示IP地址,表示的地址個數只能是42億9千萬
如何應對IP不夠用
NAT機制圖
設備1和設備2如果出現了端口重復該怎么辦?
也是通過路由器的端口替換「NAPT」。
路由器檢測到同一個子網內有兩臺相同端口的設備,會自動進行端口映射「在路由器內部維護這樣的關系」,同一個子網內IP不會重復所以可以區分對應的主機。
NAT機制缺陷
雖然一定程度上解決了IP地址不夠用的問題,但也引來了一些重要的缺陷。
子網內的設備 可以 訪問一個外網IP的設備
子網內的設備 不可以 訪問另外一個子網內的設備
由此誕生了 云服務器,作為第三方可以讓大家共同訪問「買服務器的本質是購買了一個外網IP」,如果初學,對服務器搭建Tomcat+MySQL不熟悉的可以查看我的 博客鏈接
真正解決IP地址不夠用的技術:IPV6
擁有16字節來表示IP地址「2^128」
號稱地球上的每一粒沙子都可以分配一個IP地址
為何當下還是IPV4+NAT呢?
主要是當下支持IPV4的設備「主要是路由器」大概率不兼容IPV6,要升級到IPV6,勢必要把大量的設備換成IPV6,成本比較高
國家也在大力推進IPV6的網絡建設「主要是針對國家安全和利益考慮」
3.3.6 私有IP地址和公網IP地址
特殊的IP
-
主機號全為0:當前局域網
-
主機號全為1(125):廣播IP
-
以127開頭的IP「環回」
-
內網IP是不要求唯一的「不同網段中會出現相同的IP地址」
-
除了10,172.16-172.31,192.168是私網IP以外,其余全是外網IP「外網IP要求是唯一的」
3.3.7 路由
路由選擇也就是規劃一條通信傳輸的路徑
客戶端在和服務器搭建連接的過程中是一個很復雜的過程,中間會有很多臺設備中轉才連接到的
在這些線路中找到一個最合適的路線就是路由選擇要做的工作
簡約的查找流程
重復上述步驟,就能夠找到一個合適的路由認識目的IP「通過6個人可以認識全世界的故事原理」
路由器如何認識目的IP的呢?這就用到了所謂的路由表的概念了。
路由表
這個是路由器內部維護的一個類似于 “通訊錄電話本” 的功能,是以 key:vale 形式存儲的
key:IP地址網絡號
value:網絡接口「從路由器的WAN口出還是LAN口出」
路由表又一個默認的電話本,稱為 “下一跳”
路由表的實現也很復雜,一方面可以手動生成一方面可以動態設定
3.4 數據鏈路層
主要負責相鄰的兩個節點之間的通信
3.4.1 認識以太網
3.4.1.1 以太網幀格式
以太網:并非是一個真正的網絡,而是一種技術規范「既包含了數據鏈路層也涵蓋物理層:網絡的拓撲結構,訪問控制方式,傳輸速率…」
以太網中的網線必須使用雙絞線「相同設備使用交叉線;不同設備使用直通線」
FCS:數據校驗的方式「著名的是CRC冗余驗證」
46-1500:所能承載的數據范圍「單位是字節」
最多1500:首先與當前網絡硬件的結構,不同數據鏈路層協議搭配不同的物理設備對因承載數據的能力也不同
3.4.1.2 認識MAC地址
MAC地址是6字節,表示范圍較于4字節的IPV4多了6w倍「所以才可以財大氣粗的給每個硬件在出廠的時候寫死一個MAC地址」
MAC地址起到的的主要要作用就是在相鄰的兩個基點至簡傳輸
3.4.2 對比理解MAC地址和IP地址
當數據包到達局域網之后,根據IP+Port可以直接放送給目標主機的目標應用程序。好奇的朋友可能會問:為什么有了IP地址還要發明一個MAC地址呢?
舉個例子「重復了又好像沒重復…」:
高考發送錄取通知書的時候,郵政小哥會提前打個電話通知李華,因為郵件地址填寫的是光明小區,收件人是李華。李華滿懷激動地下樓后等待快遞小哥的到來,快遞小哥為了驗證身份會問到 “李華童鞋,你的準考證號是多少?”,因為李華這個名字全國都會重復,所以報了不會重復的準考證號,于是李華報了自己的證件號 “123”,于是快遞小哥又說道 “好,準考證123號童鞋來拿你的錄取通知書”。于是李華拿了小葵花大學的錄取通知書離開了。
整個過程感覺重復了但又沒有沒重復的感覺…
來下面的解析:
其實是歷史遺留問題,理論上講:IP+Port就可以連接兩臺設備。
首先從實際出發,IP解決的事互聯網上所有設備的聯網問題。假設換句話說IP地址用MAC地址替代
MAV地址248 「2.81474976710656E14:281萬億字節,換算為存儲就是262144G也就是256T的存儲才能裝完所有MAC地址」,這顯然是不科學的。這也就是為何IP地址替代MAC的原因而MAC地址不能替代IP地址的原因
隨著互聯網的發展,路由也變得越來越復雜和困難了,于是聰明的人類發明了子網,把互聯網分成很多個子網。在路由的時候,路由器就可以把其它子網看成一個整體。對于目的地還在其它其它其它的子網時候,路由器只負責把數據報發送到子網內部即可。然后在其子網內部完成剩余的數據報發送工作。這樣做可以做到路徑選擇上接近最優而不是最優解。不過還是利大于弊,所以被采用了。
和MAC地址不同的是,IP地址和地域相關「類似于郵政編號,根據不同地區劃分不同的郵政編碼」。對于同一個子網內部的設備IP,它們的前綴都是相同的。現在路由器只記錄每個子網的位置,就知道設備在哪個子網上了,這樣大大的節約了路由器的存儲空間
既然IP不能缺掉,那么這個MAC地址又顯得多余,能不能去掉呢?
答案肯定是不可以
因為IP地址必須是設備上線后才能獲得一個路由器根據設備對應的子網來動態分配一個私有IP地址,在離線的時候我們還需要通過MAC地址來管理設備的
總之IP地址相當于一個大范圍的身份表示「光明小區李華」,而MAC地址就相當于一個屬于自己的ID「準考證號」。兩者缺一不可
對比發現:
- IP地址主要是用來表示轉發過程中的起點和終點
- MAC地址主要是用來表示任意一次轉發過程中的起點和終點
3.4.3 認識MTU
MTU「最大傳輸單元:Maximum Transfer Unit」相當于對數據幀的限制,這個限制是數據鏈路層對下一層的物理層的管理也影響上一層網絡層的協議。
- MTU范圍 [46, 1500] 閉區間上
- 最大值1500被稱為MTU,不同網絡類型有不同的MTU
- 不同數據鏈路層MTU標準不同
- 如果一個數據包從以太網上到達了數據鏈路層,若數據包超過MTU,這個數據就被 分片處理
3.4.3.1 MTU對IP協議的影響
如果IP數據報超過了1500字節,就無法被封裝到一個以太網數據幀中,這個時候就會觸發IP的分包操作「IP的分包一般不是因為報頭中的64限制了數據報整體的大小,大概率是因為數據鏈路層以太網幀的MTU限制來分的」
以下是MTU對IP報如何分片的
3.4.3.1 MTU對UDP協議的影響
以下是MTU對UDP的分片
3.4.3.1 MTU對TCP協議的影響
TCP的數據報也不能無限大,主要還是受限于MTU。TCP單個數據報的最大長度是MSS「Maximum Segment Size」
好奇的童鞋有可能會問:為什么TCP沒有數據長度限制呢?那UDP有嗎?
在回顧一下TCP和UDP格式會發現,只有UDP又一個2字節數據長度「還記得計算的是不超過64K嗎?」,而TCP協議格式中則沒有對數據長度的限制
以下是MTU對TCP的分片
3.4.4 ARP協議
這個了解即可,ARP協議其實并非是一個單純的數據鏈路層協議,而是作用在數據鏈路層和網絡層之間的協議
3.4.4.1 ARP協議的作用
用來簡歷IP地址和MAC地址之間的映射關系
- 在網絡通信是,發送端知道接收端的 IP+端口,卻不知道接收端的 硬件地址「MAC地址」
- 站在接收端的角度來看:數據包先是被網卡驅動程序接收再去處理上層「網絡層,傳輸層這些」協議,如果發現數據包的硬件地址和本機地址不符,則會直接丟掉
- 因此在通信前,還需要或讀接收端的MAC地址
3.4.4.2 ARP協議工作流程
先獲取MAC地址
再來看一下使用ARP發送數據過程
3.5 總結
應用層
- 應用層的作用:滿足我們日常使用的網絡程序
- DNS解析
- NAT技術結合應用程序訪問外界IP
傳輸層
- 傳輸層的作用:負責端到端的數據傳輸
- 由端口號區分應用程序
- UDP協議及特點
- TCP協議的可靠性「兩個狀態的轉化CLOSE,TIME_WAIT」
- TCP安全性:確認應答,連接管理,超時重傳,流量控制,擁塞控制,TCP異常機制
- TCP的效率:滑動窗口,捎帶應答,延遲應答
- TCP面向字節流,沾包問題的解決方案
- 基于UDP抄TCP作業實現可靠傳輸
- MTU對IP,TCP,UDP影響
網絡層
- 網絡層的作用:負責端到端過程中每個點到點的數據傳輸
- IP地址,MAC地址
- IP協議格式
- 網段劃分
- IP數量不足的解決辦法
- IP數據包地址路由的選擇過程「如何跨網段送達目的地」
- IP數據包分片原因
- NAT設備工作原理
數據鏈路層
- 數據鏈路層的作用:兩個設備之間的數據傳輸
- 以太網的理解
- 以太網幀格式
- MAC地址
- ARP協議
- MTU初識
總結
以上是生活随笔為你收集整理的当初我要是这么学习计算机网络就好了「附图文解析」的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 怕被发垃圾邮件?用临时邮箱来注册账号
- 下一篇: 好用的图片标注工具