“JAVA反射机制详解”

什么是反射?

反射是与正射相对应的,正射就是在使用某个类之前就已经知道了这个类的类型等等,可以直接使用new关键字调用构造方法进行初始化,之后就可以正常使用这个类的的对象了
Java 反射机制是在运行状态中,对于任意一个类,都能够知道这个类中的所有属性和方法,对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为Java 语言的反射机制。

反射的特点

  1. 运行时类信息访问:反射允许程序在运行时获取类的完结构信息,包括类名、包名、父类、实现的接口、构造函数、方法和字段等等
  2. 动态对象创建:可以使用反射API动态地创建对象实例,即使在编译时不知道具体的类名。这是通过Class类的newInstance()方法或Constructor对象的newnstance()方法实现的。
  3. 动态方法调用:可以在运行时动态地调用对象的方法,包括私有方法。这通过Method类的invoke()方法
    实现,允许你传入对象实例和参数值来执行方法。
    4.访问和修改字段值:反射还允许程序在运行时访问和修改对象的字段值,即使是私有的。这是通过Field类的qet()和set()方法完成的

反射的缺点

破坏封装:由于反射允许访问私有字段和私有方法,所以可能会破坏封装而导致安全问题
性能开销:由于反射涉及到动态解析,因此无法执行Java 虚拟机优化,再加上反射的写法的确要复杂得多,所以性能要比“正射”差很多,在一些性能敏感的程序中应该避免使用反射。

反射的应用场景

  1. 通用框架中加载配置文件:类似spring、springboot等,为了保持通用性,通过不同的配置文件来加载不同的对象,比如数据库驱动等等
  2. 动态代理:在面向切面编程中,需要拦截特定的方法,就会选择动态代理的方式,动态代理的底层实现就是利用了反射
  3. 注解:注解本身不会直接影响程序的执行,但可以通过反射机制在运行时获取和处理这些注解

反射的基本使用

在 Java 中,Class 对象是一种特殊的对象,它代表了程序中的类和接口。Java 中的每个类型(包括类、接口、数组以及基础类型)在 JVM 中都有一个唯一的 Class 对象与之对应。这个 Class 对象被创建的时机是在 JVM 加载类时,由 JVM 自动完成。Class 对象中包含了与类相关的很多信息,如类的名称、类的父类、类实现的接口、类的构造方法、类的方法、类的字段等等。这些信息通常被称为元数据(metadata)。

获取类的Class对象

共有三种方法:

  1. 通过类的全名获取Class对象
    1
    Class clazz = Class.forName("com.xxx");
  2. 通过 Class 对象获取构造方法 Constructor 对象
    1
    Constructor constructor = clazz.getConstructor();
  3. 通过 Constructor 对象初始化反射类对象
    1
    Object object = constructor.newInstance();
  4. 获取要调用的方法的 Method 对象
    1
    2
    Method setNameMethod = clazz.getMethod("setName", String.class);
    Method getNameMethod = clazz.getMethod("getName");
  5. 通过 invoke() 方法执行
    1
    2
    setNameMethod.invoke(object, "zhangsan");
    getNameMethod.invoke(object)

反射底层机制

使用反射,必须要获得反射类的Class对象,每一个类只有一个Class对象,无论这个类生成了多少个对象。这个Class对象是由JAVA虚拟机生成的,由它来获取这个类的所有结构信息
也就是说,java.lang.Class 是所有反射 API 的入口
但是方法的反射调用都是通过Method对象的invoke()方法完成的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public Object invoke(Object obj, Object... args)
throws IllegalAccessException, IllegalArgumentException,
InvocationTargetException {
// 如果方法不允许被覆盖,进行权限检查
if (!override) {
if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
Class<?> caller = Reflection.getCallerClass();
// 检查调用者是否具有访问权限
checkAccess(caller, clazz, obj, modifiers);
}
}
// 获取方法访问器(从 volatile 变量中读取)
MethodAccessor ma = methodAccessor;
if (ma == null) {
// 如果访问器为空,尝试获取方法访问器
ma = acquireMethodAccessor();
}
// 使用方法访问器调用方法,并返回结果
return ma.invoke(obj, args);
}

invoke() 方法实际上是委派给 MethodAccessor 接口来完成的

MethodAccessor 接口有三个实现类,其中的 MethodAccessorImpl 是一个抽象类,另外两个具体的实现类 NativeMethodAccessorImplDelegatingMethodAccessorImpl 继承了这个抽象类。
NativeMethodAccessorImpl:通过本地方法来实现反射调用;
DelegatingMethodAccessorImpl:通过委派模式来实现反射调用;

invoke() 方法在执行的时候,会先调用 DelegatingMethodAccessorImpl,然后调用 NativeMethodAccessorImpl,最后再调用实际的方法

为什么不直接调用本地实现呢?

之所以采用委派实现,是为了能够在本地实现和动态实现之间切换。动态实现是另外一种反射调用机制,它是通过生成字节码的形式来实现的。如果反射调用的次数比较多,动态实现的效率就会更高,因为本地实现需要经过 Java 到 C/C++ 再到 Java 之间的切换过程,而动态实现不需要;但如果反射调用的次数比较少,反而本地实现更快一些。而这个临界点默认是15次,可以通过 -Dsun.reflect.inflationThreshold 参数类调整

反射常用的API

Class.forName(),参数为反射类的完全限定名。

类名 + .class,只适合在编译前就知道操作的 Class。

获取构造器、字段、方法等

getConstructor():返回反射类的特定 public 构造方法,可以传递参数,参数为构造方法参数对应 Class 对象;缺省的时候返回默认构造方法。
getDeclaredConstructor():返回反射类的特定构造方法,不限定于 public 的。
getConstructors():返回类的所有 public 构造方法。
getDeclaredConstructors():返回类的所有构造方法,不限定于 public 的。
获取字段和方法都是类似,换成Field或者Method就行

注意,如果反射访问私有字段和(构造)方法的话,需要使用 Constructor/Field/Method.setAccessible(true) 来绕开 Java 语言的访问限制

注:参考javabetterxiaolincoding,侵删

用JavaSocket编程开发聊天室

用JavaSocket编程开发聊天室

实验要求:

  1. 用Java图形用户界面编写聊天室服务器端和客户端, 支持多个客户端连接到一个服务器。每个客户端能够输入账号。
  2. 可以实现群聊(聊天记录显示在所有客户端界面)。
  3. 完成好友列表在各个客户端上显示。
  4. 可以实现私人聊天,用户可以选择某个其他用户,单独发送信息。
  5. 服务器能够群发系统消息,能够强行让某些用户下线。
  6. 客户端的上线下线要求能够在其他客户端上面实时刷新。
    本人目前大二,这是期末课程设计做得一个完整的玩具项目,但是由于水平和时间等问题,这个项目的设计和架构还是有些问题,比如说发送消息和指令都是使用Socket发送的,仅仅使用一些特殊字符来区别消息和指令,如果用户端直接发送和指令相同的字符串,则会导致bug。因此本项目的健壮性和可拓展性都较差,仅能作为课设使用。
    分为客户端和服务器端。
    服务器端功能:
  • 可以实现查看所有在线用户
  • 可以强制下线在线用户
  • 可以发送系统消息
  • 用户正常登录和退出会通知所有在线用户

客户端功能:

  • 输入服务器、端口和用户名即可登录
  • 可以发生群聊消息
  • 右侧显示所有在线用户
  • 双击右侧在线用户可发送私信

项目地址:
javaSocket聊天室
客户端Client:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
package client;

import java.io.*;
import java.net.Socket;
import java.net.UnknownHostException;

public class Client {
private Socket socket;
private DataOutputStream outputStream;
private PrintWriter out;
private BufferedReader in;
private String serverAddress;
private int port;
private String username;
private OnMessageReceivedListener listener;

public Client(String serverAddress, int port, String username, OnMessageReceivedListener listener) throws IOException {
this.serverAddress = serverAddress;
this.port = port;
this.username = username;
this.listener = listener;
initConnection();
}



private void initConnection() throws IOException {
if (socket != null && !socket.isClosed()) {
return;
}
socket = new Socket(serverAddress, port);
outputStream = new DataOutputStream(socket.getOutputStream());
out = new PrintWriter(new OutputStreamWriter(socket.getOutputStream(), "UTF-8"), true);
in = new BufferedReader(new InputStreamReader(socket.getInputStream(), "UTF-8"));
}

public void sendMessage(String message) {
if (outputStream != null && !socket.isClosed()) {
try {
out.println(message);
out.flush();
} catch (Exception e) {
handleSendError(e);
}
} else {
System.err.println("Socket is not properly initialized or is closed.");
}
}

public void sendPrivateMessage(String recipient, String message) {
sendMessage("/pm " + recipient + " " + message);
}

private void handleSendError(Exception e) {
e.printStackTrace();
try {
socket.close();
} catch (IOException closeException) {
closeException.printStackTrace();
}
listener.onConnectionLost();
}

public void connect() {
try {
initConnection();
sendMessage(username); // 发送用户名以登录

Thread readerThread = new Thread(() -> {
String message;
try {
while ((message = in.readLine()) != null) {
listener.onMessageReceived(message);
}
} catch (IOException e) {
listener.onConnectionLost();
e.printStackTrace();
} finally {
try {
socket.close();
} catch (IOException ex) {
ex.printStackTrace();
}
}
});
readerThread.start();
} catch (UnknownHostException e) {
System.err.println("Server not found: " + e.getMessage());
listener.onConnectionLost();
} catch (IOException e) {
System.err.println("Error connecting to server: " + e.getMessage());
listener.onConnectionLost();
}
}

public void disconnect() {
try {
if (socket != null) {
socket.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}

public String getUsername() {
return username;
}

public interface OnMessageReceivedListener {
void onMessageReceived(String message);
void onConnectionLost();
void onUpdateOnlineUsers(String userListData);
void onForceLogout();
}
}

客户端界面ClientGUI:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
package client;

import javax.swing.*;
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;

public class ClientGUI extends Component {
private JTextField serverAddressField, portField, usernameField, messageField;
private JButton connectButton, sendButton;
private JTextArea chatArea;
private JList<String> onlineList;
private DefaultListModel<String> onlineListModel;
private Client client;

public static void main(String[] args) {
SwingUtilities.invokeLater(() -> new ClientGUI().initializeUI());
}

private void initializeUI() {
JFrame frame = new JFrame("Chat Client");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(800, 600);

chatArea = new JTextArea();
chatArea.setEditable(false);
JScrollPane scrollPane = new JScrollPane(chatArea);
frame.add(scrollPane, BorderLayout.CENTER);

JPanel southPanel = new JPanel();
southPanel.setLayout(new BorderLayout());

JPanel inputPanel = new JPanel();
inputPanel.setLayout(new FlowLayout(FlowLayout.LEFT));
messageField = new JTextField(30);
sendButton = new JButton("Send");
inputPanel.add(messageField);
inputPanel.add(sendButton);
southPanel.add(inputPanel, BorderLayout.CENTER);

JPanel connectPanel = new JPanel();
connectPanel.setLayout(new FlowLayout(FlowLayout.LEFT));
serverAddressField = new JTextField("localhost", 10);
portField = new JTextField("12345", 5);
usernameField = new JTextField("User", 10);
connectButton = new JButton("Connect");
connectPanel.add(new JLabel("Server: "));
connectPanel.add(serverAddressField);
connectPanel.add(new JLabel(" Port: "));
connectPanel.add(portField);
connectPanel.add(new JLabel(" Username: "));
connectPanel.add(usernameField);
connectPanel.add(connectButton);
southPanel.add(connectPanel, BorderLayout.SOUTH);

frame.add(southPanel, BorderLayout.SOUTH);

onlineListModel = new DefaultListModel<>();
onlineList = new JList<>(onlineListModel);
JScrollPane onlineScrollPane = new JScrollPane(onlineList);
onlineScrollPane.setPreferredSize(new Dimension(150, 0));
frame.add(onlineScrollPane, BorderLayout.EAST);

createEvents();

frame.setVisible(true);
}

private void createEvents() {
connectButton.addActionListener(e -> {
String serverAddress = serverAddressField.getText();
int port = Integer.parseInt(portField.getText());
String username = usernameField.getText();
connectButton.setEnabled(false);
try {
client = new Client(serverAddress, port, username, new GUIListener());
client.connect();
} catch (Exception ex) {
JOptionPane.showMessageDialog(this, "连接失败,请检查输入信息。", "连接错误", JOptionPane.ERROR_MESSAGE);
connectButton.setEnabled(true);
}
});

sendButton.addActionListener(e -> {
String message = messageField.getText();
if (!message.isEmpty()) {
client.sendMessage(message);
displaySentMessage(message);
messageField.setText("");
}
});

onlineList.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
if (e.getClickCount() == 2) {
String selectedUser = onlineList.getSelectedValue();
if (selectedUser != null && client != null) {
String privateMessage = JOptionPane.showInputDialog(ClientGUI.this,
"输入要发送给 " + selectedUser + " 的消息:", "发送私信", JOptionPane.PLAIN_MESSAGE);
if (privateMessage != null && !privateMessage.trim().isEmpty()) {
client.sendPrivateMessage(selectedUser, privateMessage);
displaySentMessage("[私信给 " + selectedUser + "]: " + privateMessage); // 显示发送的私聊消息
}
}
}
}
});
}

private void displaySentMessage(String message) {
SwingUtilities.invokeLater(() -> {
chatArea.append(client.getUsername() + ": " + message + "\n");
chatArea.setCaretPosition(chatArea.getDocument().getLength());
});
}

private class GUIListener implements Client.OnMessageReceivedListener {
@Override
public void onMessageReceived(String message) {
SwingUtilities.invokeLater(() -> {
if (message.startsWith("/users ")) { // 检查消息是否以/users开头
onUpdateOnlineUsers(message.substring(7)); // 去掉"/users "前缀,然后更新在线用户列表
}else if(message.equals("/forceLogout")){
onForceLogout();
}else if(message.equals("/server/ERROR: 用户名已被占用,请选择其他用户名。")){
JOptionPane.showMessageDialog(ClientGUI.this, "用户名已被占用,请选择其他用户名。", "连接错误", JOptionPane.ERROR_MESSAGE);
connectButton.setEnabled(true);
client.disconnect();
}else if(message.equals("/server/SUCCESS: 连接成功")){
JOptionPane.showMessageDialog(ClientGUI.this, "连接成功", "连接状态", JOptionPane.INFORMATION_MESSAGE);
}
else {
chatArea.append(message + "\n");
chatArea.setCaretPosition(chatArea.getDocument().getLength());
}
});
}

@Override
public void onConnectionLost() {
SwingUtilities.invokeLater(() -> {
JOptionPane.showMessageDialog(ClientGUI.this, "连接丢失,请检查网络或重新登录。", "连接错误", JOptionPane.ERROR_MESSAGE);
connectButton.setEnabled(true);
});
}

@Override
public void onUpdateOnlineUsers(String userListData) {
SwingUtilities.invokeLater(() -> {
String[] usernames = userListData.split(","); // 假设用户列表是以逗号分隔的用户名
onlineListModel.clear(); // 清空现有在线用户列表
for (String username : usernames) {
onlineListModel.addElement(username.trim()); // 添加每个用户名到在线用户列表模型中
}
});
}

@Override
public void onForceLogout(){
SwingUtilities.invokeLater(() -> {
JOptionPane.showMessageDialog(null, "抱歉!您已被服务器强制下线!", "强制下线", JOptionPane.WARNING_MESSAGE);
System.exit(0); // 关闭客户端程序
});
}
}
}

Common包下Message类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
package common;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

public class Message {
private String sender;
private String recipient;
private String content;
private LocalDateTime timestamp;

// 构造函数
public Message(String sender, String recipient, String content) {
this.sender = sender;
this.recipient = recipient;
this.content = content;
this.timestamp = LocalDateTime.now(); // 当前时间作为发送时间
}

public Message(String sender, String content) {
this(sender, "Everyone", content);
}

// Getter 和 Setter 方法
public String getSender() {
return sender;
}

public void setSender(String sender) {
this.sender = sender;
}

public String getRecipient() {
return recipient;
}

public void setRecipient(String recipient) {
this.recipient = recipient;
}

public String getContent() {
return content;
}

public void setContent(String content) {
this.content = content;
}

public LocalDateTime getTimestamp() {
return timestamp;
}

// 格式化时间戳的字符串表示
public String getFormattedTimestamp() {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
return timestamp.format(formatter);
}

// 重写toString方法,便于打印或显示消息详情
@Override
public String toString() {
return String.format("[%s] %s -> %s: %s",
getFormattedTimestamp(),
sender,
recipient,
content);
}
}

服务器端Server:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
package server;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
import common.Message;


public class Server {
private static final int PORT = 12345; // 服务器端口
private final List<ServerThread> clients = new ArrayList<>(); // 存储所有连接的客户端线程
private ServerGUI gui;

public Server(ServerGUI gui) {
this.gui = gui;
}
public static void main(String[] args) {
ServerGUI gui = new ServerGUI();
Server server = new Server(gui);
gui.setServer(server);
server.startServer();
}

public void startServer() {
try (ServerSocket serverSocket = new ServerSocket(PORT)) {
System.out.println("服务器启动,正在监听端口: " + PORT);

while (true) {
Socket socket = serverSocket.accept(); // 阻塞等待客户端连接
ServerThread serverThread = new ServerThread(socket, this);
serverThread.start(); // 启动线程处理客户端请求
System.out.println("新客户端连接: " + socket.getInetAddress());
}
} catch (IOException e) {
e.printStackTrace();
System.err.println("服务器启动失败");
}
}

public synchronized void addClient(ServerThread client) {
clients.add(client);
updateOnlineUsers();
gui.updateUserList(clients);
}

public synchronized boolean removeClient(ServerThread client) {
boolean removed = clients.remove(client);
if (removed) {
updateOnlineUsers();
gui.updateUserList(clients);
}
return removed;
}

public synchronized void broadcast(Message message, ServerThread excludeClient) {
for (ServerThread client : clients) {
if (client != excludeClient) {
client.send(message);
}
}
}

public synchronized void updateOnlineUsers() {
StringBuilder userList = new StringBuilder("/users ");
for (ServerThread client : clients) {
userList.append(client.getUsername()).append(",");
}
String userListMessage = userList.toString();
if (userListMessage.endsWith(",")) {
userListMessage = userListMessage.substring(0, userListMessage.length() - 1);
}
for (ServerThread client : clients) {
client.sendRawMessage(userListMessage);
}
}

public List<ServerThread> getClients() {
return clients;
}

public void forceLogout(String username) {
for (ServerThread client : clients) {
if (client.getUsername().equals(username)) {
client.interrupt(); // 中断客户端线程以强制下线
removeClient(client); // 从列表中移除客户端
client.forceLogout();
gui.updateUserList(clients);
break;
}
}
}
public synchronized boolean isUsernameTaken(String username) {
for (ServerThread client : clients) {
if (client.getUsername().equals(username)) {
return true;
}
}
return false;
}

public synchronized void sendSystemMessage(String content) {
Message systemMessage = new Message("Server", "Everyone", content);
broadcast(systemMessage, null);
}
}

客户端界面ServerGUI:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
package server;

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.List;

public class ServerGUI extends JFrame {
private JList<String> userList;
private DefaultListModel<String> userListModel;
private Server server;

public ServerGUI() {
setTitle("Chat Server");
setSize(400, 300);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setLocationRelativeTo(null);

userListModel = new DefaultListModel<>();
userList = new JList<>(userListModel);
JScrollPane scrollPane = new JScrollPane(userList);

JButton forceLogoutButton = new JButton("Force Logout");
forceLogoutButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
String selectedUser = userList.getSelectedValue();
if (selectedUser != null) {
server.forceLogout(selectedUser);
}
}
});

// 系统消息输入框和发送按钮
JTextField systemMessageField = new JTextField();
JButton sendSystemMessageButton = new JButton("发送系统消息");
sendSystemMessageButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
String message = systemMessageField.getText();
if (message != null && !message.trim().isEmpty()) {
server.sendSystemMessage(message);
systemMessageField.setText(""); // 清空输入框
}
}
});

JPanel panel = new JPanel(new BorderLayout());

panel.add(scrollPane, BorderLayout.CENTER);
panel.add(forceLogoutButton, BorderLayout.SOUTH);

// 系统消息面板
JPanel systemMessagePanel = new JPanel(new BorderLayout());
systemMessagePanel.add(systemMessageField, BorderLayout.CENTER);
systemMessagePanel.add(sendSystemMessageButton, BorderLayout.EAST);

// 添加到主窗口
add(panel, BorderLayout.CENTER);
add(systemMessagePanel, BorderLayout.SOUTH);

setVisible(true);
}

public void updateUserList(List<ServerThread> clients) {
SwingUtilities.invokeLater(() -> {
userListModel.clear();
for (ServerThread client : clients) {
userListModel.addElement(client.getUsername());
}
});
}

public void setServer(Server server) {
this.server = server;
}
}

客户端ServerThread:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
package server;

import common.Message;

import java.io.*;
import java.net.Socket;

public class ServerThread extends Thread {
private Socket socket;
private PrintWriter out;
private BufferedReader in;
private String username;
private Server server;
private volatile boolean running = true;

public ServerThread(Socket socket, Server server) {
this.socket = socket;
this.server = server;
try {
out = new PrintWriter(socket.getOutputStream(), true);
in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
} catch (IOException e) {
e.printStackTrace();
System.err.println("Error initializing streams for client: " + e.getMessage());
}
}

@Override
public void run() {
try {
this.username = in.readLine();
if (server.isUsernameTaken(this.username)) {
sendRawMessage("/server/ERROR: 用户名已被占用,请选择其他用户名。");
socket.close();
return;
}

sendRawMessage("/server/SUCCESS: 连接成功");
System.out.println("账号" + this.username + "已经登录");
server.addClient(this); // 注意不要在构造函数中重复调用 addClient
server.broadcast(new Message(username, "Server", username + " has joined the chat!"), this);

String inputLine;
while (running && (inputLine = in.readLine()) != null) {
if (inputLine.startsWith("/pm ")) {
handlePrivateMessage(inputLine);
}
else {
server.broadcast(new Message(username, "Everyone", inputLine), this);
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
boolean removed = server.removeClient(this);
if (removed) {
server.broadcast(new Message(username, "Server", username + " has left the chat!"), null);
}
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}

public void send(Message message) {
if (out != null && !out.checkError()) {
out.println(message.toString());
out.flush();
}
}

public void sendRawMessage(String message) {
try {
if (out != null && !out.checkError()) {
out.println(message);
out.flush();
} else {
System.err.println("Output stream is closed, cannot send message.");
}
} catch (Exception e) {
e.printStackTrace();
System.err.println("Error sending message: " + e.getMessage());
}
}

private void handlePrivateMessage(String inputLine) {
int firstSpace = inputLine.indexOf(" ");
int secondSpace = inputLine.indexOf(" ", firstSpace + 1);
if (secondSpace != -1) {
String recipient = inputLine.substring(firstSpace + 1, secondSpace);
String message = inputLine.substring(secondSpace + 1);
for (ServerThread client : server.getClients()) {
if (client.getUsername().equals(recipient)) {
client.send(new Message(username, recipient, message));
break;
}
}
}
}

public String getUsername() {
return username;
}
public void forceLogout() {
running = false; // 停止主循环
sendRawMessage("/forceLogout");
try {
socket.close(); // 关闭Socket以触发IOException并停止线程
} catch (IOException e) {
e.printStackTrace();
}
}
}

使用方法

首先启动服务器端Server,再启动客户端界面ClientGUI,关于同时启动多个实例比较简单,大家可以自行搜索。

其他

个人主页:
张明宇的个人主页