最新版 Java 网络编程经典案例:IM 系统、网络拷贝|万字笔记
文章目录
- 1.网络聊天小程序
-
- 1.1 设计目标
- 1.2 程序功能概述
- 1.3 详细实现步骤
-
- 1.3.1. 创建服务器端
- 1.3.2. 创建客户端
- 1.4 总结
- 2.网络聊天小程序(GUI)
-
- 2.1 设计目标
- 2.2 程序功能概述
- 2.3 详细实现步骤
-
- 2.3.1. 创建服务器端
- 2.3.2. 创建客户端的 Swing 界面
- 2.4 总结
- 3. 聊天室添加表情功能
-
- 服务端代码 (ChatServer.java)
- 客户端代码 (ChatClient.java)
- 4.文件拷贝器概述
-
- 4.1程序设计
-
- 4.1.1. GUI界面设计(Swing)
- 4.1.2. 文件拷贝任务(多线程)
- 4.1.3. 线程池的使用
- 4.2 总结
- 5.文件拷贝器
-
- 5.1. **设计目的与问题背景**
- 5.2. **功能描述**
- 5.3. **GUI设计与布局**
-
- 5.3.1 GUI组件
- 5.4. **程序设计与实现**
-
- 5.4.1 主程序结构
- 5.5. **详细解读**
-
- 5.5.1 主程序结构
- 5.5.2 文件拷贝与多线程
- 5.6. **总结**
- 6.文件拷贝器设计与实现
-
- 6.1. 项目结构
- 6.2. 实现步骤
-
- 6.2.1 GUI部分
- 6.2.2 代码解读
- 6.3 代码改进
-
- 6.3.1 代码改进解读
- 6.4 总结
1.网络聊天小程序
1.1 设计目标
设计一个基于Java的网络聊天小程序,该程序能够通过网络让多个客户端与服务器通信,并实现基本的聊天功能。这个项目旨在让学生更好地理解和运用以下Java编程知识点:
- Java网络编程:实现客户端与服务器之间的通信。
- Lambda表达式:简化代码,尤其是在处理集合时。
- Map集合:用于存储和管理客户端与其相关信息。
- List集合:管理聊天室中的消息记录。
- 多线程:支持多个客户端同时连接和发送消息。
- 线程池:提高资源利用率和管理线程。
1.2 程序功能概述
- 服务器端:
- 监听客户端的连接。
- 为每个连接创建一个独立的线程处理。
- 将消息广播给所有已连接的客户端。
- 客户端:
- 连接到服务器。
- 发送消息到服务器。
- 接收并显示来自其他客户端的消息。
1.3 详细实现步骤
1.3.1. 创建服务器端
目的:实现服务器端,能够处理多个客户端的连接请求,并将消息广播给所有客户端。
步骤:
- Socket和ServerSocket:
- 使用
ServerSocket
监听特定端口,等待客户端连接。 - 使用
Socket
对象来处理每个客户端的连接。
- 使用
- 多线程:
- 每当有客户端连接时,服务器创建一个新的线程来处理这个客户端的请求,确保服务器能够同时处理多个客户端的连接。
- 线程池:
- 使用
Executors.newFixedThreadPool()
创建一个固定大小的线程池,以避免服务器因大量线程而崩溃。
- 使用
- Map集合:
- 使用
Map<String, Socket>
来存储已连接客户端的用户名和Socket,便于广播消息时遍历所有客户端。
- 使用
- Lambda表达式:
- 使用Lambda表达式简化对集合的操作,如在广播消息时遍历Map集合。
代码示例:
import java.io.*;
import java.net.*;
import java.util.*;
import java.util.concurrent.*;
public class ChatServer {
// 服务器监听的端口号
private static final int PORT = 12345;
// 使用ConcurrentHashMap来存储客户端名称和对应的Socket,确保线程安全
private static Map<String, Socket> clients = new ConcurrentHashMap<>();
// 创建一个固定大小的线程池来处理客户端连接
private static ExecutorService pool = Executors.newFixedThreadPool(10);
public static void main(String[] args) {
try (ServerSocket serverSocket = new ServerSocket(PORT)) {
System.out.println("聊天服务器已启动...");
// 持续监听新的客户端连接
while (true) {
Socket clientSocket = serverSocket.accept();
// 每个新连接都交给线程池处理
pool.execute(new ClientHandler(clientSocket));
}
} catch (IOException e) {
e.printStackTrace();
}
}
// 内部类,处理每个客户端的连接
private static class ClientHandler implements Runnable {
private Socket clientSocket;
private String clientName;
public ClientHandler(Socket socket) {
this.clientSocket = socket;
}
@Override
public void run() {
try (BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
PrintWriter out = new PrintWriter(clientSocket.getOutputStream(), true)) {
// 读取客户端的名称
clientName = in.readLine();
// 将客户端添加到在线列表中
clients.put(clientName, clientSocket);
// 广播客户端加入的消息
broadcastMessage("Server", clientName + " 加入了聊天室!");
String message;
// 持续读取客户端消息并广播
while ((message = in.readLine()) != null) {
broadcastMessage(clientName, message);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
// 客户端断开连接时的清理工作
if (clientName != null) {
clients.remove(clientName);
broadcastMessage("Server", clientName + " 离开了聊天室.");
}
}
}
// 广播消息到所有客户端
private void broadcastMessage(String sender, String message) {
clients.forEach((name, socket) -> {
try {
PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
out.println(sender + ": " + message);
} catch (IOException e) {
e.printStackTrace();
}
});
}
}
}
代码解释:
- 端口设置:PORT变量定义了服务器监听的端口号。
- 客户端管理:clients是一个ConcurrentHashMap,用于存储客户端的名称和对应的Socket,确保在多线程环境下操作安全。
- 线程池:使用ExecutorService创建一个固定大小的线程池,避免每个新连接都创建新线程。
- 主方法:
- 启动服务器并监听指定端口。
- 接受客户端连接后,使用线程池执行ClientHandler来处理每个客户端。
- ClientHandler类:
- 实现Runnable接口,每个客户端连接时启动一个新线程。
- 从客户端读取名称,并将其加入到在线客户端列表。
- 持续读取客户端发送的消息,并通过broadcastMessage方法发送给所有其他客户端。
- 广播方法:
- broadcastMessage方法遍历所有在线客户端,将消息发送给每个客户端。
1.3.2. 创建客户端
目的:实现客户端,能够连接服务器,发送和接收消息。
步骤:
- Socket:
- 使用
Socket
对象连接到服务器。
- 使用
- 多线程:
- 使用独立线程处理从服务器接收的消息,以便客户端能够同时发送和接收消息。
- List集合:
- 存储并管理客户端的聊天记录。
代码示例:
import java.io.*;
import java.net.*;
import java.util.concurrent.*;
public class ChatClient {
// 服务器地址和端口
private static final String SERVER_ADDRESS = "localhost";
private static final int SERVER_PORT = 12345;
// 创建一个线程池,用于处理消息接收
private static ExecutorService pool = Executors.newFixedThreadPool(2);
public static void main(String[] args) {
try (
// 创建与服务器的Socket连接
Socket socket = new Socket(SERVER_ADDRESS, SERVER_PORT);
// 读取服务器发送的消息
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
// 向服务器发送消息
PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
// 从控制台读取用户输入
BufferedReader keyboard = new BufferedReader(new InputStreamReader(System.in))
) {
// 提示用户输入名字
System.out.print("请输入您的名字: ");
String name = keyboard.readLine();
// 将名字发送给服务器
out.println(name);
// 使用线程池启动一个线程来接收服务器消息
pool.execute(() -> {
try {
String message;
// 持续读取服务器消息并打印
while ((message = in.readLine()) != null) {
System.out.println(message);
}
} catch (IOException e) {
e.printStackTrace();
}
});
// 主线程用于发送消息给服务器
String message;
// 持续读取用户输入并发送
while ((message = keyboard.readLine()) != null) {
out.println(message);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
代码解释:
- 服务器连接:SERVER_ADDRESS和SERVER_PORT定义了客户端将要连接的服务器信息。
- 线程池:pool用于管理接收消息的线程,避免主线程阻塞。
- 主方法:
- 使用try-with-resources确保所有资源在使用后被关闭。
- Socket socket:创建与服务器的Socket连接。
- BufferedReader in:从服务器读取消息。
- PrintWriter out:向服务器发送消息。
- BufferedReader keyboard:从控制台读取用户输入。
- 用户输入和连接:
- 提示用户输入名字并发送给服务器。
- 消息接收:
- 使用线程池启动一个新线程来持续读取服务器消息并打印到控制台。
- 消息发送:
- 主线程持续读取用户输入并发送到服务器。
1.4 总结
这个网络聊天小程序将Java网络编程、多线程、线程池、集合框架以及Lambda表达式结合在一起,为学生提供了一个综合性的学习案例。通过该程序,学生能够深入理解如何在实际应用中使用这些编程概念,并学会设计和实现一个多用户的网络应用。
2.网络聊天小程序(GUI)
2.1 设计目标
设计一个基于 Java 的网络聊天小程序,并使用 Swing 来实现图形用户界面(GUI)。该项目旨在帮助学生掌握以下Java编程知识点:
- Java 网络编程:实现客户端与服务器之间的通信。
- Lambda 表达式:简化代码逻辑。
- Map 集合:管理客户端与其相关信息。
- List 集合:存储和管理消息记录。
- 多线程:支持多个客户端同时连接和发送消息。
- 线程池:有效管理和分配服务器的线程资源。
- Swing:创建客户端的图形用户界面。
2.2 程序功能概述
- 服务器端:
- 监听客户端的连接。
- 为每个连接创建一个独立线程处理。
- 将消息广播给所有已连接的客户端。
- 客户端:
- 使用 Swing 创建一个简单的聊天界面。
- 连接到服务器。
- 发送消息并在界面上显示接收到的消息。
2.3 详细实现步骤
2.3.1. 创建服务器端
目的:实现服务器端,处理多个客户端连接,并将消息广播给所有客户端。
步骤:
- Socket 和 ServerSocket:
- 使用
ServerSocket
监听指定端口,等待客户端连接。 - 每个连接使用
Socket
对象进行处理。
- 使用
- 多线程:
- 每当有客户端连接时,服务器创建一个新线程处理该客户端。
- 线程池:
- 使用
ExecutorService
创建一个固定大小的线程池,以提高资源利用率并管理并发线程。
- 使用
- Map 集合:
- 使用
Map<String, Socket>
存储每个已连接客户端的用户名及其对应的 Socket 对象,方便消息广播。
- 使用
- Lambda 表达式:
- 使用 Lambda 表达式简化广播消息时对集合的操作。
代码示例:
import java.io.*;
import java.net.*;
import java.util.*;
import java.util.concurrent.*;
public class ChatServer {
// 服务器监听的端口号
private static final int PORT = 12345;
// 使用 ConcurrentHashMap 来存储客户端名称和对应的 Socket,确保线程安全
private static Map<String, Socket> clients = new ConcurrentHashMap<>();
// 创建一个固定大小的线程池来处理客户端连接
private static ExecutorService pool = Executors.newFixedThreadPool(10);
public static void main(String[] args) {
try (ServerSocket serverSocket = new ServerSocket(PORT)) {
System.out.println("聊天服务器已启动...");
// 持续监听新的客户端连接
while (true) {
Socket clientSocket = serverSocket.accept();
// 每个新连接都交给线程池处理
pool.execute(new ClientHandler(clientSocket));
}
} catch (IOException e) {
e.printStackTrace();
}
}
// 内部类,处理每个客户端的连接
private static class ClientHandler implements Runnable {
private Socket clientSocket;
private String clientName;
public ClientHandler(Socket socket) {
this.clientSocket = socket;
}
@Override
public void run() {
try (BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
PrintWriter out = new PrintWriter(clientSocket.getOutputStream(), true)) {
// 读取客户端的名称
clientName = in.readLine();
// 将客户端添加到在线列表中
clients.put(clientName, clientSocket);
// 广播客户端加入的消息
broadcastMessage("Server", clientName + " 加入了聊天室!");
String message;
// 持续读取客户端消息并广播
while ((message = in.readLine()) != null) {
broadcastMessage(clientName, message);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
// 客户端断开连接时的清理工作
if (clientName != null) {
clients.remove(clientName);
broadcastMessage("Server", clientName + " 离开了聊天室.");
}
}
}
// 广播消息到所有客户端
private void broadcastMessage(String sender, String message) {
clients.forEach((name, socket) -> {
try {
PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
out.println(sender + ": " + message);
} catch (IOException e) {
e