Java後端面試系列-計算機網絡篇

1 OSI七層概念模型

在這裏插入圖片描述

1.1 物理層

	目的:解決兩臺物理機的通信需求,傳遞的是比特流,也就是010101...二進制數據,先轉
成電流的強弱進行傳輸,達到後轉成010101...(也就是所謂的數模轉換和模數轉換)。網卡
就是工作在這一層。

1.2 數據鏈路層

	在傳輸比特流的過程中,會產生錯傳、數據傳輸不完整等情況。針對這些情況,數據鏈路層
定義瞭如何格式化數據,控制對物理介質的訪問,還提供錯誤檢測和糾正以保證數據傳輸的可
靠性,其中將比特流組成了幀。交換機就是工作在這一層,對幀解碼,並將幀中的數據發送到
正確的接收方。

1.3 網絡層

	主要工作是將網絡地址翻譯成對應的物理地址,並決定如何將數據從發送方路由到接收方。
路由器屬於網絡層。此層的數據成爲數據包,需要關注的協議是IP協議。

1.4 傳輸層

	傳輸層解決了主機間的數據傳輸,同時解決了傳輸質量的問題,該層需要關注的是TCP協議
和UDP協議。

1.5 會話層

	作用是建立和管理應用程序之間的通訊。

1.6 表示層

	解決信息的語法語義及它們的關聯,如加密解密、轉換翻譯、壓縮解壓縮。

1.7 應用層

	規定應用發送方和接收方必須使用固定長度的消息頭,且有固定規則,需要關注的是HTTP
協議。

2 TCP/IP

OSI只是一個概念模型,TCP/IP可以看成OSI的“實現”

OSI七層模型 TCP/IP模型 功能 TCP/IP協議族
應用層 應用層 文件傳輸,電子郵件、文件服務,虛擬終端 TFTP,HTTP,SNMP,FTP,SMTP,DNS,Telnet
表示層 數據格式化,代碼轉換,數據加密 沒有協議
會話層 解除或建立與別的接點的聯繫 沒有協議
傳輸層 傳輸層 提供端對端的接口 TCP,UDP
網絡層 網絡層 爲數據包選擇路由 IP,ICMP,RIP,OSPF,BGP,IGMP
數據鏈路層 鏈路層 傳輸有地址的幀以及錯誤檢測功能 SLIP,CSLIP,PPP,ARP,RARP,MTU
物理層 以二進制數據形式在物理媒體上傳輸數據 ISO2110,IEEE802,IEEE802.2

3 說說TCP的三次握手

3.1 傳輸控制協議TCP簡介

  • 面向連接的、可靠的、基於字節流的傳輸層通信協議
  • 將應用層的數據流分割爲報文段併發送給目標節點的TCP層
  • 數據包都有序號,對方收到則發送ACK確認,未收到則重傳
  • 使用校驗和來檢驗數據在傳輸過程中的是否有誤

3.2 TCP報文頭

在這裏插入圖片描述

  1. 源端口,目的端口:各佔2個字節
  2. 序列號:佔4個字節,因爲字節流中每個字節都按順序標號,這就是那個號
  3. 確認號:佔4個字節,是期望收到的下一個字節的序號
  4. Offset:指出TCP數據距離TCP報文的起始處有多遠
  5. Reserved:保留域,默認爲0
  6. TCP Flags:佔一個字節,詳情見3.3
  7. Window:代表滑動窗口的大小,用來告知發送端接收端的緩存大小,以此控制發送端的發送速率,從而達到流量控制。
  8. 校驗和:佔2個字節
  9. 緊急指針:僅當URG爲1時有效,指出報文的緊急數據的字節數
    10.TCP可選項:定義其他的可選參數

3.3 TCP Flags

  • URG:緊急指針標誌
  • ACK:確認序號標誌(1表示有,0則忽略)
  • PSH:push標誌
  • RST:重置連接標誌
  • SYN:同步序號,用於建立連接過程
  • FIN:finish標誌,用於釋放連接

3.4 三次握手流程

在這裏插入圖片描述
注意:前兩次握手不可以攜帶數據,第三次握手可帶可不帶,若不帶,則不會消耗序號。
(1)第一次握手:建立連接時,客戶端發送SYN包(syn=j)到服務端,並今日SYN_SEND狀態,等待服務端確認;
(2)第二次握手:服務端接收到SYN包,必須確認客戶的SYN(ack=j+1),同時自己也發送一個SYN包(syn=k),即SYN+ACK包,此時服務器進入SYN_RCVD狀態;
(3)第三次握手:客戶端接收到服務器的SYN+ACK包,向服務器發送確認包ACK(ack=k+1),此包發送完畢,客戶端和服務器今日ESTABLISHED狀態,完成三次握手

3.5 爲什麼需要三次握手才能建立連接

爲了初始化Sequence Number的初始值
通信雙方要通知給對方自己初始化的Sequence Number,這個序列號要作爲以後數據通訊的序號以保證傳輸層接受到的數據不會因網絡上的傳輸問題而亂序,即TCP會用這個序號來拼接數據,因此在服務器回發它的Sequence Number,即第二次握手之後,還需要發送確認報文給服務器,告知服務器客戶端已經收到你的Sequence Number了。

3.6 首次握手的隱患——SYN超時

問題起因分析

  • Service收到Client的SYN,回覆SYN-ACK的時候未收到ACK
  • Service不斷重試直至超時,Linux默認等待63秒才斷開連接(重試5次,每個等待時間翻倍,即等待1s,2s,4s,8s,16s,發送五次後,會在等待32秒才判定超時,總共63s)
    針對SYN Flood的防護措施
  • SYN隊列滿後,TCP會通過源地址端口、目的地址端口、時間戳打造一個特殊的Sequence NUmber,即tcp_syncookies參數(簡稱SYN Cookie),發回去。
  • 若爲攻擊者,不會有相應;若爲正常連接,則會將這個SYN Cookie發回來,直接建立連接。
  • 通過SYN Cookie,即便SYN隊列滿了,本請求不在隊列中,依然能偶簡歷連接

3.7 建立連接後,Clinet出現故障怎麼辦

保活機制

  • 在一段時間內,即保活時間,稱爲keep alive time,連接處於非活動狀態,開啓保活機制的一方將向對方發送保活探測報文,如果未收到相應則重發
  • 直到嘗試次數達到保活探測數,仍未收到相應則中斷連接

4 談談TCP的四次揮手

在這裏插入圖片描述
注1:這一過程又客戶端或服務端任意一端執行close來觸發,這裏假設客戶端主動觸發close。
注2:MSL代表最大報文生存時間

4.1 四次揮手流程

(1)第一次揮手:Client發送一個FIN,用來關閉Client到Server的數據傳送,Client進入FIN_WAIT_1狀態
(2)第二次揮手:Server收到FIN後,發送一個ACK給Cient,確認確認序號爲收到序號+1,Server進入CLOSE_WAIT狀態
(3)第三次揮手:Server發送一個FIN,用來關閉Server到Client的數據傳送,Server進入LAST_ACK狀態
(4)第四次揮手:Client收到FIN後,Client進入TIME_WAIT狀態,接着發送一個ACK給Server,確認序號爲收到序號+1,Server進入CLOSED狀態,完成四次揮手。

4.2 爲什麼會有TIME_WAIT狀態

原因:

  • 確保有足夠的時間讓對方收到ACK包
  • 避免新舊連接混淆

4.3 爲什麼需要四次揮手才能中斷連接

因爲全雙工(即:客戶端和服務端都可向對方發送數據),發送方和接收方都需要FIN報文和ACK報文

4.4 服務器大量出現CLOSE_WAIT狀態的原因

對方關閉socket連接,我方忙於讀或寫,沒有及時關閉連接
如何處理?

  • 檢查代碼,特別是釋放資源的代碼
  • 檢查配置,特別是處理請求的線程配置(比如線程池)
// 查看CLOSE_WAIT的數量
$ netstat -n | awk '/^tcp/{++S[$NF]}END{for(a in S) print a,S[a]}'

在這裏插入圖片描述

5 說說UDP

5.1 UDP報文結構

在這裏插入圖片描述

5.2 UDP特點

  • 面向無連接
  • 不維護連接狀態,支持同時向多個客戶端傳輸相同的消息
  • 數據包報頭只有8個字節,額外開銷小
  • 吞吐量只受限於數據生成速率、傳輸速率以及機器性能
  • 盡最大努力交付,不保證可靠交付,不需要維持複雜的鏈接狀態表
  • 面向報文,不對應用程序提交的報文信息進行拆分或者合併

6 TCP和UDP的區別

  1. 面向連接 vs 無連接
  2. 可靠性
  3. 有序性
  4. 速度
  5. 量級

7 TCP的滑動窗口

前置概念

  • RTT:發送一個數據包到收到對應的ACK,所花費的時間
  • RTO:重傳時間間隔(不固定,由RTT計算而來)

TCP使用滑動窗口做流量控制與亂序重排,有兩個作用:

  1. 保證TCP的可靠性
  2. 保證TCP的流控制特性

TCP頭部中的Window字段用於接受方告知發送方自己緩存的剩餘空間,發送方根據接受方的處理能力發送數據,不會導致接受方處理不過來。

窗口數據的計算過程
在這裏插入圖片描述
首先明確左邊緩衝中的這些段的意思:
LastByteAcked之前:已發送且已確認的數據
LastByteAcked 到 LastByteSent:已發送但未確認的數據
LastByteSent 到 LastByteWritten:上層應用已寫完,但還未發送的數據

在明確右邊緩衝中的這些段的意思:
LastByteRead之前:上層應用已經讀完,也都發送回執的數據
LastByteRead 到 LastByteExpected:已經收到,但還未發送回執的數據
LastByteRcvd:接收到的最後一個字節的位置

  • 接收方還可以處理的量: AdvertisedWindow = MaxRcvBuffer - (LastByteRcvd - LastByteRead)
  • 窗口內剩餘可發送數據的大小:EffectiveWindow = AdvertisedWindow - (LastByteSent - LastByteAcked)

TCP滑動窗口原理


TCP會話的發送方:
在這裏插入圖片描述
其中,類別2發送但未確認這段和類別3未發送但允許發送這段組成的這段連續空間成爲發送窗口
在這裏插入圖片描述
注意:只有當左邊數據(上圖是指32到35數據都被確認了)被連續確認之後,滑動窗口才會向右移動,新包括進來的數據纔可以發送。


TCP會話的接收方 ![在這裏插入圖片描述](https://img-blog.csdnimg.cn/20200512204450104.png) 其中,類別3未接收但準備接收的這段窗口成爲**接收窗口**。只有當左邊都接受到數據後,纔會向右滑動。

8 HTTP協議

8.1 超文本傳輸協議HTTP的主要特點

  • 支持客戶/服務器模式
  • 簡單快速(僅需要請求方法和地址)
  • 靈活(傳輸類型多)
  • 無連接
  • 無狀態(對事務處理沒有記憶)

HTTP1.1相較於HTTP1.0最大的特點是:增加了keep-alive

8.2 HTTP請求結構

在這裏插入圖片描述
注意:請求頭部與請求正文必有一個空行

8.3 HTTP響應結構

在這裏插入圖片描述
注意:響應頭部與相應正文必有一個空行

8.4 請求/相應步驟

  • 客戶端連接到Web服務器
  • 發送HTTP請求
  • 服務器接受請求並返回HTTP響應
  • 釋放連接TCP連接
  • 客戶端瀏覽器解析HTML內容

9 在瀏覽器地址欄鍵入URL,按下回車之後經歷的流程

  1. DNS解析。服務器會依據URL逐層查詢DNS服務器緩存,解析URL域名所對應的IP地址。DNS緩存由近到遠依次是:瀏覽器緩存、系統緩存、路由器緩存、IPS服務器緩存、根域名服務器緩存、頂級域名服務器緩存。從哪個緩存找到對應的IP,就直接返回,不再繼續查詢
  2. TCP連接。即:三次握手
  3. 發送HTTP請求
  4. 服務器處理請求並返回HTTP報文
  5. 瀏覽器解析渲染頁面
  6. 連接結束。即:四次揮手

注:第5步與第6步可同時發生,哪個在前沒有特別要求。

10 HTTP狀態碼

10.1 五種可能取值

  1. 1××:指示信息——表示請求已接收,繼續處理
  2. 2××:成功——表示請求已被成功接受、理解、接受
  3. 3××:重定向——要完成請求必須進行更進一步的操作
  4. 4××:客戶端錯誤——請求有語法錯誤或請求無法實現
  5. 5××:服務端有錯誤——服務器未能實現合法的請求

10.2 常見狀態碼

200 OK:正常返回信息
301:永久重定向
302:臨時重定向
400 Bad Request:客戶端請求有語法錯誤,不能被服務器所理解
401 Unauthorized:請求未經授權,這個狀態碼必須和WWW-Authenticate報頭域一起使用
403 Forbidden:服務器收到請求,但是拒絕提供服務
404 Not Found:請求資源不存在,比如輸錯了URL
500 Internal Server Error:服務器發生了不可預期的錯誤
503 Server Unavailable:服務器當前不能處理客戶端的請求,一段時間後可能恢復正常。

11 GET請求和POST請求的區別

從三個層面來解答:

  1. HTTP報文層面:GET將請求信息放在URL,POST放在報文體中(因此GET對數據長度具有長度限制、POST沒有限制)
  2. 數據庫層面:GET符合冪等性(即:對數據庫的一次操作和多次操作獲得數據是一致的)和安全性(即:沒有改變數據庫中的數據),POST不符合
  3. 其他層面:GET可以被緩存、被存儲(大部分請求被CDN處理了,大大減少服務器壓力),而POST不行

12 Cookie和Session

12.1 Cookie簡介

  • 是由服務器發送給客戶端的特殊信息,以文本的形式存放在客戶端
  • 客戶端再次請求的時候,會把Cookie回發
  • 服務器接收到後,會解析Cookie生成與客戶端相對應的內容

12.2 Cookie的設置以及發送過程

在這裏插入圖片描述

12.3 Session簡介

  • 服務器端的機制,在服務器上保存的信息(類似於散列形式)
  • 解析客戶端請求並操作session id,按需保存狀態信息

先檢查請求裏是否包含一個session id,若有,則使用此id獲取信息;若沒有,則創建一個session,並創建一個對應的session id,並返回回去。

12.4 Session的實現方式

  1. 使用Cookie來實現(服務器爲每個客戶端分配一個JSESSIONID,客戶端發起請求時,會在Cookie頭中攜帶這個JSESSIONID)
  2. 使用URL回寫來實現(服務器在給客戶端發送的每個頁面都攜帶一個JSESSION,客戶端點擊任意一個連接都會將JSESSIONID帶回去)
    在這裏插入圖片描述
    注:Tomat同時使用這兩種方式,若支持Cookie則使用第一種,若不支持,則使用第二種。

12.5 Cookie和Session的區別

  • Cookie數據存放在客戶的瀏覽器上,Session數據放在服務器
  • Session相對於Cookie更安全
  • 若考慮減輕服務器負擔,應當使用Cookie

13 HTTPS

13.1 HTTPS簡介

在這裏插入圖片描述
保護交換數據隱私、完整性以及身份認證的功能

13.2 SSL(Security Sockets Layer,安全套接層)

  • 爲網絡通信提供安全及數據完整性的一種安全協議
  • 是操作系統對外的API,SSL3.0後更名爲TLS
  • 採用身份驗證數據加密保證網絡通信的安全和數據的完整性

13.3 加密方式

  • 對稱加密:加密和解密都使用同一個密鑰
  • 非對稱加密:加密使用的密鑰和解密使用的密鑰是不相同的
  • 哈希算法:將任意長度的信息轉換成固定長度的值,算法不可逆
  • 數字簽名:證明某個消息或者文件是某人發出/認同的

13.4 HTTPS數據傳輸流程

  1. 瀏覽器將支持的加密算法信息發送給服務器
  2. 服務器選擇一套瀏覽器支持的加密算法,以證書的形式回發瀏覽器
  3. 瀏覽器驗證證書合法性,並結合證書公鑰加密信息發送給服務器
  4. 服務器使用私鑰解密信息,驗證哈希,加密響應消息回發瀏覽器
  5. 瀏覽器解密響應信息,並對消息進行驗證,之後進行加密交互數據

13.5 HTTP和HTTPS的區別

  • HTTPS需要到CA申請證書,HTTP不需要
  • HTTPS密文傳輸,HTTP明文傳輸
  • 連接方式不同,HTTPS默認使用443端口,HTTP使用80端口
  • HTTPS=HTTP+加密+認證+完整性保護,較HTTP安全

13.6 HTTPS真的很安全嗎

那倒未必

  • 瀏覽器默認填充http://,請求需要進行跳轉,又被劫持的風險
  • 可使用HSTS(HTTP Strict Transport Security)優化

14 Socket

14.1 Socket簡介

Socket是對TCP/IP協議的抽象,是操作系統對外開放的接口
在這裏插入圖片描述

14.2 Socket通信流程

在這裏插入圖片描述

14.3 Socket相關的面試題

	編寫一個網絡應用程序,有客戶端與服務器端,客戶端向服務器發送一個字符串,服務器收到
該字符串後將其打印到命令行上,然後向客戶端返回該字符串的長度,最後,客戶端輸出服務器
端返回的該字符串的長度,分別用TCP和UDP兩種方式去實現

TCP:
LengthCalculator.java

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;

public class LengthCalculator extends Thread{
    // 以socket爲成員變量
    private Socket socket;

    LengthCalculator(Socket socket){
        this.socket = socket;
    }

    @Override
    public void run() {
        try {
            // 獲取socket的輸出流
            OutputStream os = socket.getOutputStream();
            // 獲取socket的輸入流
            InputStream is = socket.getInputStream();
            int ch = 0;
            byte[] buff = new byte[1024];
            // buff主要用來讀取輸入的內容,存成byte數組,ch主要用來獲取讀取數組的長度
            ch = is.read(buff);
            // 將接受流的byte數組轉換成字符串,這裏獲取的內容是客戶端發送過來的字符串參數
            String content = new String(buff, 0, ch);
            System.out.println(content);
            // 往輸出流裏寫入獲得字符串的長度,回發給客戶端
            os.write(String.valueOf(content.length()).getBytes());
            // 關閉輸入輸出流以及socket
            is.close();
            os.close();
            socket.close();
        }catch (IOException e){
            e.printStackTrace();
        }
    }
}

TCPServer.java

import java.net.ServerSocket;
import java.net.Socket;

public class TCPServer {
    public static void main(String[] args) throws Exception{
        // 創建socket,並將socket綁定到6499端口
        ServerSocket ss = new ServerSocket(6499);
        // 死循環,使得socket一直等待並處理客戶端發送過來的請求
        while (true) {
            // 監聽65000端口,直到客戶端返回連接信息後才返回
            Socket socket = ss.accept();
            // 獲取客戶端的請求信息後,執行相關業務邏輯
            new LengthCalculator(socket).start();
        }
    }
}

TCPClient.java

import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;

public class TCPClient {
    public static void main(String[] args) throws Exception{
        // 創建socket,並指定連接是本機的端口號爲6499的服務器socket
        Socket socket = new Socket("127.0.0.1", 6499);
        // 獲取輸出流
        OutputStream os = socket.getOutputStream();
        // 獲取輸入流
        InputStream is = socket.getInputStream();
        // 將要傳遞給server的字符串參數轉換給byte數組,並將數組寫入到輸入流中
        os.write("hello world".getBytes());
        int ch = 0;
        byte[] buff = new byte[1024];
        // buff主要用來讀取輸入的內容,存成byte數組,ch主要用來獲取讀取數組的長度
        ch = is.read(buff);
        // 將接收流的byte數組轉換成字符串,這裏是從服務端回發回來的字符串參數的長度
        String content = new String(buff, 0, ch);
        System.out.println(content);
        // 關閉輸入輸出流以及socket
        is.close();
        os.close();
        socket.close();
    }
}

先執行Server,後執行Client,結果如下:
在這裏插入圖片描述
在這裏插入圖片描述
UDP:
UDPServer.java

import java.net.DatagramPacket;
import java.net.DatagramSocket;

public class UDPServer {
    public static void main(String[] args) throws Exception{
        // 服務器接受客戶端發送的數據報
        DatagramSocket socket = new DatagramSocket(6599); // 監聽的端口號
        byte[] buff = new byte[100]; // 存儲從客戶端接收到的內容
        DatagramPacket packet = new DatagramPacket(buff, buff.length);
        // 接受客戶端發送過來的內容,並將內容封裝進DatagramPacket對象中
        socket.receive(packet);

        byte[] data = packet.getData(); // 從DatagramPacket對象中獲取真正存儲的數據
        // 將數據從二進制轉換成字符串形式
        String content = new String(data, 0, packet.getLength());
        System.out.println(content);
        // 將要發送給客戶端的數據轉換成二進制
        byte[] sendedContent = String.valueOf(content.length()).getBytes();
        // 服務器給客戶端發送數據報
        // 從DatagramPacket對象中獲取到數據的來源地址與端口號
        DatagramPacket packetToClient = new DatagramPacket(sendedContent,
                sendedContent.length, packet.getAddress(), packet.getPort());
        socket.send(packetToClient);
    }
}

UDPClient.java

import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;

public class UDPClient {
    public static void main(String[] args) throws Exception{
        // 客戶端發送數據報給服務端
        DatagramSocket socket = new DatagramSocket();
        // 要發送給服務端的數據
        byte[] buf = "Hello UDP".getBytes();
        // 將IP地址封裝成InetAddress對象
        InetAddress address = InetAddress.getByName("127.0.0.1");
        DatagramPacket packet = new DatagramPacket(buf, buf.length, address,
                6599);
        // 發送數據給服務端
        socket.send(packet);

        // 客戶端接受服務端發送過來的數據報
        byte[] data = new byte[100];
        // 創建DatagramPacket對象用來存儲服務端發送過來的數據
        DatagramPacket reveivedPacket = new DatagramPacket(data, data.length);
        // 將接受到的數據存儲到DatagramPacket對象中
        socket.receive(reveivedPacket);

        // 將服務端發送過來的數據取過來並打印到控制檯
        String content = new String(reveivedPacket.getData(), 0,
                reveivedPacket.getLength());
        System.out.println(content);
    }
}

先執行Server,後執行Client,結果如下:
在這裏插入圖片描述
在這裏插入圖片描述

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