Java 之 TCP 通信程序


一、TCP協議概述

  TCP(Transmission Control Protocol,傳輸控制協議)被稱作一種端對端協議。是一種面向連接的、可靠的、基於字節流的傳輸層的通信協議,可以連續傳輸大量的數據。

  這是因為它為當一台計算機需要與另一台遠程計算機連接時,TCP協議會采用“三次握手”方式讓它們建立一個連接,用於發送和接收數據的虛擬鏈路。數據傳輸完畢TCP協議會采用“四次揮手”方式斷開連接。

  TCP協議負責收集這些信息包,並將其按適當的次序放好傳送,在接收端收到后再將其正確的還原。TCP協議保證了數據包在傳送中准確無誤。TCP協議使用重發機制,當一個通信實體發送一個消息給另一個通信實體后,需要收到另一個通信實體確認信息,如果沒有收到另一個通信實體確認信息,則會再次重復剛才發送的消息。

  TCP 通信能實現兩台計算機之間的數據交互,通信的兩端,要嚴格區分為客戶端(Client)與服務端(Server)。

  兩端通信時步驟:

    1、服務端程序,需要事先啟動,等待客戶端的連接;

    2、客戶端主動連接服務器端,連接成功才能通信。服務端不可以主動連接客戶端。

  在 Java 中,提供了兩個類用於實現 TCP 通信程序:

    1、客戶端:java.net.Socket 類表示。創建 Socket 對象,向服務端發出連接請求,服務器響應請求,兩者建立連接開始通信。

    2、服務端:java.net.ServerSocket 類表示。創建 ServerSocket 對象,相當於開啟一個服務,並等待客戶端的連接。

二、Socket 類

  Socket 類:該類實現客戶端套接字,套接字指的是兩台設備之間通訊的端點。

  1、構造方法

public Socket(String host, int port) :創建套接字對象並將其連接到指定主機上的指定端口號。如果指定的host是null ,則相當於指定地址為回送地址。

  Tips:回送地址(127.x.x.x)是本機回送地址(Loopback Address),主要用於網絡軟件測試以及本地進程間通信,無論什么程序,一旦使用回送地址發送數據,立即返回,不進行任何網絡傳輸。

  2、成員方法

public InputStream getInputStream() : 返回此套接字的輸入流。
    •    如果此 Socket 具有相關聯的通信,則生成的 InputStream 的所有操作也關聯該通道。
    •    關閉生成的 InputStream 也將關閉相關的 Socket。
public OutputStream getOutputStream() : 返回此套接字的輸出流
    •    如果此 Socket 具有相關聯的通道,則生成的 OutputStream 的所有操作也關聯該通道。
    •    關閉生成的 OutputStream 也將關閉相關的 Socket。
public void close() :關閉此套接字
    •  一旦一個 Socket 被關閉,它不可再使用。
    •   關閉此 Socket 也將關閉相關的 InputStream 和 OutputStream。
public void shutdownOutput() : 禁用此套接字的輸出流
    •   任何先前寫出的數據將被發送,隨后終止輸出流。

三、ServerSocket類

  ServerSocket 類:這個類實現了服務器套接字,該對象等待通過網絡的請求。

  1、構造方法

public ServerSocket(int port) :使用該構造方法在創建ServerSocket對象時,就可以將其綁定到一個指定的端口號上,參數port就是端口號。

  Demo:

ServerSocket server = new ServerSocket(6666);

  2、成員方法

public Socket accept() :偵聽並接受連接,返回一個新的Socket對象,用於和客戶端實現通信。該方法會一直阻塞直到建立連接。

  

四、基於 TCP 協議的網絡通信程序結構

  Java 語言的基於套接字 TCP 編程分為服務端編程和客戶端編程,其通信模型如圖所示:

  

   服務器程序的工作過程包含以下五個基本的步驟:

   (1)使用 ServerSocket(int port) :創建一個服務器端套接字,並綁定到指定端口上。用於監聽客戶端的請求;

   (2)調用 accept()方法:監聽連接請求,如果客戶端請求連接,則接受連接,創建與該客戶端的通信套接字對象。否則該方法將一直處於等待

   (3)調用 該Socket對象的 getOutputStream() 和 getInputStream ():獲取輸出流和輸入流,開始網絡數據的發送和接收

   (4)關閉Socket對象:某客戶端訪問結束,關閉與之通信的套接字

   (5)關閉ServerSocket:如果不再接收任何客戶端的連接的話,調用close()進行關閉

  客戶端 Socket 的工作過程包含以下四個基本的步驟:

   (1)創建 Socket:根據指定服務端的 IP 地址或端口號構造 Socket 類對象,創建的同時會自動向服務器方發起連接。若服務器端響應,則建立客戶端到服務器的通信線路。若連接失敗,會出現異常

   (2)打開連接到Socket 的輸入/出流:使用 getInputStream()方法獲得輸入流,使用 getOutputStream()方法獲得輸出流,進行數據傳輸

   (3)進行讀/寫操作:通過輸入流讀取服務器發送的信息,通過輸出流將信息發送給服務器

   (4)關閉 Socket:斷開客戶端到服務器的連接

   注意:

    客戶端和服務器端在獲取輸入流和輸出流時要對應,否則容易死鎖。例如:客戶端先獲取字節輸出流(即先寫),那么服務器端就先獲取字節輸入流(即先讀);反過來客戶端先獲取字節輸入流(即先讀),那么服務器端就先獲取字節輸出流(即先寫)

四、簡單的 TCP 網絡程序

  TCP 通信分析圖解

    1、【服務端】啟動,創建 ServerSocket 對象,等待連接。

   2、【客戶端】啟動,創建 Socket 對象,請求連接。

   3、【服務端】接收連接,調用 accept 方法,並返回一個 Socket 對象

   4、【客戶端】Socket 對象,獲取 OutputStream ,向服務端寫出數據

   5、【服務端】Socket對象,獲取 InputStream,讀取客戶端發送的數據。

    到此,客戶端向服務端發送數據成功。

  

     自此,服務端向客戶端回寫數據。

   6、【服務端】Socket對象,獲取 OutputStream,向客戶端回寫數據。

   7、【客戶端】Socket對象,獲取 InputStream,解析回寫數據。

   8、【客戶端】釋放資源,斷開連接。

  客戶端向服務器發送數據

    服務端實現:

 1 public class ServerTCP {  2     public static void main(String[] args) throws IOException {  3       System.out.println("服務端啟動 , 等待連接 .... ");  4     // 1.創建 ServerSocket對象,綁定端口,開始等待連接
 5     ServerSocket ss = new ServerSocket(6666);  6     // 2.接收連接 accept 方法, 返回 socket 對象.
 7     Socket server = ss.accept();  8     // 3.通過socket 獲取輸入流
 9     InputStream is = server.getInputStream(); 10     // 4.一次性讀取數據 11     // 4.1 創建字節數組
12     byte[] b = new byte[1024]; 13     // 4.2 據讀取到字節數組中.
14     int len = is.read(b); 15     // 4.3 解析數組,打印字符串信息
16     String msg = new String(b, 0, len); 17     System.out.println(msg); 18     //5.關閉資源.
19     is.close(); 20     server.close(); 21   } 22 }

 

    客戶端實現:

 1 public class ClientTCP {  2   public static void main(String[] args) throws Exception {  3     System.out.println("客戶端 發送數據");  4     // 1.創建 Socket ( ip , port ) , 確定連接到哪里.
 5     Socket client = new Socket("localhost", 6666);  6     // 2.獲取流對象 . 輸出流
 7     OutputStream os = client.getOutputStream();  8     // 3.寫出數據.
 9     os.write("你好么? tcp ,我來了".getBytes()); 10     // 4. 關閉資源 .
11     os.close(); 12     client.close(); 13   } 14 }

  服務器向客戶端回寫數據

    服務端實現:

 1 public class ServerTCP {  2   public static void main(String[] args) throws IOException {  3     System.out.println("服務端啟動 , 等待連接 .... ");  4     // 1.創建 ServerSocket對象,綁定端口,開始等待連接
 5     ServerSocket ss = new ServerSocket(6666);  6     // 2.接收連接 accept 方法, 返回 socket 對象.
 7     Socket server = ss.accept();  8     // 3.通過socket 獲取輸入流
 9     InputStream is = server.getInputStream(); 10     // 4.一次性讀取數據 11     // 4.1 創建字節數組
12     byte[] b = new byte[1024]; 13     // 4.2 據讀取到字節數組中.
14     int len = is.read(b); 15     // 4.3 解析數組,打印字符串信息
16     String msg = new String(b, 0, len); 17     System.out.println(msg); 18     // =================回寫數據======================= 19     // 5. 通過 socket 獲取輸出流
20     OutputStream out = server.getOutputStream(); 21     // 6. 回寫數據
22     out.write("我很好,謝謝你".getBytes()); 23     // 7.關閉資源.
24     out.close(); 25     is.close(); 26     server.close(); 27   } 28 }

    客戶端實現:

 1 public class ClientTCP {  2   public static void main(String[] args) throws Exception {  3     System.out.println("客戶端 發送數據");  4     // 1.創建 Socket ( ip , port ) , 確定連接到哪里.
 5     Socket client = new Socket("localhost", 6666);  6     // 2.通過Scoket,獲取輸出流對象
 7     OutputStream os = client.getOutputStream();  8     // 3.寫出數據.
 9     os.write("你好么? tcp ,我來了".getBytes()); 10     // ==============解析回寫========================= 11     // 4. 通過Scoket,獲取 輸入流對象
12     InputStream in = client.getInputStream(); 13     // 5. 讀取數據數據
14     byte[] b = new byte[100]; 15     int len = in.read(b); 16     System.out.println(new String(b, 0, len)); 17     // 6. 關閉資源 .
18     in.close(); 19     os.close(); 20     client.close(); 21   } 22 }

 

五、文件上傳案例

  文件上傳分析圖解

  1.【客戶端】輸入流,從硬盤讀取文件數據到程序中。

  2.【客戶端】輸出流,寫出文件數據到服務端。

  3.【服務端】輸入流,讀取文件數據到服務程序。

  4.【服務端】輸出流,寫出文件數據到服務器硬盤中。

 

   基本實現

  服務端實現:

 1 public class FileUpload_Server {  2   public static void main(String[] args) throws IOException {  3     System.out.println("服務器 啟動..... ");  4     // 1. 創建服務端ServerSocket
 5     ServerSocket serverSocket = new ServerSocket(6666);  6     // 2. 建立連接
 7     Socket accept = serverSocket.accept();  8     // 3. 創建流對象  9     // 3.1 獲取輸入流,讀取文件數據
10     BufferedInputStream bis = new BufferedInputStream(accept.getInputStream()); 11     // 3.2 創建輸出流,保存到本地 .
12     BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("copy.jpg")); 13     // 4. 讀寫數據
14     byte[] b = new byte[1024 * 8]; 15     int len; 16     while ((len = bis.read(b)) != ‐1) { 17       bos.write(b, 0, len); 18     } 
19     //5. 關閉 資源 20     bos.close(); 21     bis.close(); 22     accept.close(); 23     System.out.println("文件上傳已保存"); 24   } 25 }

 

  客戶端實現:

 1 public class FileUPload_Client {  2   public static void main(String[] args) throws IOException {  3     // 1.創建流對象  4     // 1.1 創建輸入流,讀取本地文件
 5     BufferedInputStream bis = new BufferedInputStream(new FileInputStream("test.jpg"));  6     // 1.2 創建輸出流,寫到服務端
 7     Socket socket = new Socket("localhost", 6666);  8     BufferedOutputStream bos = new BufferedOutputStream(socket.getOutputStream());  9     //2.寫出數據.
10     byte[] b = new byte[1024 * 8 ]; 11     int len ; 12     while (( len = bis.read(b))!=‐1) { 13       bos.write(b, 0, len); 14       bos.flush(); 15     } 16     System.out.println("文件發送完畢"); 17     // 3.釋放資源
18     bos.close(); 19     socket.close(); 20     bis.close(); 21     System.out.println("文件上傳完畢 "); 22   } 23 }

 

  文件上傳優化分析:

  1、文件名稱寫死的問題

    服務端,保存文件的名稱如果寫死,那么最終導致服務器硬盤,只會保留一個文件,建議使用系統時間優化,保證文件名唯一性。

    代碼實現:

FileOutputStream fis = new FileOutputStream(System.currentTimeMillis()+".jpg") // 文件名稱
BufferedOutputStream bos = new BufferedOutputStream(fis);

  2、循環接收的問題

    服務端,指保存一個文件就關閉了,之后的用戶無法再上傳,這是不符合實際的,使用循環改進,可以不斷的接收不同用戶的文件。

    代碼實現:

// 每次接收新的連接,創建一個Socket
while(true){
Socket accept = serverSocket.accept();
......
}

  3、效率問題

    服務端,在接收大文件時,可能消耗幾秒鍾的時間,此時不能接收其他用戶上傳,所以,使用多線程技術優化。

    代碼實現:

while(true){
Socket accept = serverSocket.accept();
// accept 交給子線程處理.
new Thread(() ‐> {
......
InputStream bis = accept.getInputStream();
......
}).start();
}

  優化實現:

 1 public class FileUpload_Server { 2   public static void main(String[] args) throws IOException { 3     System.out.println("服務器 啟動..... "); 4     // 1. 創建服務端ServerSocket
 5     ServerSocket serverSocket = new ServerSocket(6666); 6     // 2. 循環接收,建立連接
 7     while (true) { 8       Socket accept = serverSocket.accept(); 9       /* 10       3. socket對象交給子線程處理,進行讀寫操作 11       Runnable接口中,只有一個run方法,使用lambda表達式簡化格式 12       */
13       new Thread(() ‐> { 14         try ( 15           //3.1 獲取輸入流對象
16           BufferedInputStream bis = new BufferedInputStream(accept.getInputStream()); 17           //3.2 創建輸出流對象, 保存到本地 .
18           FileOutputStream fis = new FileOutputStream(System.currentTimeMillis() +19 ".jpg"); 20           BufferedOutputStream bos = new BufferedOutputStream(fis);) { 21           // 3.3 讀寫數據
22           byte[] b = new byte[1024 * 8]; 23           int len; 24           while ((len = bis.read(b)) != ‐1) { 25             bos.write(b, 0, len); 26           } 27           //4. 關閉 資源
28           bos.close(); 29           bis.close(); 30           accept.close(); 31           System.out.println("文件上傳已保存"); 32         } catch (IOException e) { 33           e.printStackTrace(); 34         } 35       }).start(); 36     } 37   } 38 }

 

  信息回寫分析圖解

   前四部與基本文件上傳一致。

   5.【服務端】獲取輸出流,回寫數據。

   6.【客戶端】獲取輸入流,解析回寫數據。

  

   回寫實現:

    服務端實現:

 1 public class FileUpload_Server {  2   public static void main(String[] args) throws IOException {  3     System.out.println("服務器 啟動..... ");  4     // 1. 創建服務端ServerSocket
 5     ServerSocket serverSocket = new ServerSocket(6666);  6     // 2. 循環接收,建立連接
 7     while (true) {  8       Socket accept = serverSocket.accept();  9       /*
10       3. socket對象交給子線程處理,進行讀寫操作 11       Runnable接口中,只有一個run方法,使用lambda表達式簡化格式 12       */
13       new Thread(() ‐> { 14         try ( 15           //3.1 獲取輸入流對象
16           BufferedInputStream bis = new BufferedInputStream(accept.getInputStream()); 17           //3.2 創建輸出流對象, 保存到本地 .
18           FileOutputStream fis = new FileOutputStream(System.currentTimeMillis() +".jpg"); 19           BufferedOutputStream bos = new BufferedOutputStream(fis); 20          ) { 21           // 3.3 讀寫數據
22           byte[] b = new byte[1024 * 8]; 23           int len; 24           while ((len = bis.read(b)) != ‐1) { 25             bos.write(b, 0, len); 26           } 
27           // 4.=======信息回寫===========================
28           System.out.println("back ........"); 29           OutputStream out = accept.getOutputStream(); 30           out.write("上傳成功".getBytes()); 31           out.close(); 32           //================================ 33           //5. 關閉 資源
34           bos.close(); 35           bis.close(); 36           accept.close(); 37           System.out.println("文件上傳已保存"); 38          } catch (IOException e) { 39             e.printStackTrace(); 40          } 41         }).start(); 42      } 43   } 44 }

 

    客戶端實現:

 1 public class FileUpload_Client {  2   public static void main(String[] args) throws IOException {  3     // 1.創建流對象  4     // 1.1 創建輸入流,讀取本地文件
 5     BufferedInputStream bis = new BufferedInputStream(new FileInputStream("test.jpg"));  6     // 1.2 創建輸出流,寫到服務端
 7     Socket socket = new Socket("localhost", 6666);  8     BufferedOutputStream bos = new BufferedOutputStream(socket.getOutputStream());  9     //2.寫出數據.
10     byte[] b = new byte[1024 * 8 ]; 11     int len ; 12     while (( len = bis.read(b))!=‐1) { 13       bos.write(b, 0, len); 14     } 
15     // 關閉輸出流,通知服務端,寫出數據完畢 16     socket.shutdownOutput(); 17     System.out.println("文件發送完畢"); 18     // 3. =====解析回寫============
19     InputStream in = socket.getInputStream(); 20     byte[] back = new byte[20]; 21     in.read(back); 22     System.out.println(new String(back)); 23     in.close(); 24     // ============================ 25     // 4.釋放資源
26     socket.close(); 27     bis.close(); 28   } 29 }

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM