1. 程式人生 > 實用技巧 >第十六章、網路程式設計

第十六章、網路程式設計

網路程式設計

1. 網編先導知識

  1. 網路應用開發架構

    • C / S即client(客戶端) / server(服務端)

      ​ 飛秋、Git、百度雲、輸入法....

    • B / S即browser(瀏覽器)/ server(伺服器)

      ​ 淘寶、郵箱、百度、知乎....

    • B / S是特殊的C / S架構

  2. 網絡卡:每個實際存在在計算機硬體裡面的

  3. mac地址:每塊網絡卡上都有一個全球唯一的mac地址

  4. 交換機:是連線多臺機器並幫助通訊的物理裝置,只可以識別mac地址

  5. 協議:兩臺物理裝置之間對於要傳送的內容,長度和順序做的一些規範

  6. ip地址(規格)

    • ipv4協議:韋德點分十進位制,32位的二進位制

      ​ 範圍:0.0.0.0~255.255.255.255

    • ipv6協議:8位的冒分十六制,128位十進位制來表示 (點分十進位制不足以表示)

      ​ 範圍:0:0:0:0:0:0:0:0 ~ FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF

  7. ip地址的使用:

    • 如果一個ip地址向北所有人都看到,這個ip地址是必須要申請的,即公網ip

    • 提供內網ip,供區域網使用:

      192.168.0.0 - 192.168.255.255

      172.16.0.0 - 172.31.255.255

      10.0.0.0 - 10.255.255.255

  8. 交換機實現的ARP協議(地址解析協議)

    • 通過ip地址獲取一臺機器的mac地址

    • 交換機工作原理:收到一個請求,通知所有連線他的ip地址,獲取對應的ip地址的mac地址並返回給請求的ip地址

  9. 閘道器ip:一個區域網的網路出口,訪問區域網以外的區域都需要經過路由器的閘道器

  10. 網段:一般是一個末尾為0的地址段

  11. 子網掩碼:判斷兩個機器是否在同一個網段

    遮蔽一個IP地址的網路部分的“全1”位元模式。對於A類地址來說,預設的子網掩碼是255.0.0.0;對於B類地址來說預設的子網掩碼是255.255.0.0;對於C類地址來說預設的子網掩碼是255.255.255.0

    # 示例:通過將請求的計算的子網掩碼和兩個要匹配的計算機的二進位制按位與運算
    ip = 255.255.255.255
    	11111111.11111111.11111111.11111111
    ip = 192.168.12.87
        11000000.10101000.00001100.00000111
    ip = 192.168.12.7
    
  12. ip地址可以確認一臺機器,port埠可以確認一臺機器的一個應用,埠的範圍:0~65535

  13. 一般實現互聯,使用127.0.0.1,是自己的地址,不過減緩及ip地址是可以過交換機的。

2. ISO模型(五層結構)

  1. 物理層

    • 物理層是ISO模型的最底層,負責網路裝置在各種物理介質上傳輸位元流,並規定各種各種物理傳輸介質、介面的機械特性和電氣特性。一般用位表示。
  2. 資料鏈路層

    • mac地址,ARP協議 物理裝置:網絡卡,交換機。
  3. 網路層

    • IPV4/IPV6協議,物理裝置:路由器,三層交換機(交換機具有路由功能)ip通過DNS解析獲取(DNS域名和ip互相對映的資料庫)
  4. 傳輸層

    • tcp協議和udp協議,物理裝置:埠,四層路由器,四層交換機。
  5. 應用層

    • 應用層 :https/http/ftp/smtp協議 所有的應用層協議是基於tcp或者是udp協議
  6. 資料封裝和解封

    資料封裝

    資料解封

3. 傳輸層的兩種協議(tcp/udp)

3.1 tcp協議

  1. 特點

    • 面向連線的,可靠,但是慢,可以實現全雙工通訊,即雙方都是實時的,區別於半雙工(傳呼機)
    • 無邊界,流式傳輸(導致粘包問題)
    • 長連線:會一直佔用雙方的埠
    • 能夠傳輸的資料長度幾乎沒有限制
  2. 三次握手和四次揮手

    • 具體的三次握手(連線方式):

      # accept接受過程中等待客戶端的連線
      # connect客戶端發起了一個syn連線請求(附帶了一個隨機數)
      # 如果得到了server端的響應ack的同時還會再收到一個由server端發來的syn連結請求
      # client端進行回覆ack之後,就建立起了一個tcp協議的連結
      # 備註:三次握手的過程再程式碼中是由accept和connect共同完成的,具體的細節再socket中沒有體現出來
      
    • 具體的四次揮手(斷開連線方式):

      # server和client端對應的在程式碼中都有close方法
      # 每一端發起的close操作都是一次fin的斷開請求,得到'斷開確認ack'之後,就可以結束一端的資料傳送
      # 如果兩端都發起close,那麼就是兩次請求和兩次回覆,一共是四次操作
      # 可以結束兩端的資料傳送,表示連結斷開了
      
  3. 應用場景

    • QQ和微信等上面的傳輸壓縮檔案,快取下載的電影等等

3.2 udp協議

  1. 特點
    • 面向資料報的,無連線,速度很快,類似於發簡訊,能實現一對一,多對一,一對多的高效通訊
    • 由於沒有回執,對於傳送資訊和接受資訊的雙方來說,可能存在丟失訊息的情況
    • 能夠傳遞的長度有限,是根據資料傳遞裝置的位置有關係
  2. 應用場景
    • 通訊類的 如QQ 微信,發簡訊, 線上觀看小視訊等

3.3 Socket

  1. 概念:網路上的兩個程式通過一個雙向的通訊連線實現資料的交換,這個連線的一端稱為一個socket。

  2. 原理:程序通訊之前,雙方首先必須各自建立一個端點,否則是沒有辦法建立聯絡並相互通訊的

  3. Socket之間的連線過程

    • 伺服器監聽
    • 客戶端請求
    • 連線確認
  4. java實現Socket步驟

    • 建立Socket

    • 開啟連線到Socket的輸入/出流

    • 按照一定的協議對Socket進行讀/寫操作

    • 關閉Socket

  5. 程式碼實現

    // 服務端 
    package demo2;
    
    import java.io.BufferedReader;
    import java.io.BufferedWriter;
    import java.io.IOException;
    import java.io.InputStreamReader;
    import java.io.OutputStreamWriter;
    import java.net.ServerSocket;
    import java.net.Socket;
    
    public class Server {
    	public static void main(String[] args) {
    		try {
    			// 1.建立伺服器socket服務。通過ServerSocket物件,指定埠號
    			ServerSocket ss = new ServerSocket(8888);
    			System.out.println("服務端等待連線....");
    
    			// 2.獲取連線過來的客戶端物件,使用accept()
    			Socket s = ss.accept(); // 進行阻塞態
    			System.out.println("客戶端連線成功....");
    
    			// 3.通過客戶端物件獲取socket流讀取客戶端發來的資料
    			BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream()));
    			String str = br.readLine();
    			System.out.println("客戶端發過來的訊息是:" + str);
    
    			// 4.通過客戶端物件獲取socket流向客戶端傳送資料
    			BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));
    			bw.write("你好,客戶端!");
                
    			// 關閉相關資源
    			bw.close();
    			br.close();
    			s.close();
    			ss.close();
    		} catch (IOException e) {
    			// TODO Auto-generated catch block
    			e.printStackTrace();
    		}
    	}
    }
    
    // 客戶端
    package demo2;
    
    import java.io.BufferedReader;
    import java.io.BufferedWriter;
    import java.io.InputStreamReader;
    import java.io.OutputStreamWriter;
    import java.net.Socket;
    
    public class Client {
    	public static void main(String[] args) {
    		try {
    			// Socket(String host,int port);
    			// 1.建立Socket物件,指定伺服器的ip地址和埠號
    			Socket s = new Socket("127.0.0.1", 8888);
    
    			// 2.通過客戶端物件獲取socket流讀取服務端發來的資料
    			BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));
    			bw.write("你好,服務端!");
    			bw.flush();
    			s.shutdownOutput();
    
    			// 3.通過客戶端物件獲取socket流讀取客戶端發來的資料
    			BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream()));
    			String str = br.readLine();
    			System.out.println("服務端傳送過來的資訊是:" + str);
    
    			// 4.關閉相關資源
    			br.close();
    			bw.close();
    			s.close();
    		} catch (Exception e) {
    			e.printStackTrace();
    		}
    	}
    }
    

4.基於TCP實現Socket程式設計模型

  • 服務端

    1. 建立ServerSocket類的物件,並提供埠號。

    2. 等待客戶端連線,使用accept()方法

    3. 等Socket物件,並使用輸入輸出流進行通訊

    4. 關閉相互資源

  • 客戶端

    1. 建立Socket型別的物件,並指定伺服器的IP地址和埠號
    2. 使用輸入輸出流進行通訊
    3. 關閉相互資源
  • 程式碼示例

    • 雙向通訊

      // 服務端 
      package demo2;
      
      import java.io.BufferedReader;
      import java.io.BufferedWriter;
      import java.io.IOException;
      import java.io.InputStreamReader;
      import java.io.OutputStreamWriter;
      import java.net.ServerSocket;
      import java.net.Socket;
      
      /*
       * 建立TCP伺服器端思路
       * 		1.建立伺服器socket服務。通過ServerSocket物件
       * 		2.伺服器端必須對外提供一個埠,否則客戶端無法連線
       * 		3.獲取連線過來的客戶端物件
       * 		4.通過客戶端物件獲取socket流讀取客戶端發來的資料
       * 		5.關閉資源,關閉流,關閉伺服器
       */
      public class Server {
      	public static void main(String[] args) {
      		try {
      			// 1.建立伺服器socket服務。通過ServerSocket物件,指定埠號
      			ServerSocket ss = new ServerSocket(8888);
      			System.out.println("服務端等待連線....");
      
      			// 2.獲取連線過來的客戶端物件,使用accept()
      			Socket s = ss.accept(); // 進行阻塞態
      			System.out.println("客戶端連線成功....");
      
      			// 3.通過客戶端物件獲取socket流讀取客戶端發來的資料
      			BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream()));
      			String str = br.readLine();
      			System.out.println("客戶端發過來的訊息是:" + str);
      
      			// 4.通過客戶端物件獲取socket流向客戶端傳送資料
      			BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));
      			bw.write("你好,客戶端!");
      
      			// 關閉相關資源
      			bw.close();
      			br.close();
      			s.close();
      			ss.close();
      		} catch (IOException e) {
      			// TODO Auto-generated catch block
      			e.printStackTrace();
      		}
      	}
      }
      
      // 客戶端
      package demo2;
      
      import java.io.BufferedReader;
      import java.io.BufferedWriter;
      import java.io.InputStreamReader;
      import java.io.OutputStreamWriter;
      import java.net.Socket;
      
      /*
       * 客戶端建立思路
       * 		1.建立TCP客戶端socket服務,使用的是Socket物件
       * 		2.如果連線成功,說明資料傳輸連結建立成功
       * 			通過的是socket流,是底層建立好的
       * 			既然是流的話,就應該有輸入輸出
       * 			想要獲取輸入或輸出物件,可以找socket獲取
       * 			可以通過getOutputStream()和getInputStream()來獲取兩個字元流
       * 		3.使用輸入流,將資料寫入
       * 		4.關閉資源
       * 
       * 			建立連線後,通過socket中的IO流進行資料傳輸
       *			向伺服器傳送:你好,伺服器
       *			如果想要使用字元流就需要使用OutputStreamWriter/InputStreamReader 轉換流
       */
      public class Client {
      	public static void main(String[] args) {
      
      		try {
      			// Socket(String host,int port);
      			// 1.建立Socket物件,指定伺服器的ip地址和埠號
      			Socket s = new Socket("127.0.0.1", 8888);
      
      			// 2.通過客戶端物件獲取socket流讀取服務端發來的資料
      			BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));
      			bw.write("你好,服務端!");
      			bw.flush();
      			s.shutdownOutput();
      
      			// 3.通過客戶端物件獲取socket流讀取客戶端發來的資料
      			BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream()));
      			String str = br.readLine();
      			System.out.println("服務端傳送過來的資訊是:" + str);
      
      			// 4.關閉相關資源
      			br.close();
      			bw.close();
      			s.close();
      
      		} catch (Exception e) {
      			e.printStackTrace();
      		}
      	}
      }
      
    • 多人聊天室

      // 服務端
      package demo4;
      
      import java.io.IOException;
      import java.net.ServerSocket;
      import java.net.Socket;
      
      /*
       * 要求客戶端傳送的內容由使用者手動輸入,使用BufferedReader類
      	要求伺服器收到客戶端的訊息之後,想客戶端回發訊息”I Receive!”
      	要求伺服器和客戶端可以不斷地進行通訊,當客戶端傳送”bye”時結束通訊。
      	要求服務能夠同時支援多個客戶端的連線,而且能夠和多個客戶端同時聊天,多執行緒。
       */
      public class Server {
      	public static void main(String[] args) {
      		try {
      			// 1.建立ServerSocket型別的物件。並繫結埠
      			ServerSocket ss = new ServerSocket(8999);
      
      			while (true) {
      				System.out.println("等待客戶端連線...");
      				// 等待客戶端連線請求
      				Socket s = ss.accept();
      				System.out.println("客戶端" + s.getInetAddress() + "連線成功!");
      				// 只要有客戶端連線成功,應該啟動一個新執行緒為之服務,主執行緒始終接待
      				new ServerThread(s).start();
      			}
      		} catch (IOException e) {
      			e.printStackTrace();
      		}
      	}
      }
      
      // 服務端執行緒
      package demo4;
      
      import java.io.BufferedReader;
      import java.io.InputStreamReader;
      import java.io.PrintStream;
      import java.net.Socket;
      
      public class ServerThread extends Thread {
      	private Socket s;
      	public ServerThread(Socket s) {
      		this.s = s;
      	}
      	@Override
      	public void run() {
      		try {
      			// 用來接收客戶端發來的內容
      			BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream()));
      			// 向客戶端傳送字串內容”I Receive!“
      			PrintStream ps = new PrintStream(s.getOutputStream());
                  
      			while (true) {
      				String str = br.readLine();
      
      				if ("bye".equalsIgnoreCase(str)) {
      					System.out.println("客戶端" + s.getInetAddress() + "已下線");
      					break;
      				}
      				System.out.println("客戶端" + s.getInetAddress() + "發來的訊息是:" + str);
      				ps.println("I Receive!");
      			}
      			ps.close();
      			br.close();
      			s.close();
      		} catch (Exception e) {
      			e.printStackTrace();
      		}
      	}
      }
      
      // 客戶端
      package demo4;
      
      import java.io.BufferedReader;
      import java.io.IOException;
      import java.io.InputStreamReader;
      import java.io.PrintStream;
      import java.net.Socket;
      import java.net.UnknownHostException;
      
      public class Client {
      	public static void main(String[] args) {
      		try {
      			// 建立Socket型別的物件,並提供IP地址和埠號
      			Socket s = new Socket("localhost", 8999);
      			// 使用輸入輸出流進行通訊
      			BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
      			// 用來接收服務端發來的內容
      			BufferedReader br2 = new BufferedReader(new InputStreamReader(s.getInputStream()));
      			PrintStream ps = new PrintStream(s.getOutputStream());
      			while (true) {
      				System.out.println("請輸入要傳送的內容:");
      				String str = br.readLine();
      
      				ps.println(str);
      				System.out.println("成功傳送資料到伺服器");
      
      				// 判斷客戶端傳送的內容是否是"bye",若是則結束通訊
      				if ("bye".equalsIgnoreCase(str)) {
      					break;
      				}
      
      				String str2 = br2.readLine();
      				System.out.println("伺服器端發來的訊息是:" + str2);
      
      			}
      
      			br2.close();
      			br.close();
      			ps.close();
      			s.close();
      		} catch (UnknownHostException e) {
      			// TODO Auto-generated catch block
      			e.printStackTrace();
      		} catch (IOException e) {
      			// TODO Auto-generated catch block
      			e.printStackTrace();
      		}
      	}
      }
      

5. 基於UDP實現Socket程式設計模型

  • 接收方

    1. 建立DatagramSocket型別的物件,並提供埠號

    2. 建立DatagramPacket型別的物件,用於接收發來的資料

    3. 使用上述的物件接收資料內容,使用recieve()方法

    4. 關閉相關資源

  • 傳送方

    1. 建立DatagramSocket型別的物件。
    2. 建立DatagramPacket型別的物件,並提供埠號和IP地址
    3. 使用上述的物件傳送資料內容,使用send()方法。
    4. 關閉相關資源
  • 程式碼示例

    // 傳送方
    package demo5;
    
    import java.net.DatagramPacket;
    import java.net.DatagramSocket;
    import java.net.InetAddress;
    
    public class TestRecieve {
    	public static void main(String[] args) {
    
    		try {
    			// 1.建立DatagramSocket型別的物件
    			DatagramSocket ds = new DatagramSocket();
    
    			// 2.建立DatagramPacket型別的物件,並向外提供一個埠號和ip地址
    			byte[] data = "hello!".getBytes();
    			InetAddress ia = InetAddress.getLocalHost();
    
    			DatagramPacket dp = new DatagramPacket(data, data.length, ia, 8888);
    
    			// 3.傳送資料內容,使用send()方法
    			ds.send(dp);
    			System.out.println("成功傳送資料內容!");
    
    			// 4.關閉套接字
    			ds.close();
    		} catch (Exception e) {
    			// TODO Auto-generated catch block
    			e.printStackTrace();
    		}
    	}
    }
    
    // 接收方
    package demo5;
    
    import java.net.DatagramPacket;
    import java.net.DatagramSocket;
    
    public class TestSend {
    	public static void main(String[] args) {
    
    		try {
    			// 1.建立DatagramSocket型別的物件,並提供埠號
    			DatagramSocket ds = new DatagramSocket(8888);
    
    			// 2.建立DatagramPacket型別的物件,用於接收資料內容
    			byte[] data = new byte[1024];
    
    			DatagramPacket dp = new DatagramPacket(data, data.length);
    
    			// 3.接收資料內容並儲存到資料報中,使用receive()方法
    			ds.receive(dp);
    
    			System.out.println("接收到的資料是:" + new String(data, 0, dp.getLength()) + "!");
    
    			// 4.關閉套接字
    			ds.close();
    		} catch (Exception e) {
    			// TODO Auto-generated catch block
    			e.printStackTrace();
    		}
    	}
    }