1.計算機網絡
IP
-
定義與作用 :IP 地址是在網絡中用于標識設備的數字標簽,它允許網絡中的設備之間相互定位和通信。每一個設備在特定網絡環境下都有一個唯一的 IP 地址,以此來確定其在網絡中的位置。
-
分類 :常見的 IP 地址分為 IPv4 和 IPv6。
-
IPv4 地址是一個 32 位的二進制數,通常被分為 4 個字節,表示為十進制形式,如 192.168.1.1。
-
由于 IPv4 地址數量有限,逐漸出現了 IPv6 地址, 它是128 位的二進制數,通常表示為 8 組 16 進制數,如 2001:0db8:85a3:0000:0000:8a2e:0370:7334。
端口
-
定義與作用 :端口是操作系統中的一個抽象概念,用于區分同一設備上運行的不同程序。在 TCP/IP 協議族中,端口號是一個 16 位的數字,范圍從 0 到 65535。當數據到達設備后,系統會根據端口號將數據轉發給對應的程序進行處理。
-
分類 :端口可以分為三大類。
-
一類是熟知端口(0 - 1023),這些端口通常被系統服務所使用,如 HTTP 協議使用 80 端口,HTTPS 協議使用 443 端口等。
-
另一類是注冊端口(1024 - 49151),這些端口可以被用戶進程或應用程序使用,但需要在相關機構進行注冊。
-
還有一類是動態和 / 或私有端口(49152 - 65535),這些端口可以自由使用,通常用于臨時會話或應用程序內部通信。
TCP/UDP 傳輸協議
-
TCP 協議
-
特點 :TCP(Transmission Control Protocol,傳輸控制協議)是一種面向連接的、可靠的、基于字節流的傳輸層通信協議。它在正式通信之前需要建立連接,如同打電話之前先撥號建立通話線路一樣。在數據傳輸過程中,TCP 通過三次握手建立連接,確保雙方都準備好數據進行傳輸。并且它采用流量控制、擁塞控制等機制,能夠有效避免網絡擁塞和數據丟失。
-
應用場景 :適用于對數據準確性要求高、數據傳輸量較大的場景,如文件傳輸、郵件傳輸、網頁瀏覽等。
-
-
UDP 協議
-
特點 :UDP(User Datagram Protocol,用戶數據報協議)是一種無連接的傳輸層協議。它不建立連接就直接發送數據,數據傳輸就像是直接投遞一封信,不考慮對方是否準備好接收。UDP 不保證的數據可靠傳輸,數據可能會丟失、重復或亂序到達。不過,UDP 的傳輸速度相對較快,因為它不需要進行繁瑣的連接建立和維護過程。
-
應用場景 :適用于對實時性要求高、對數據丟失不太敏感的場景,如視頻直播、在線游戲、實時語音通信等。
-
服務器與客戶端
-
服務器
-
定義與作用 :服務器是網絡環境中的高性能計算機或設備,它運行特定的服務器軟件,為客戶端提供各種服務和資源。
-
工作原理 :服務器通常會一直保持監聽狀態,等待客戶端的連接請求。當收到請求后,服務器會根據請求的內容和協議進行相應的處理,并將處理結果返回給客戶端。為了確保能夠同時處理多個客戶端的請求,服務器通常會采用多線程、多進程或異步 IO 等技術。
-
-
客戶端
-
定義與作用 :客戶端是用戶直接使用并與服務器進行交互的設備或軟件。它向服務器發送請求,并接收服務器返回的響應。
-
工作原理 :客戶端一般需要知道服務器的 IP 地址和端口號,通過建立與服務器的連接,按照特定的協議格式發送請求消息。在接收到服務器的響應后,客戶端會對響應進行解析和處理,以實現用戶期望的功能。
-
IO 模型
-
同步 IO 模型
-
阻塞 IO :阻在塞 IO 模型下,客戶端調用 IO 操作后會一直阻塞,直到操作完成并返回結果。例如,在文件讀取操作中,如果采用阻塞 IO,那么在讀取文件內容的過程中,程序會暫停執行,等待文件讀取完成才能繼續向下執行。這種方式的缺點是資源利用率較低,因為線程或進程在等待 IO 完成期間無法進行其他工作。
-
非阻塞 IO :非阻塞 IO 模型與阻塞 IO 不同,它在調用 IO 操作時會立即返回,不會一直等待操作完成。但需要客戶端不斷輪詢 IO 操作的狀態,直到操作完成。例如,客戶端發送一個非阻塞的網絡請求后,會不斷地詢問操作系統這個請求是否完成,這種方式會增加 CPU 的負擔,因為需要不斷地進行輪詢操作。
-
-
異步 IO 模型
-
特點 :異步 IO 模型是基于事件通知機制的。客戶端發起 IO 操作后,會立即返回,操作系統會在 IO 操作完成時主動通知客戶端。這樣客戶端可以在等待 IO 完成期間繼續執行其他任務,提高了資源的利用率。例如,在網頁瀏覽器中,當用戶點擊一個按鈕觸發一個異步請求時,瀏覽器可以繼續響應用戶的其他操作,如滾動頁面等,而不會被這個請求所阻塞。
-
優勢 :可以提高程序的并發能力和響應速度,特別適用于高并發的場景,如 Web 服務器處理大量客戶端請求時,采用異步 IO 能夠更有效地利用系統資源,提高服務器的性能。
-
通信架構模式
-
C/S 架構(Client/Server 架構)
-
特點 :C/S 架構是一種基于請求 - 響應模式的架構。客戶端負責與用戶交互,將用戶請求發送給服務器;服務器負責處理客戶端請求,并將處理結果返回給客戶端。這種架構需要在客戶端安裝專門的客戶端軟件,如銀行的客戶端應用程序、企業的內部辦公軟件等。
-
優點 :客戶端軟件可以提供豐富的用戶界面和交互功能,能夠對硬件資源進行充分利用,同時可以更好地控制數據的安全性和完整性。
-
缺點 :客戶端軟件的安裝和維護成本較高,當需要進行軟件升級時,需要對所有客戶端進行更新。并且客戶端對服務器的依賴性較強,服務器的性能和穩定性直接影響到整個系統的運行。
-
-
B/S 架構(Browser/Server 架構)
-
特點 :B/S 架構是基于瀏覽器和服務器的架構。客戶端只需要安裝瀏覽器,通過瀏覽器訪問服務器上的 Web 應用程序。服務器負責處理所有請求和業務邏輯,并返回相應的網頁給客戶端進行展示。例如,各種在線購物網站、社交媒體平臺等都是基于 B/S 架構的。
-
優點 :客戶端無需安裝專門的軟件,只要能夠訪問網絡并打開瀏覽器即可使用。軟件的升級和維護只需要在服務器端進行,降低了維護成本和復雜度。并且具有很好的兼容性和跨平臺性,可以在不同的操作系統和設備上使用。
-
缺點 :相比 C/S 架構,B/S 架構下的網頁應用在用戶交互和界面體驗方面可能會受到瀏覽器的限制。同時,服務器端的負載相對較大,需要處理大量的請求和業務邏輯。
-
2.核心API
Socket
-
定義與作用 :Socket(套接字)是網絡通信中的一種抽象概念,它是網絡通信過程中的一個端點,用于實現不同設備之間的雙向通信。通過 Socket,應用程序可以在網絡中進行數據的發送和接收,就好像在設備之間建立了一條虛擬的通信管道。
-
使用流程 :對于 TCP Socket,客戶端先創建 Socket,然后連接到服務器的特定端口和 IP 地址;服務器端創建 Socket,綁定到本地的某個端口和 IP 地址,開始監聽連接請求,接收客戶端連接后,雙方就可以通過 Socket 進行數據的讀寫操作。對于 UDP Socket,不需要建立連接,客戶端和服務器端都可以直接發送和接收數據報。
ServerSocket
-
定義與作用 :ServerSocket 是服務器端用于監聽和接受客戶端連接請求的套接字。它在服務器端等待客戶端的連接,當客戶端請求連接時,ServerSocket 會接受請求并創建一個新的 Socket 來與客戶端進行通信,從而實現服務器與多個客戶端的同時通信。
-
工作原理 :ServerSocket 綁定到本地的某個端口,然后開始監聽該端口的連接請求。當客戶端發送連接請求時,ServerSocket 會接受這個請求,并返回一個與客戶端通信的 Socket 對象。服務器端可以通過這個 Socket 對象與客戶端進行數據的讀寫操作,而 ServerSocket 則繼續監聽該端口,等待其他客戶端的連接請求。
DNS 域名解析服務器
-
定義與作用 :DNS(Domain Name System)域名解析服務器是用于將域名轉換為 IP 地址的服務器。在互聯網中,用戶通常使用域名來訪問網站,而計算機之間通信需要使用 IP 地址。DNS 服務器的作用就是將用戶輸入的域名解析為對應的 IP 地址,使得用戶可以通過域名方便地訪問網站。
-
工作原理 :當用戶在瀏覽器中輸入一個域名時,瀏覽器會首先向本地 DNS 服務器發送域名解析請求。本地 DNS 服務器會先在自己的緩存中查找該域名對應的 IP 地址,如果找到則直接返回給瀏覽器;如果沒有找到,本地 DNS 服務器會向根 DNS 服務器發送請求,根 DNS 服務器會根據域名的頂級域名(如.com、.net 等)返回相應的頂級域名服務器的地址,本地 DNS 服務器再向頂級域名服務器發送請求,頂級域名服務器會根據域名的二級域名返回相應的權威 DNS 服務器的地址,本地 DNS 服務器最后向權威 DNS 服務器發送請求,權威 DNS 服務器會返回該域名對應的 IP 地址,本地 DNS 服務器將這個 IP 地址緩存起來并返回給瀏覽器,瀏覽器就可以使用這個 IP 地址來訪問網站了。
InputStream
-
定義與作用 :InputStream 是 Java 中的一個輸入流類,它用于從源讀取字節數據。源可以是文件、網絡連接、內存緩沖區等。InputStream 是所有字節輸入流的父類,它提供了一組基本的方法用于讀取數據,如 read() 方法用于讀取單個字節,read(byte[] b) 方法用于將數據讀入一個字節數組等。
-
常見子類與特點
-
FileInputStream :用于從文件中讀取數據。它可以打開一個文件進行讀取,讀取到文件末尾時返回 - 1 表示結束。
-
BufferedInputStream :對其他輸入流進行緩沖處理,提高讀取效率。它通過內部維護一個緩沖區,減少對底層數據源的讀取次數,從而提高讀取速度。
-
ObjectInputStream :用于讀取 Java 對象序列化的數據。它可以將序列化的對象從輸入流中還原為 Java 對象,前提是該對象的類實現了 Serializable 接口。
-
OutputStream
-
定義與作用 :OutputStream 是 Java 中的一個輸出流類,它用于將字節數據寫入目標。目標可以是文件、網絡連接、內存緩沖區等。OutputStream 是所有字節輸出流的父類,它提供了一組基本的方法用于寫入數據,如 write(int b) 方法用于寫入單個字節,write(byte[] b) 方法用于將字節數組中的數據寫入輸出流等。
-
常見子類與特點
-
FileOutputStream :用于將數據寫入文件。它可以創建一個新文件或追加數據到現有文件中。
-
BufferedOutputStream :對其他輸出流進行緩沖處理,提高寫入效率。它通過內部維護一個緩沖區,減少對底層數據目標的寫入次數,從而提高寫入速度。
-
ObjectOutputStream :用于將 Java 對象序列化后寫入輸出流。它可以將 Java 對象轉換為字節流序列,便于存儲或傳輸,前提是該對象的類實現了 Serializable 接口。
-
3.代碼
服務器端代碼(MyChatServer)
服務器啟動
public class MyChatServer {public static void main(String[] args) {try {// 創建服務器套接字,監聽端口12000ServerSocket serverSocket = new ServerSocket(12000);System.out.println("服務器啟動,等待客戶端連接......");
-
ServerSocket
:用于監聽特定端口的傳入連接請求。 -
serverSocket.accept()
:阻塞等待客戶端連接,直到有客戶端連接時返回一個Socket
對象。
處理客戶端連接
// 接受客戶端連接Socket socket = serverSocket.accept();System.out.println("客戶端已連接:" + socket.getInetAddress() + ":" + socket.getPort());
-
socket.getInetAddress()
:獲取客戶端的IP地址。 -
socket.getPort()
:獲取客戶端的端口號。
獲取輸入輸出流
// 獲取輸入輸出流InputStream is = socket.getInputStream();OutputStream os = socket.getOutputStream();BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); // 從控制臺讀取消息BufferedReader serverIn = new BufferedReader(new InputStreamReader(is)); // 讀取客戶端消息
-
socket.getInputStream()
:獲取輸入流,用于讀取客戶端發送的數據。 -
socket.getOutputStream()
:獲取輸出流,用于向客戶端發送數據。 -
BufferedReader
:用于從控制臺或輸入流讀取文本數據。
通信循環
System.out.println("等待客戶端消息......");while (true) {// 讀取客戶端發送的消息int messageLength = is.read(); // 讀取消息長度byte[] messageBuffer = new byte[messageLength];for (int i = 0; i < messageBuffer.length; i++) {messageBuffer[i] = (byte) is.read();}String clientMessage = new String(messageBuffer);System.out.println("收到客戶端消息:" + clientMessage);// 檢查是否是退出命令if ("exit".equalsIgnoreCase(clientMessage)) {System.out.println("客戶端已斷開連接");break;}// 從控制臺讀取要發送給客戶端的消息System.out.print("請輸入要發送的消息:");String serverMessage = br.readLine();// 發送消息給客戶端os.write(serverMessage.getBytes().length); // 發送消息長度os.write(serverMessage.getBytes()); // 發送消息內容os.flush();// 檢查是否是退出命令if ("exit".equalsIgnoreCase(serverMessage)) {System.out.println("服務器將斷開連接");break;}}
-
服務器首先讀取消息的長度,然后根據長度讀取消息內容。
-
消息以字節數組的形式傳輸,然后轉換為字符串。
-
如果收到"exit"消息,服務器將退出通信循環。
-
服務器從控制臺讀取消息,并將其發送給客戶端。
-
消息發送前,先發送消息長度,以便客戶端知道要讀取多少字節。
關閉資源
// 關閉資源br.close();os.close();is.close();socket.close();serverSocket.close();} catch (IOException e) {e.printStackTrace();}}
}
-
逐個關閉資源,確保沒有資源泄漏。
完整代碼
public class MyChatServer {public static void main(String[] args) {try {// 創建服務器套接字,監聽端口12000ServerSocket serverSocket = new ServerSocket(12000);System.out.println("服務器啟動,等待客戶端連接......");// 接受客戶端連接Socket socket = serverSocket.accept();System.out.println("客戶端已連接:" + socket.getInetAddress() + ":" + socket.getPort());// 獲取輸入輸出流InputStream is = socket.getInputStream();OutputStream os = socket.getOutputStream();BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); // 從控制臺讀取消息BufferedReader serverIn = new BufferedReader(new InputStreamReader(is)); // 讀取客戶端消息System.out.println("等待客戶端消息......");while (true) {// 讀取客戶端發送的消息int messageLength = is.read();byte[] messageBuffer = new byte[messageLength];for (int i = 0; i < messageBuffer.length; i++) {messageBuffer[i] = (byte) is.read();}String clientMessage = new String(messageBuffer);System.out.println("收到客戶端消息:" + clientMessage);// 檢查是否是退出命令if ("exit".equalsIgnoreCase(clientMessage)) {System.out.println("客戶端已斷開連接");break;}// 從控制臺讀取要發送給客戶端的消息System.out.print("請輸入要發送的消息:");String serverMessage = br.readLine();// 發送消息給客戶端os.write(serverMessage.getBytes().length);os.write(serverMessage.getBytes());os.flush();// 檢查是否是退出命令if ("exit".equalsIgnoreCase(serverMessage)) {System.out.println("服務器將斷開連接");break;}}// 關閉資源br.close();os.close();is.close();socket.close();serverSocket.close();} catch (IOException e) {e.printStackTrace();}}
}
客戶端代碼(ChatClient)
連接到服務器
public class ChatClient {public static void main(String[] args) {try {// 連接到服務器Socket socket = new Socket("127.0.0.1", 12000);System.out.println("客戶端已連接到服務器");
-
Socket
:用于建立與服務器的連接。 -
"127.0.0.1"
:本地主機IP地址,表示客戶端和服務器在同一臺機器上。 -
12000
:服務器監聽的端口號。
獲取輸入輸出流
// 獲取輸入輸出流InputStream is = socket.getInputStream();OutputStream os = socket.getOutputStream();BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); // 從控制臺讀取消息
-
客戶端同樣需要獲取輸入輸出流,用于與服務器通信。
通信循環
System.out.println("等待服務器消息......");while (true) {// 從控制臺讀取要發送的消息System.out.print("請輸入要發送的消息:");String clientMessage = br.readLine();// 發送消息給服務器os.write(clientMessage.getBytes().length); // 發送消息長度os.write(clientMessage.getBytes()); // 發送消息內容os.flush();// 檢查是否是退出命令if ("exit".equalsIgnoreCase(clientMessage)) {System.out.println("客戶端將斷開連接");break;}// 讀取服務器的響應消息int messageLength = is.read(); // 讀取消息長度byte[] messageBuffer = new byte[messageLength];for (int i = 0; i < messageBuffer.length; i++) {messageBuffer[i] = (byte) is.read();}String serverMessage = new String(messageBuffer);System.out.println("收到服務器消息:" + serverMessage);// 檢查是否是退出命令if ("exit".equalsIgnoreCase(serverMessage)) {System.out.println("服務器已斷開連接");break;}}
-
客戶端的通信邏輯與服務器類似,先發送消息,然后等待服務器的響應。
-
如果收到"exit"消息,客戶端將退出通信循環。
關閉資源
// 關閉資源br.close();os.close();is.close();socket.close();} catch (IOException e) {e.printStackTrace();}}
}
完整代碼
public class ChatClient {public static void main(String[] args) {try {// 連接到服務器Socket socket = new Socket("127.0.0.1", 12000);System.out.println("客戶端已連接到服務器");// 獲取輸入輸出流InputStream is = socket.getInputStream();OutputStream os = socket.getOutputStream();BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); // 從控制臺讀取消息System.out.println("等待服務器消息......");while (true) {// 從控制臺讀取要發送的消息System.out.print("請輸入要發送的消息:");String clientMessage = br.readLine();// 發送消息給服務器os.write(clientMessage.getBytes().length);os.write(clientMessage.getBytes());os.flush();// 檢查是否是退出命令if ("exit".equalsIgnoreCase(clientMessage)) {System.out.println("客戶端將斷開連接");break;}// 讀取服務器的響應消息int messageLength = is.read();byte[] messageBuffer = new byte[messageLength];for (int i = 0; i < messageBuffer.length; i++) {messageBuffer[i] = (byte) is.read();}String serverMessage = new String(messageBuffer);System.out.println("收到服務器消息:" + serverMessage);// 檢查是否是退出命令if ("exit".equalsIgnoreCase(serverMessage)) {System.out.println("服務器已斷開連接");break;}}// 關閉資源br.close();os.close();is.close();socket.close();} catch (IOException e) {e.printStackTrace();}}
}
這個示例展示了一個基本的客戶端-服務器Socket通信模型。服務器監聽特定端口,客戶端連接到該端口,雙方可以通過輸入輸出流互相發送和接收消息。消息傳輸時,先發送消息長度,然后發送消息內容,這樣可以確保接收方能夠完整地讀取消息。