一、概述
記錄時間 [2025-08-29]
前置文章:
網絡編程 01:計算機網絡概述,網絡的作用,網絡通信的要素,以及網絡通信協議與分層模型
網絡編程 02:IP 地址,IP 地址的作用、分類,通過 Java 實現 IP 地址的信息獲取
網絡編程 03:端口的定義、分類,端口映射,通過 Java 實現了 IP 和端口的信息獲取
本文講述網絡編程相關知識——TCP連接,包括客戶端與服務器的區別,如何實現 TCP 聊天及文件上傳等。
同時,文章簡單介紹了 Tomcat(服務器)的相關使用。
關于創作紀念日
維持現狀。512 紀念日快樂。
里程碑專區
二、TCP
1. TCP 聊天
思路整理
客戶端和服務器之間如何進行通信——創建連接 + 收發消息。
客戶端
- 連接服務器 Socket
- 發送消息
服務器
- 建立服務的端口 ServerSocket
- 等待用戶的連接 accept
- 接收用戶消息
客戶端和服務器之間收發消息通過 I/O 流來實現。
- 發送消息,
socket.getOutputStream()
- 接收消息,
socket.getInputStream()
為防止接收消息亂碼,接收方需要使用管道流來處理接收到的消息。
new ByteArrayOutputStream()
所有資源在使用后都需要正確關閉,如,socket,serverSocket 等。
- 關閉資源的順序為:先開后關;
- 關閉資源前要先判斷它是否為空,非空則關閉;
- 關閉資源操作需要拋出異常。
服務器代碼實現
服務器先啟動,處在監聽過程中,等待客戶端的連接。
服務器有一個地址(IP + Port),通過這個地址,客戶端才能和服務器連接。
通過 serverSocket.accept()
獲取連接過來的客戶端,就是客戶端的 socket
。
然后讀取客戶端的信息。
經過管道處理后,輸出信息。
結束后關閉資源。
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;// 服務端
public class TcpServerDemo01 {public static void main(String[] args) {// 服務端地址ServerSocket serverSocket = null;// 連接過來的客戶端Socket socket = null;// 輸入流InputStream is = null;// 管道流ByteArrayOutputStream baos = null;try {// 1. 我得有一個地址serverSocket = new ServerSocket(9999);// 循環等待客戶端連接過來while (true) {// 2. 等待客戶端連接過來socket = serverSocket.accept();// 3. 讀取客戶端的消息// 消息從客戶端流出 Out,流進服務器 Inputis = socket.getInputStream();// 管道流// 給流進來的消息套一個管道,得到從管道中流出來的消息,所有用 Outputbaos = new ByteArrayOutputStream();byte[] buffer = new byte[1024];int len;while ((len = is.read(buffer)) != -1) {baos.write(buffer, 0, len);}System.out.println(baos.toString());}} catch (IOException e) {e.printStackTrace();} finally {// 4. 關閉資源, 判非空, 然后先開后關// null 了就沒必要關了if (baos != null) {try {baos.close();} catch (IOException e) {e.printStackTrace();}}if (is != null) {try {is.close();} catch (IOException e) {e.printStackTrace();}}if (socket != null) {try {socket.close();} catch (IOException e) {e.printStackTrace();}}if (serverSocket != null) {try {serverSocket.close();} catch (IOException e) {e.printStackTrace();}}}}
}
客戶端代碼實現
客戶端通過服務器的地址(IP + Port)連接上服務器,連接的方式是 socket
。
客戶端給服務器發消息。
結束后關閉資源。
import java.io.IOException;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;// 客戶端
public class TcpClientDemo01 {public static void main(String[] args) {// 客戶端連接Socket socket = null;// 輸出流OutputStream os = null;try {// 1. 要知道服務器的地址, 端口號InetAddress serverIP = InetAddress.getByName("127.0.0.1");int port = 9999;// 2. 創建一個 socket 連接socket = new Socket(serverIP, port);// 3. 發送 IO 消息流os = socket.getOutputStream();os.write("你好,歡迎".getBytes());} catch (Exception e) {e.printStackTrace();} finally {// 4. 關閉資源, 判非空, 然后先開后關if (os != null) {try {os.close();} catch (IOException e) {e.printStackTrace();}}if (socket != null) {try {socket.close();} catch (IOException e) {e.printStackTrace();}}}}
}
2. TCP 文件上傳
文件流,流的概念
客戶端給服務端傳文件:
- 文件通過文件管道流出:客戶端中,先流入(In)文件管道,再流出(Out)去到服務端。
- 文件流入(In)服務端,流入要讀(read);服務端用文件管道流出(Out),流出要寫(write), 就是保存。
就相當于客戶端流出,到服務端流入,然后它們自己可以套管道,管道一頭流入,另一頭流出。
流入要讀(read), 流出要寫(write)
while ((len = fis.read(buffer)) != -1) {os.write(buffer, 0, len);
}
工作目錄
當我們要讀取一個文件時,得先知道該文件的位置,也就是文件路徑。
new File("file")
:獲取項目文件的方法,需要傳入的參數是文件路徑,如果是相對路徑的話,起始路徑為當前 Java 項目的工作目錄。
在項目中有一個圖片資源,如何獲取這個圖片的相對路徑呢?需要通過 Java 項目的工作目錄。
獲取當前 Java 項目的工作目錄的方式:
public class TestPath {public static void main(String[] args) {String currentDir = System.getProperty("user.dir");System.out.println("當前工作目錄: " + currentDir);}
}
例如:
當前工作目錄為:C:\JavaSE
;
NetStudy
是 JavaSE
項目中的一個模塊,圖片資源 tx.jpg
位于 JavaSE/NetStudy
目錄下;
那么 tx.jpg
的獲取方式是:new File("NetStudy/tx.jpg")
服務器代碼實現
服務器監聽、等待客戶端的連接。
接收客戶端發送過來的文件,通過文件管道流處理后,保存文件。(這里的文件是一張圖片)
通知客戶端,文件接收完成。
結束后關閉資源。
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;// 服務端
public class TcpServerDemo02 {public static void main(String[] args) throws Exception {// 1. 創建服務, 給出連接的端口ServerSocket serverSocket = new ServerSocket(9000);// 2. 監聽客戶端的連接// 一直等待, 會阻塞直到有客戶端連接// 這個 socket 就是客戶端的 socketSocket socket = serverSocket.accept();// 3. 獲取輸入流// 創建一個輸入流,用來輸入客戶端的流InputStream is = socket.getInputStream();// 4. 文件輸出, 接收客戶端的文件// 用文件管道流寫出文件, 給出文件保存到位置和文件名FileOutputStream fos = new FileOutputStream(new File("NetStudy/receive.jpg"));byte[] buffer = new byte[1024];int len;while ((len = is.read(buffer)) != -1) {fos.write(buffer, 0, len);}// 5. 通知客戶端, 文件接收完成// 創建一個輸出流, 用來輸出給客戶端OutputStream os = socket.getOutputStream();// 傳信息給客戶端os.write("文件已被服務端接收完成".getBytes());// 6. 關閉資源os.close();fos.close();is.close();socket.close();serverSocket.close();}
}
客戶端代碼實現
文件先通過文件管道流讀入項目里,然后才能通過 socket 發送。
建立 socket 連接后,向服務器發送文件。
文件發送完畢后,結束輸出流,并告知服務器。(因為服務器和客戶端用的是同一個 socket,如果文件發送完不結束流的話,會影響后面的消息發送和接收)
接收服務器發送的 “完成信號”。
結束后關閉資源。
import java.io.*;
import java.net.InetAddress;
import java.net.Socket;// 客戶端
public class TcpClientDemo02 {public static void main(String[] args) throws Exception {// 1. 建立服務端連接,ip+portSocket socket = new Socket(InetAddress.getByName("127.0.0.1"), 9000);// 2. 創建一個輸出流// 用來輸出給服務端OutputStream os = socket.getOutputStream();// 3. 傳文件給服務端// 讀取文件: 文件輸入流, 先把文件輸入管道,管道才能讀出來// System.getProperty("user.dir"); 獲取項目的工作目錄// 獲取 tx.jpg 的相對位置FileInputStream fis = new FileInputStream(new File("NetStudy/tx.jpg"));// 讀出文件管道流中的文件, 并向服務端寫出文件byte[] buffer = new byte[1024];int len;while ((len = fis.read(buffer)) != -1) {os.write(buffer, 0, len);}// 4. 通知服務端文件傳輸完畢socket.shutdownOutput();System.out.println("通知服務端文件傳輸完畢");// 5. 確定服務端接收完畢, 才能斷開連接// 收到服務端的完成信號后,斷開連接// 創建一個輸入流,用來接收服務端的流InputStream is = socket.getInputStream();// 用管道流寫出文件ByteArrayOutputStream baos = new ByteArrayOutputStream();byte[] buffer2 = new byte[1024];int len2;while ((len2 = is.read(buffer2)) != -1) {baos.write(buffer2, 0, len2);}// 輸出管道流里的內容System.out.println(baos.toString());// 6. 關閉資源baos.close();is.close();fis.close();os.close();socket.close();}
}
三、使用 Tomcat
在前面的 TCP 中,我們講到了客戶端與服務器(Client/Server),服務器就是用來接收客戶端的請求的。
而 Tomcat,就是一個充當服務器的角色。
1. 啟動 Tomcat
可以通過腳本啟動,雙擊 bin
目錄下的 startup.bat
即可。
默認在 8080 端口下啟動,啟動成功后可通過 localhost:8080
進行訪問。
如果端口被占用,則無法成功啟動。
2. Tomcat 亂碼
Tomcat 啟動過程中會打印日志信息,不難發現,日志信息中存在亂碼現象。
導致亂碼的原因:字符編碼在解碼過程中選擇了錯誤的解碼方式。
解碼規范 / 方式:GBK,UTF-8 等。
文件在計算機中是以字符編碼的形式存儲的,而我們平常看到文字是字符串形式的。這之間就有編碼和解碼兩個操作。
而 GBK 這類規范就是告訴計算機應該用何種方式進行編碼或解碼。
那么,如果一個文件是用 GBK 進行編碼的,卻使用 UTF-8 進行解碼,那么就會導致亂碼。正所謂 “解鈴還須系鈴人”,GBK 的編碼需要用 GBK 來解碼,UTF-8 同理。
在 conf
目錄下,有日志配置文件 logging.properties
,在里面可以修改編碼 / 解碼方式。
CMD
選擇 GBKIDEA
選擇 UTF-8
要解決 IDEA 控制臺亂碼,需要同時設置 JVM 加載 .class
文件時使用 UTF-8 字符集。
-Dfile.encoding=UTF-8
3. Tomcat 訪問部署的資源
啟動 Tomcat 后,可以訪問其部署的資源,在 webapps
目錄下。
訪問根目錄:localhost:8080
;
訪問自定義資源:webapps
目錄下的 test
中的 hello.txt
文件。
http://localhost:8080/test/hello.txt
參考資料
狂神說 - 網絡編程:https://www.bilibili.com/video/BV1LJ411z7vY
Java 8 幫助文檔:https://docs.oracle.com/javase/8/docs/api/