目錄
一、網絡編程
二、客戶端和服務器
三、客戶端和服務器的交互模式
四、TCP 和 UDP
UDP socket api 的使用
1、DatagramSoket
2、DatagramPacket
TCP socket api 的使用
1、ServerSocket
2、Socket?
一、網絡編程
本質上就是學習傳輸層給應用層提供的 api,通過 api 把數據交給傳輸層,進一步地層層封裝將數據通過網卡發送出去,這也是網絡程序的基本工作流程。
掌握了基礎 api 就能更好的理解實際開發中使用的框架(spring,dubbo)的工作過程,也提供了魔改/自己實現框架的能力。
二、客戶端和服務器
在網絡中,主動發起通信的一方稱為“客戶端”,被動接受的一方稱為“服務器”。同一個程序在不同的場景中,可能是客戶端也可能是服務器。
客戶端給服務器發送的數據,稱為“請求”(request);
服務器給客戶端返回的數據,稱為“響應”(response);
三、客戶端和服務器的交互模式
1、“一問一答”
一個請求對于一個響應,這是交互模式是最常見的,后續進行的“網站開發”(web開發)都是這種模式。
2、“一問多答”
主要在“下載”場景中涉及
3、“多問一答”
主要在“上傳”場景中涉及
4、“多問多答”
主要在“遠程控制/遠程桌面”場景中涉及
四、TCP 和 UDP
進行網絡編程,需要使用系統的 API,【本質上是傳輸層提供的協議】。
傳輸層主要涉及到兩個協議:TCP 和 UDP。
連接性 | 可靠性 | 面向 | 數據傳輸方式 | |
TCP | 面向連接 | 可靠傳輸 | 面向字節流 | 全雙工 |
UDP | 無連接 | 不可靠傳輸 | 面向數據報 | 全雙工 |
- 連接:此處說的“連接”不是物理意義的連接,是抽象虛擬的“連接”。所謂計算機中的“網絡連接”是指通信雙方各自保存對方的信息。客戶端的數據結構中記錄了誰是它的服務器;服務器的數據結構中記錄了誰是它的客戶端;本質上就是記錄對方的信息。
- 可靠傳輸/不可靠傳輸:無論如何都不能保證100%的信息傳輸。可靠傳輸主要是指發送方能夠感知數據有沒有傳輸給接收方,如果沒接收到,可以采取相應的措施補救,例如重傳機制。
- 面向字節流:與文件中的字節流完全一致,網絡中傳輸數據的基本單位就是字節。
- 面向數據報:每次傳輸的基本單位是一個數據報(有一系列字節構成)。
- 全雙工:一個信道,可以雙向通信,就叫全雙工。可以理解成馬路的多車道,就是全雙工。
- 半雙工:可以理解為吸管,同一時刻只能吸或者呼。
UDP socket api 的使用
Java 把系統原生 api 封裝了,UDP socket 提供的兩個核心的類:
1、DatagramSoket
操作系統中有一類文件,就叫 socket 文件,這類文件抽象地表示了“網卡”這樣的硬件設備。而進行網絡通信最核心的硬件設備就是網卡。
DatagramSocket 類就是負責對 socket 文件進行讀寫,從而借助網卡發送接收數據。
2、DatagramPacket
UDP 面向數據報,每次發送接收數據的基本單位是一個 UDP 數據報。
DatagramPacket 類就表示了一個 UDP 數據報。

UdpEchoServer 實例
public class UdpEchoServer {private DatagramSocket socket = null;public UdpEchoServer(int port) throws SocketException {socket = new DatagramSocket(port);}public void start() throws IOException {System.out.println("服務器啟動");while (true) {//每次循環,都是一次處理請求,進行響應的過程//1. 讀取請求并解析DatagramPacket requestPacket = new DatagramPacket(new byte[4096], 4096);socket.receive(requestPacket);// 將讀到的字節數組轉換成 String 方便后續操作String request = new String(requestPacket.getData(),0,requestPacket.getLength());//2. 根據請求計算響應String response = process(request);//3. 把響應返回到客戶端// 與請求數據報創建不同,請求數據報是使用空白字節數組,而此處直接把 String 里包含的字節數組作為參數創建,DatagramPacket responsePacket = new DatagramPacket(response.getBytes(), response.getBytes().length,requestPacket.getSocketAddress()); // 因為 UDP 無連接,因此必須從【請求數據報】中獲取對應客戶端的 ip 和端口socket.send(responsePacket);//打印日志System.out.printf("[%s:%d] req: %s, resp: %s\n",requestPacket.getAddress().toString(),requestPacket.getPort(), request, response);}}private String process(String request) {return request;}public static void main(String[] args) throws IOException {UdpEchoServer server = new UdpEchoServer(9090);server.start();}
}
?上述代碼中:
1、可以看到 23 行需要從【請求數據報】中獲取對應客戶端 ip 和端口號才能完成發送響應,證明了 UDP socket 自身不保存對端的 ip 和端口號,體現了無連接。
2、不可靠傳輸,代碼中沒有體現。
3、receive 和 socket 都是以DatagramPacket 為單位,體現了面向數據報。
4、一個 socket 既能發送(send)有能接收(receive),體現了全雙工。
UdpEchoClient 示例?
public class UdpEchoClient {private DatagramSocket socket = null;private String serverIp;private int serverPort;public UdpEchoClient(String serverIp, int serverPort) throws SocketException {// 客戶端,正常情況下不需要指定端口socket = new DatagramSocket();this.serverIp = serverIp;this.serverPort = serverPort; // 客戶端對應的服務器端口號}public void start() throws IOException {System.out.println("客戶端啟動");Scanner sc = new Scanner(System.in);while (true) {//1. 從控制臺讀取要發送的數據System.out.print("-> "); //表示提示用戶輸入if (!sc.hasNext()) { //hasNext 具有阻塞功能break;}String request = sc.next();//2. 構造請求并發送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.println(response);}}public static void main(String[] args) throws IOException {UdpEchoClient client = new UdpEchoClient("127.0.0.1", 9090);client.start();}
}
TCP socket api 的使用
由于 TCP 是面向字節流的,傳輸的基本單位是字節,因此沒有像 UDP 中 DatagramPacket 這樣的類。
Java 把系統原生 api 封裝了,TCP socket 提供的兩個核心的類:
1、ServerSocket
這是Socket 類,同樣抽象地表示了“網卡”但是這個類與 UDP 中使用的 DatagramSocket 不同,這個類只能給服務器進行使用。只負責處理對客戶端的連接,主要 api 是 accept()。
2、Socket?
對應到“網卡”,既能給服務器使用,又能給客戶端使用。相當于電話的兩個聽筒,通過 Socket 完成對端之間的通信。主要的 api 是 getInputStream 和 getOutputStream。
需要注意:由于服務器端的 Socket 對象與客戶端時一一對應的,為了避免無限占用文件描述符表,使用完畢后需要 close 關閉。
TcpEchoServer 示例
public class TcpEchoServer {private ServerSocket serverSocket = null;public TcpEchoServer(int port) throws IOException {serverSocket = new ServerSocket(port);}public void start() throws IOException {System.out.println("服務器啟動!");while (true) {// 當客戶端創建出 socket 后(new socket),就會和對應的服務器進行 tcp 連接建立流程// 此時通過 accept 方法來“接聽電話”,然后才能進行通信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(), clientSocket.getPort());// 循環讀取客戶端的請求并返回響應try (InputStream inputStream = clientSocket.getInputStream();OutputStream outputStream = clientSocket.getOutputStream()){// 可以使用 inputStream 原本的 read 方法進行讀取// 但是比較繁瑣,為了【方便讀入】,這里使用 Scanner 對輸入流進行輸入Scanner sc = new Scanner(inputStream);while (true) { // 長連接if (!sc.hasNext()) {// 讀取完畢,客戶端斷開連接System.out.printf("[%s:%d] 客戶端下線!\n", clientSocket.getInetAddress(), clientSocket.getPort());break;}//1. 讀取請求并解析,此處使用 next ,需要注意 next 的讀入規則String request = sc.next();//2. 根據請求計算響應String response = process(request);//3. 把響應返回給客戶端/* 通過這種方式也可以寫回,但是這種方式不方便添加 \noutputStream.write(response.getBytes(),0,response.getBytes().length);*/// 因此為了【方便寫入】,給 outputStream 也套一層,即使用 printWriter// 此處的 printWriter 就類似于 Scanner 將輸入流包裝了一下,而 printWriter 對輸出流包裝了一下PrintWriter printWriter = new PrintWriter(outputStream);// 通過 println 在末尾添加了 \n,與客戶端的 scNetwork.next 呼應printWriter.println(response);// 刷新緩沖區,確保數據能夠發送出去printWriter.flush();// 打印日志System.out.printf("[%s:%d] req: %s, resp: %s\n", clientSocket.getInetAddress(), clientSocket.getPort(),request,response);}} catch (IOException e) {throw new RuntimeException(e);} finally {clientSocket.close();}} private String process(String request) {return request;}public static void main(String[] args) throws IOException {TcpEchoServer server = new TcpEchoServer(9090);server.start();}
}
需要注意的是:
1、理解ServerSocket 和Socket 的不同作用,Socket作為接收對象。
2、只有當客戶端 new Socket 時,ServerSocket 才能通過 accept 完成連接。
3、Scanner 和 PrintWriter 。
4、flush 刷新緩沖區。
5、finaly{ clientSocket.close(); }?每個客戶端對應一個Socket,因此每個客戶端完成任務后,需要關閉文件,從而銷毀文件描述符表。而 try()自動關閉的是流對象,而沒有釋放文件本體。
TcpEchoClient 示例
public class TcpEchoClient {private Socket socket = null;public TcpEchoClient(String serverIp, int serverPort) throws IOException {// 這里直接將 ip 和 port 傳入,是由于 tcp 是有連接的,socket 里能夠保存 ip 和 portsocket = new Socket(serverIp,serverPort);// 因此也不需要額外創建【類成員對象】來保存 ip 和 port}public void start() {System.out.println("客戶端啟動!");try (InputStream inputStream = socket.getInputStream();OutputStream outputStream = socket.getOutputStream()){// 此處的 scanner 用于控制臺讀取數據Scanner scConsole = new Scanner(System.in);// 此處的 scanner 用于讀取服務器響應回來的數據Scanner scNetwork = new Scanner(inputStream);// 此處 printWriter 用于向服務器寫入請求數據PrintWriter printWriter = new PrintWriter(outputStream);while (true) {// 這里流程和 UDP 的客戶端類似//1. 從控制臺讀取輸入的字符串System.out.print("-> ");if (!scConsole.hasNext()) {break;}String request = scConsole.next();//2. 把請求發送給服務器,// 使用 printWriter 是為了使發送的請求末尾帶有 \n,與服務器的 sc.next 呼應printWriter.println(request);// 刷新緩沖區,確保數據能夠發送出去printWriter.flush();//3. 從服務器讀取響應String response = scNetwork.next();//4. 打印響應System.out.println(response);}} catch (IOException e) {throw new RuntimeException(e);}}public static void main(String[] args) throws IOException {TcpEchoClient client = new TcpEchoClient("127.0.0.1", 9090);client.start();}
}
需要注意的是:
1、TCP 是有連接的,因此 Socket 能夠直接保存 ip 和 port。
2、flush 刷新緩沖區。
【博主推薦】
【Java多線程】面試常考——鎖策略、synchronized的鎖升級優化過程以及CAS(Compare and swap)-CSDN博客https://blog.csdn.net/zzzzzhxxx/article/details/136288256?spm=1001.2014.3001.5501【Java多線程】對線程池的理解并模擬實現線程池-CSDN博客
https://blog.csdn.net/zzzzzhxxx/article/details/136160003?spm=1001.2014.3001.5501【Java多線程】分析線程加鎖導致的死鎖問題以及解決方案-CSDN博客
https://blog.csdn.net/zzzzzhxxx/article/details/136150237?spm=1001.2014.3001.5501
如果覺得作者寫的不錯,求給博主一個大大的點贊支持一下,你們的支持是我更新的最大動力!
如果覺得作者寫的不錯,求給博主一個大大的點贊支持一下,你們的支持是我更新的最大動力!
如果覺得作者寫的不錯,求給博主一個大大的點贊支持一下,你們的支持是我更新的最大動力!