習題
以下是這些 TCP 通信練習題的 Java 代碼實現及解析:
TCP 通信練習 1 - 多發多收
客戶端(Client1.java)
java
import java.io.IOException; import java.io.OutputStream; import java.net.Socket; ? public class Client1 {public static void main(String[] args) {try (Socket socket = new Socket("localhost", 8888)) {OutputStream os = socket.getOutputStream();String[] messages = {"Hello", "World", "Java"};for (String message : messages) {os.write(message.getBytes());os.flush();}} catch (IOException e) {e.printStackTrace();}} }
服務器(Server1.java)
java
import java.io.IOException; import java.io.InputStream; import java.net.ServerSocket; import java.net.Socket; ? public class Server1 {public static void main(String[] args) {try (ServerSocket serverSocket = new ServerSocket(8888);Socket socket = serverSocket.accept();InputStream is = socket.getInputStream()) {byte[] buffer = new byte[1024];int len;while ((len = is.read(buffer)) != -1) {String message = new String(buffer, 0, len);System.out.println("收到客戶端消息: " + message);}} catch (IOException e) {e.printStackTrace();}} }
解析:
-
客戶端:創建 Socket 連接到本地主機的 8888 端口,獲取輸出流,循環發送多個字符串消息。
-
服務器:創建 ServerSocket 監聽 8888 端口,接受客戶端連接后獲取輸入流,通過循環讀取輸入流數據,直到沒有數據可讀,打印接收到的消息。
TCP 通信練習 2 - 接收和反饋
客戶端(Client2.java)
java
import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.Socket; ? public class Client2 {public static void main(String[] args) {try (Socket socket = new Socket("localhost", 8888)) {OutputStream os = socket.getOutputStream();os.write("你好".getBytes());os.flush(); ?InputStream is = socket.getInputStream();byte[] buffer = new byte[1024];int len = is.read(buffer);String feedback = new String(buffer, 0, len);System.out.println("收到服務器反饋: " + feedback);} catch (IOException e) {e.printStackTrace();}} }
服務器(Server2.java)
java
import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.ServerSocket; import java.net.Socket; ? public class Server2 {public static void main(String[] args) {try (ServerSocket serverSocket = new ServerSocket(8888);Socket socket = serverSocket.accept();InputStream is = socket.getInputStream();OutputStream os = socket.getOutputStream()) {byte[] buffer = new byte[1024];int len = is.read(buffer);String message = new String(buffer, 0, len);System.out.println("收到客戶端消息: " + message); ?os.write("已收到".getBytes());os.flush();} catch (IOException e) {e.printStackTrace();}} }
解析:
-
客戶端:連接到服務器,發送一條消息后,等待接收服務器反饋并打印。
-
服務器:接受客戶端連接,讀取客戶端發送的消息并打印,然后向客戶端發送反饋消息。
TCP 通信練習 3 - 上傳文件
客戶端(Client3.java)
java
import java.io.*; import java.net.Socket; ? public class Client3 {public static void main(String[] args) {try (Socket socket = new Socket("localhost", 8888);FileInputStream fis = new FileInputStream("test.txt");OutputStream os = socket.getOutputStream()) {byte[] buffer = new byte[1024];int len;while ((len = fis.read(buffer)) != -1) {os.write(buffer, 0, len);}socket.shutdownOutput(); ?InputStream is = socket.getInputStream();byte[] feedbackBuffer = new byte[1024];int feedbackLen = is.read(feedbackBuffer);String feedback = new String(feedbackBuffer, 0, feedbackLen);System.out.println(feedback);} catch (IOException e) {e.printStackTrace();}} }
服務器(Server3.java)
java
import java.io.*; import java.net.ServerSocket; import java.net.Socket; ? public class Server3 {public static void main(String[] args) {try (ServerSocket serverSocket = new ServerSocket(8888);Socket socket = serverSocket.accept();InputStream is = socket.getInputStream();FileOutputStream fos = new FileOutputStream("received.txt")) {byte[] buffer = new byte[1024];int len;while ((len = is.read(buffer)) != -1) {fos.write(buffer, 0, len);}OutputStream os = socket.getOutputStream();os.write("文件上傳成功".getBytes());os.flush();} catch (IOException e) {e.printStackTrace();}} }
解析:
-
客戶端:連接服務器,讀取本地文件并通過 Socket 輸出流發送給服務器,發送完畢后關閉輸出流,然后接收服務器反饋。
-
服務器:監聽端口接受連接,通過輸入流讀取客戶端發送的數據寫入新文件,文件接收完畢后向客戶端發送反饋。
TCP 通信練習 4 - 上傳文件(文件名重復問題)
解決方案思路
在服務器端接收文件時,檢查文件名是否已存在,如果存在可以在文件名后添加序號或時間戳等方式進行重命名。以下是簡單示例代碼在 Server3 基礎上修改:
java
import java.io.*; import java.net.ServerSocket; import java.net.Socket; import java.util.Date; ? public class Server4 {public static void main(String[] args) {try (ServerSocket serverSocket = new ServerSocket(8888);Socket socket = serverSocket.accept();InputStream is = socket.getInputStream()) {String fileName = "received.txt";File file = new File(fileName);int count = 1;while (file.exists()) {String filePrefix = fileName.substring(0, fileName.lastIndexOf('.'));String fileSuffix = fileName.substring(fileName.lastIndexOf('.'));fileName = filePrefix + "(" + count + ")" + fileSuffix;file = new File(fileName);count++;}try (FileOutputStream fos = new FileOutputStream(file)) {byte[] buffer = new byte[1024];int len;while ((len = is.read(buffer)) != -1) {fos.write(buffer, 0, len);}OutputStream os = socket.getOutputStream();os.write("文件上傳成功".getBytes());os.flush();}} catch (IOException e) {e.printStackTrace();}} }
解析:在服務器接收文件前,先檢查文件名是否存在,若存在則通過添加序號的方式生成新文件名,再進行文件接收操作。
TCP 通信練習 5 - 上傳文件(多線程版)
服務器(Server5.java)
java
import java.io.*; import java.net.ServerSocket; import java.net.Socket; ? public class Server5 {public static void main(String[] args) {try (ServerSocket serverSocket = new ServerSocket(8888)) {while (true) {Socket socket = serverSocket.accept();new Thread(() -> {try (InputStream is = socket.getInputStream();FileOutputStream fos = new FileOutputStream("received_" + System.currentTimeMillis() + ".txt")) {byte[] buffer = new byte[1024];int len;while ((len = is.read(buffer)) != -1) {fos.write(buffer, 0, len);}System.out.println("文件接收成功");} catch (IOException e) {e.printStackTrace();}}).start();}} catch (IOException e) {e.printStackTrace();}} }
客戶端(可復用 Client3.java)
解析:服務器通過無限循環接受客戶端連接,每接收到一個連接就創建一個新線程來處理文件接收,這樣可以同時處理多個客戶端的上傳請求。
TCP 通信練習 6 - 上傳文件(線程池優化)
服務器(Server6.java)
java
import java.io.*; import java.net.ServerSocket; import java.net.Socket; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; ? public class Server6 {private static final int THREAD_POOL_SIZE = 5;private static ExecutorService executorService = Executors.newFixedThreadPool(THREAD_POOL_SIZE); ?public static void main(String[] args) {try (ServerSocket serverSocket = new ServerSocket(8888)) {while (true) {Socket socket = serverSocket.accept();executorService.submit(() -> {try (InputStream is = socket.getInputStream();FileOutputStream fos = new FileOutputStream("received_" + System.currentTimeMillis() + ".txt")) {byte[] buffer = new byte[1024];int len;while ((len = is.read(buffer)) != -1) {fos.write(buffer, 0, len);}System.out.println("文件接收成功");} catch (IOException e) {e.printStackTrace();}});}} catch (IOException e) {e.printStackTrace();}} }
客戶端(可復用 Client3.java)
解析:使用線程池(這里是固定大小線程池)來管理線程,避免頻繁創建和銷毀線程,提高系統資源利用率,當有客戶端連接時,將任務提交給線程池處理。
TCP 通信練習 7 - BS(接收瀏覽器的消息并打印)
服務器(Server7.java)
java
import java.io.*; import java.net.ServerSocket; import java.net.Socket; ? public class Server7 {public static void main(String[] args) {try (ServerSocket serverSocket = new ServerSocket(8080);Socket socket = serverSocket.accept();InputStream is = socket.getInputStream();BufferedReader br = new BufferedReader(new InputStreamReader(is))) {String line;while ((line = br.readLine()) != null) {System.out.println(line);if (line.isEmpty()) {break;}}} catch (IOException e) {e.printStackTrace();}} }
解析:服務器監聽 8080 端口(常用 HTTP 端口),接受瀏覽器連接后,通過 BufferedReader 逐行讀取瀏覽器發送的 HTTP 請求消息并打印,當讀取到空行時(HTTP 請求頭結束標志)停止讀取。
TCP 通信練習 8 - 控制臺版的聊天室
客戶端(ChatClient.java)
java
import java.io.*; import java.net.Socket; import java.util.Scanner; ? public class ChatClient {public static void main(String[] args) {try (Socket socket = new Socket("localhost", 8888);OutputStream os = socket.getOutputStream();PrintWriter pw = new PrintWriter(os, true);InputStream is = socket.getInputStream();BufferedReader br = new BufferedReader(new InputStreamReader(is));Scanner scanner = new Scanner(System.in)) {new Thread(() -> {try {String message;while ((message = br.readLine()) != null) {System.out.println(message);}} catch (IOException e) {e.printStackTrace();}}).start(); ?String input;while ((input = scanner.nextLine()) != null) {pw.println(input);if ("exit".equals(input)) {break;}}} catch (IOException e) {e.printStackTrace();}} }
服務器(ChatServer.java)
java
import java.io.*; import java.net.ServerSocket; import java.net.Socket; import java.util.ArrayList; import java.util.List; ? public class ChatServer {private static List<PrintWriter> writers = new ArrayList<>(); ?public static void main(String[] args) {try (ServerSocket serverSocket = new ServerSocket(8888)) {while (true) {Socket socket = serverSocket.accept();new Thread(() -> {try (InputStream is = socket.getInputStream();BufferedReader br = new BufferedReader(new InputStreamReader(is));OutputStream os = socket.getOutputStream();PrintWriter pw = new PrintWriter(os, true)) {writers.add(pw);String message;while ((message = br.readLine()) != null) {for (PrintWriter writer : writers) {writer.println(message);}if ("exit".equals(message)) {writers.remove(pw);break;}}} catch (IOException e) {e.printStackTrace();}}).start();}} catch (IOException e) {e.printStackTrace();}} }
解析:
-
客戶端:創建 Socket 連接服務器,啟動一個線程用于接收服務器轉發的消息并打印,通過 Scanner 讀取控制臺輸入,將輸入消息發送給服務器,輸入 “exit” 時退出。
-
服務器:監聽端口接受客戶端連接,為每個連接創建新線程,將每個客戶端的輸出流(PrintWriter)保存到列表中,讀取客戶端發送的消息并轉發給所有客戶端,當客戶端發送 “exit” 時從列表中移除該客戶端的輸出流。
以上代碼基于 Java 語言實現,在實際運行時,需根據情況調整端口等配置,并且要注意異常處理等方面的優化。