利用Java编写的一个简单的群聊程序,模仿QQ群聊,利用本地通讯,实现了简单的群聊功能。
源码请自取:
csdn资源下载传送门(免费)
程序共分为3个包 (先编译运行TerminalServerView.java
,再运行LoginView.java
):
- client包中存放辅助实现客户端功能的类
- server包中存放辅助实现服务终端功能的类
- view包中存放界面相关的类
附各模块代码:
目录:
1. client包
1.1 package-info.java
/**
* 提供帮助实现客户端功能的类
*
* @author JuJunjian
* @version 1.0 2020-11-21
*/
package com.jujunjian.client;
1.2 Client.java
package com.jujunjian.client;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.Socket;
import java.net.SocketException;
import java.net.UnknownHostException;
/**
* 实现了客户端的功能
*
* @author JuJunjian
* @version 1.0 2020-11-21
*/
public class Client {
private static final String LOCALHOST = "127.0.0.1";
private static final String CHARSET = "UTF-8"; // 编码方式
private static final int TERMINAL_PORT = 8888; // 服务端的端口号
private Socket socket;
private InputStream inputStream;
private OutputStream oututStream;
/**
* 实例化一个客户端对象
*/
public Client() {
try {
socket = new Socket(LOCALHOST, TERMINAL_PORT);
inputStream = socket.getInputStream();
oututStream = socket.getOutputStream();
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 向服务终端申请账号的请求
*
* @param id 用户申请的账号,id为0代表申请一个随机的账号
*/
public void applyId(int id) {
byte[] byteId = toByte(id);
try {
oututStream.write(byteId);
oututStream.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* @return 返回申请账号的结果
*/
public int getApplyIdResult() {
int result = -1;
byte[] byteResult = new byte[4];
try {
inputStream.read(byteResult);
result = toInt(byteResult);
} catch (IOException e) {
e.printStackTrace();
}
return result;
}
/**
* 调用此方法以发送消息
*
* @param message 需发送的消息
*/
public void sendMessage(String message) {
try {
byte[] byteMessage = message.getBytes(CHARSET);
int length = byteMessage.length;
byte[] head = toByte(length); // 字节流前的报头,代表了消息字节流的长度,用于辅助接收端判断是否接收完毕
// 得到最终被传输的字节流
byte[] outByte = new byte[length + head.length];
System.arraycopy(head, 0, outByte, 0, head.length);
System.arraycopy(byteMessage, 0, outByte, head.length, byteMessage.length);
oututStream.write(outByte);
oututStream.flush();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 用于接收新消息,每次调用只可接收一条新消息
*
* @return 返回接收到的信息(字符串类型)
*/
public String startReceiveMessage() {
String receiveMessage = "";
byte[] head = new byte[4]; // 消息的长度
try {
inputStream.read(head);
int messageLength = toInt(head);
byte[] byteMessage = new byte[messageLength];
inputStream.read(byteMessage);
receiveMessage = new String(byteMessage, CHARSET);
} catch (SocketException e) {
} catch (IOException e) {
e.printStackTrace();
}
return receiveMessage;
}
/**
* 调用此方法可用结束与服务终端的通讯
*/
public void endCommunication() {
try {
int result = -1; // -1 表示结束整个群聊
byte[] end = toByte(result);
oututStream.write(end);
oututStream.flush();
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
// 将int类型的数据转换为4位byte类型的数据
private byte[] toByte(int intData) {
byte[] byteData = new byte[4];
byteData[0] = (byte) ((intData >> 24) & 0xFF);
byteData[1] = (byte) ((intData >> 16) & 0xFF);
byteData[2] = (byte) ((intData >> 8) & 0xFF);
byteData[3] = (byte) (intData & 0xFF);
return byteData;
}
// 将4位byte类型的数据转换为int类型的数据
private int toInt(byte[] byteData) {
int intData = 0;
for (int i = 0; i < byteData.length; i++) {
int shift = (3 - i) * 8;
intData += ((byteData[i] & 0xFF) << shift);
}
return intData;
}
}
2. server包
2.1 package-info.java
/**
* 提供了一个简单的服务端,用以接收、回馈和转发客户端的消息
*
* @author JuJunjian
* @version 1.0 2020-11-20
*/
package com.jujunjian.server;
2.2 UserSupervision.java
package com.jujunjian.server;
import java.io.IOException;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.Socket;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* 用于存储已注册用户的信息,同时提供向已注册用户发送消息、增加用户、删除用户以及其它一些辅助功能
*
* @author JuJunjian
* @version 1.0 2020-11-20
*/
public class UserSupervision {
private static final String CHARSET = "UTF-8"; // 传输数据的编码方式
private static final int SEND_THREADS_NUMBER = 10;
private static final int MAX_SIZE = 1000; // 最大聊天人数
private static final Integer MIN_ID = 9000;
private static final Integer MAX_ID = 9999;
private Map<Integer, OutputStream> userOutputStreamMap;
private Map<Integer, Socket> userSocketMap;
private ExecutorService sendMessagePool;
private int size; // 用于记录当前用户的数量
/**
* 实例化一个管理群聊用户的对象
*/
public UserSupervision() {
userOutputStreamMap = new HashMap<>();
userSocketMap = new HashMap<>();
sendMessagePool = Executors.newFixedThreadPool(SEND_THREADS_NUMBER);
size = 0;
}
/**
* 检查注册账号是否满足格式要求
*
* @param id 需检测的账号
* @return 账号符合要求返回true;否则返回false
*/
public boolean isRightId(Integer id) {
if (id >= MIN_ID && id <= MAX_ID) {
return true;
}
return false;
}
/**
* 检测群聊人数是否已满
*
* @return 聊人数是否已满返回true;否则返回false
*/
public boolean isFull() {
return (MAX_SIZE == size);
}
/**
* 检测用户申请的账号是否已经被占用
*
* @param id 需检测的账号
* @return 未被占用返回true;否则返回false
*/
public synchronized boolean isFreeId(Integer id) {
if (userOutputStreamMap.containsKey(id)) {
return false;
}
return true;
}
/**
* 向群聊中添加新用户
*
* @param id 新注册用户的账号
* @param socket 用于和此用户通讯的套接字
* @param outputStream 用于向此用户发送消息的输出流
* @return 添加成功返回true;否则返回false
*/
public synchronized boolean addUser(Integer id, Socket socket , OutputStream outputStream) {
if (isFull()) {
return false;
} else {
// 将此用户添加到群聊中
userSocketMap.put(id, socket);
userOutputStreamMap.put(id, outputStream);
size++; // 当前群聊人数加一
return true;
}
}
/**
* 从群聊中移除指定的用户
*
* @param deletedUserId 需被移除的用户的账号
* @return 删除成功返回true;否则返回false
*/
public synchronized boolean removeUser(Integer deletedUserId) {
if (userOutputStreamMap.isEmpty()) {
return false;
}
if (userOutputStreamMap.containsKey(deletedUserId)) {
try {
// 关闭并移除此用户对应的OutputStream和Socket
userOutputStreamMap.get(deletedUserId).close();
userOutputStreamMap.remove(deletedUserId);
userSocketMap.get(deletedUserId).close();
userSocketMap.remove(deletedUserId);
size--; // 当前群聊人数减一
return true;
} catch (IOException e) {
e.printStackTrace();
}
}
return false;
}
/**
* 向群内的其它用户(除了提供消息的用户)发送消息
*
* @param senderId 发送者的账号
* @param message 发送的消息
*/
public synchronized void sendMessage(Integer senderId, String message) {
for (Integer id : userOutputStreamMap.keySet()) {
if(id != senderId) {
sendMessagePool.submit(() -> {
OutputStream out = userOutputStreamMap.get(id);
// 将所需发送的消息转换为字节流,编码方式为UTF-8
byte[] byteMessage = null;
try {
byteMessage = message.getBytes(CHARSET);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
// head是消息字节前面的报头,代表消息的长度,可辅助接收端判断是否接收完毕
int length = byteMessage.length;
byte[] head = toByte(length);
// 得到最终需要传输的字节流outByte
byte[] outByte = new byte[length + head.length];
System.arraycopy(head, 0, outByte, 0, head.length);
System.arraycopy(byteMessage, 0, outByte, head.length, byteMessage.length);
// 发送消息
try {
out.write(outByte);
out.flush();
} catch (IOException e) {
e.printStackTrace();
}
});
}
}
}
/**
* 获得空闲的账号
*
* @return 存在可用账号时返回一个可用账号,否则返回-1
*/
public synchronized Integer getFreeId() {
Integer freeId = -1;
if (isFull()) {
return freeId;
}
// 遍历以寻找可用账号
for (Integer id = MIN_ID; id < MAX_ID; id++) {
if (!userOutputStreamMap.containsKey(id)) {
freeId = id;
break;
}
}
return freeId;
}
/**
* 调用此方法可结束群聊
*/
public synchronized void end() {
userOutputStreamMap.clear();
// 关闭所有socket
for (Integer id : userSocketMap.keySet()) {
try {
userSocketMap.get(id).close();
} catch (IOException e) {
e.printStackTrace();
}
}
userSocketMap.clear();
}
/**
* 将int类型的数据转换为4位byte类型的数据
*
* @param intData 传入的int类型的参数
* @return 返回转换之后的4位byte类型的数据
*/
public static synchronized byte[] toByte(int intData) {
byte[] byteData = new byte[4];
byteData[0] = (byte) ((intData >> 24) & 0xFF);
byteData[1] = (byte) ((intData >> 16) & 0xFF);
byteData[2] = (byte) ((intData >> 8) & 0xFF);
byteData[3] = (byte) (intData & 0xFF);
return byteData;
}
/**
* 将4位byte类型的数据转换为int类型的数据
*
* @param byteData 传入的4位byte类型的参数
* @return 返回转换之后的int类型的数据
*/
public static synchronized int toInt(byte[] byteData) {
int intData = 0;
for (int i = 0; i < byteData.length; i++) {
int shift = (3 - i) * 8;
intData += ((byteData[i] & 0xFF) << shift);
}
return intData;
}
}
2.3 TerminalServer.java
package com.jujunjian.server;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* 实现了一个简单的群聊服务终端
*
* @author JuJunjian
* @version 1.0 2020-11-20
*/
public class TerminalServer {
private static final int TERMINAL_PORT = 8888; // 服务端的端口号
private static final String CHARSET = "UTF-8"; // 传输数据的编码方式
private UserSupervision userManager; // 群聊用户的管理者
private ServerSocket terminalServerSocket;
private ExecutorService receiveMessagePool;
/**
* 实例化一个服务终端对象
*/
public TerminalServer() {
userManager = new UserSupervision();
receiveMessagePool = Executors.newCachedThreadPool();
try {
terminalServerSocket = new ServerSocket(TERMINAL_PORT);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 调用此方法可用使服务终端开始工作
*/
public void startRunning() {
while (true) {
try {
Socket socket = terminalServerSocket.accept(); // 开始接收用户端建里连接的请求
InputStream inputStream = socket.getInputStream();
OutputStream outputStream = socket.getOutputStream();
// 刚建立连接时会接收到4个字节的特殊消息,用于辅助服务端做出后续动作
byte[] initialByte = new byte[4];
inputStream.read(initialByte);
int initialMessage = UserSupervision.toInt(initialByte);
// 群聊结束,服务终端退出
if (-1 == initialMessage) {
userManager.end();
}
Integer uesrId = 0; // 用户申请的账号
boolean isAvailableId = false; // 用于表示账号是否可用
if (userManager.isFull()) {
int result = 0; // 发送给客户端的数据,0 代表群聊已满
byte[] byteResult = UserSupervision.toByte(result);
outputStream.write(byteResult);
outputStream.flush();
} else {
if (0 == initialMessage) { // initialMessage等于0表示用户正在申请一个随机的可用账号
uesrId = userManager.getFreeId();
int result = 3; // 3 代表未知的错误
byte[] byteResult = new byte[4];
if ((-1 != uesrId) && userManager.addUser(uesrId, socket, outputStream)) {
isAvailableId = true;
result = uesrId;
}
byteResult = UserSupervision.toByte(result);
outputStream.write(byteResult);
outputStream.flush();
} else if (!userManager.isRightId(initialMessage)) {
int result = 1; // 1 代表账号格式错误
byte[] byteResult = UserSupervision.toByte(result);
outputStream.write(byteResult);
outputStream.flush();
} else if (!userManager.isFreeId(initialMessage)) {
int result = 2; // 2 代表账号已存在
byte[] byteResult = UserSupervision.toByte(result);
outputStream.write(byteResult);
outputStream.flush();
} else {
uesrId = initialMessage;
int result = 3; // 3 代表未知的错误
byte[] byteResult = new byte[4];
if (userManager.addUser(uesrId, socket, outputStream)) {
isAvailableId = true; // 客户端申请的账号可用
result = uesrId;
}
byteResult = UserSupervision.toByte(result);
outputStream.write(byteResult);
outputStream.flush();
}
}
if (isAvailableId) {
final Integer userFinalId = uesrId;
receiveMessagePool.submit(() -> {
// 建里连接之后就一直接收对方的消息,直至对方退出
while (!socket.isClosed()) {
try {
// 前四位用于记录消息总长度,或断开通讯的通知(-1表示断开连接)
byte[] byteHead = new byte[4];
inputStream.read(byteHead);
int head = UserSupervision.toInt(byteHead);
// 结束与该用户的通讯
if (-1 == head) {
userManager.removeUser(userFinalId);
inputStream.close();
socket.close();
break;
} else {
// 读取对方发送的消息
byte[] messageByte = new byte[head];
inputStream.read(messageByte);
String message = new String(messageByte, CHARSET);
userManager.sendMessage(userFinalId, message); // 将消息发送给其它用户
}
} catch (SocketException e) {
System.out.println("一位用户已退出");
} catch (IOException e) {
e.printStackTrace();
}
}
});
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
3. view包
3.1 package-info.java
/**
* 提供客户端的界面,主要包括:聊天界面、登陆界面、警告弹窗、服务终端界面(用于结束服务终端程序)
*
* @author JuJunjian
* @version 1.0 2020-11-21
*/
package com.jujunjian.view;
3.2 TerminalServerView.java
package com.jujunjian.view;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import javax.swing.JButton;
import javax.swing.JFrame;
import com.jujunjian.server.TerminalServer;
/**
* 服务终端界面,只包含一个结束按钮,用于结束终端服务器
*
* @author JuJunjian
* @version 1.0 2020-11-21
*/
@SuppressWarnings("serial")
public class TerminalServerView extends JFrame {
private static final String TITLE = "服务终端";
private static final String END_BUTTON_TEXT = "结束服务终端";
private static final int FRAME_WIDTH = 300;
private static final int FRAME_HIGH = 150;
private static final int BUTTON_WIDTH = FRAME_WIDTH - 100;
private static final int BUTTON_HIGH = FRAME_HIGH - 100;
private JButton endButton;
private TerminalServer terminalServer;
private Thread terminalThread;
/**
* 实例化了一个服务终端界面
*/
public TerminalServerView() {
// 开启服务终端
terminalServer = new TerminalServer();
terminalThread = new Thread(() -> {
terminalServer.startRunning();
});
terminalThread.start();
// 初始化界面
setTitle(TITLE);
setSize(FRAME_WIDTH, FRAME_HIGH);
setResizable(false);
setLayout(new FlowLayout(1, 30, 30));
// 为右上角的叉号添加监听器和动作
setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
terminalServer = null;
System.exit(0);
}
});
// 初始化结束按钮
endButton = new JButton(END_BUTTON_TEXT);
endButton.setPreferredSize(new Dimension(BUTTON_WIDTH, BUTTON_HIGH));
endButton.setFont(new java.awt.Font("宋体", 1, 20));
endButton.addActionListener(e -> {
terminalServer = null;
System.exit(0);
});
add(endButton);
setVisible(true);
}
public static void main(String[] args) {
// 启动终端界面
new TerminalServerView();
}
}
3.3 LoginView.java
package com.jujunjian.view;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextArea;
import com.jujunjian.client.Client;
/**
* 用户登陆界面,大致包括:提示面板、账号输入面板、必须的按钮
*
* @author JuJunjian
* @version 1.0 2020-11-21
*/
@SuppressWarnings("serial")
public class LoginView extends JFrame {
private static final Integer MIN_ID = 9000;
private static final Integer MAX_ID = 9999;
private static final String ERROR_TEXT = "账号有误";
private static final String ID_EXIST_TEXT = "账号已存在";
private static final String USER_FULL_TEXT = "群聊人数已满";
private static final String UNKNOWN_ERROR_TEXT = "未知的错误";
private static final String TITLE = "登陆界面";
private static final String TIP_TEXT = "请输入账号(9000-9999):";
private static final String OK_BUTTON_TEXT = "确定";
private static final String GET_FREE_ID_TEXT = "获取随机账号";
private static final int FRAME_WIDTH = 400;
private static final int FRAME_HIGH = 240;
private static final int TEXT_AREA_WIDTH = 350;
private static final int TEXT_AREA_HIGH = 35;
private static final int BUTTON_WIDTH = 170;
private static final int BUTTON_HIGH = 50;
private JTextArea inputIdTextArea;
private JLabel errorTip;
private JButton okButton;
private JButton getFreeIdButton;
/**
* 实例化一个用户登陆界面
*/
public LoginView() {
setTitle(TITLE);
setSize(FRAME_WIDTH, FRAME_HIGH);
setResizable(false);
setLayout(new FlowLayout(1, 10, 10));
setLocation(0, 150);
// 为右上角的叉号添加监听器和动作
setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
new WarningView();
}
});
// 显示提示信息
JLabel showTip = new JLabel(TIP_TEXT);
showTip.setFont(new java.awt.Font("宋体", 1, 24));
// 输入账号的面板
inputIdTextArea = new JTextArea();
inputIdTextArea.setPreferredSize(new Dimension(TEXT_AREA_WIDTH, TEXT_AREA_HIGH));
inputIdTextArea.setFont(new java.awt.Font("宋体", 1, 24));
// 输入账号有误时,给出的错误提示
errorTip = new JLabel("");
errorTip.setFont(new java.awt.Font("宋体", 1, 18));
JPanel errorTipPanel = new JPanel(new FlowLayout());
errorTipPanel.setPreferredSize(new Dimension(TEXT_AREA_WIDTH, TEXT_AREA_HIGH - 10));
errorTipPanel.add(errorTip);
// 初始化确认按钮
okButton = new JButton(OK_BUTTON_TEXT);
okButton.setPreferredSize(new Dimension(BUTTON_WIDTH, BUTTON_HIGH));
okButton.setFont(new java.awt.Font("宋体", 1, 20));
okButton.addActionListener(e -> {
final int uesrId = getInputId(); // 得到用户输入的账号
if (-1 == uesrId) { // 账号中存在非数字
errorTip.setText(ERROR_TEXT);
} else {
Client client = new Client(); // 每一个群聊用户都对应了一个客户端对象
client.applyId(uesrId); // 向终端申请申请账号
int result = client.getApplyIdResult(); // 得到申请账号的结果
// 根据申请账号的结果进行处理
switch (result) {
case 0: // 群聊人数已满
errorTip.setText(USER_FULL_TEXT);
break;
case 1: // 账号格式错误
errorTip.setText(ERROR_TEXT);
break;
case 2: // 账号已存在
errorTip.setText(ID_EXIST_TEXT);
break;
case 3: // 未知的错误
errorTip.setText(UNKNOWN_ERROR_TEXT);
break;
default:
if (uesrId == result) { // 申请成功
inputIdTextArea.setText("");
new ChatView(client, uesrId);
} else { // 未知的错误
errorTip.setText(UNKNOWN_ERROR_TEXT);
}
break;
}
}
});
// 初始化随机获得账号按钮
getFreeIdButton = new JButton(GET_FREE_ID_TEXT);
getFreeIdButton.setPreferredSize(new Dimension(BUTTON_WIDTH, BUTTON_HIGH));
getFreeIdButton.setFont(new java.awt.Font("宋体", 1, 20));
getFreeIdButton.addActionListener(e -> {
Client client = new Client();
client.applyId(0); // 申请随机的账号
int result = client.getApplyIdResult();
if (0 == result) { // 0 代表群聊人数已满
errorTip.setText(USER_FULL_TEXT);
} else if ((result >= MIN_ID) && (result <= MAX_ID)) { // 申请成功
inputIdTextArea.setText("");
new ChatView(client, result);
} else {
errorTip.setText("未知的错误");
}
});
add(showTip);
add(inputIdTextArea);
add(errorTipPanel);
add(okButton);
add(getFreeIdButton);
setVisible(true);
}
// 得到用户输入的Id,读取失败时返回-1
private Integer getInputId() {
Integer inputId = -1;
String stringId = inputIdTextArea.getText();
try {
inputId = Integer.valueOf(stringId);
} catch (Exception e) {
}
return inputId;
}
public static void main(String[] args) {
new LoginView();
}
}
3.4 ChatView.java
package com.jujunjian.view;
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Font;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import com.jujunjian.client.Client;
/**
* 用户群聊天界面,大致包括:消息显示面板、输入面板、发送按钮
*
* @author JuJunjian
* @version 1.0 2020-11-21
*/
@SuppressWarnings("serial")
public class ChatView extends JFrame {
private static final String TITLE = "用户:";
private static final String SEND_BUTTON_TEXT = "发送";
private static final int FRAME_WIDTH = 1100;
private static final int FRAME_HIGH = 900;
private static final int PANEL_WIDTH = FRAME_WIDTH - 50;
private static final int CHAT_PANEL_HIGH = 500;
private static final int INPUT_PANEL_HIGH = 250;
private static final int SEND_BUTTON_WIDTH = 200;
private static final int SEND_BUTTON_HIGH = 60;
private JPanel chatPanel;
private JPanel inputPanel;
private JTextArea chatArea;
private JTextArea inputArea;
private JButton sendButton;
private Integer id; // 用户账号
private Client client; // 用户对应的客户端对象
private Thread receiveThread;
private Thread sendThread;
private boolean isRunning; // 用户的状态(false表示用户已退出群聊)
/**
* 实例化一个群聊界面
*
* @param client 注册界面申请成功之后得到的客户端对象
* @param id 用户的账号
*/
public ChatView(Client client, int id) {
// 初始客户端
this.client = client;
this.id = id;
isRunning = true;
// 界面初始化
setTitle(TITLE + this.id);
setSize(FRAME_WIDTH, FRAME_HIGH);
setResizable(false);
setLocationRelativeTo(null); // 窗口居中
setLayout(new FlowLayout(2, 10, 10));
// 为右上角的叉号添加监听器和动作
setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) { // 用户退出
isRunning = false;
client.endCommunication(); // 结束通讯
dispose();
}
});
// 消息显示面板
chatArea = new JTextArea();
initialJTextArea(chatArea);
chatPanel = new JPanel();
initialJPanel(chatPanel, PANEL_WIDTH, CHAT_PANEL_HIGH);
chatPanel.add(new JScrollPane(chatArea));
// 消息发送面板
inputArea = new JTextArea();
initialJTextArea(inputArea);
inputPanel = new JPanel();
initialJPanel(inputPanel, PANEL_WIDTH, INPUT_PANEL_HIGH);
inputPanel.add(new JScrollPane(inputArea), BorderLayout.CENTER);
// 发送按钮
sendButton = new JButton(SEND_BUTTON_TEXT);
sendButton.setPreferredSize(new Dimension(SEND_BUTTON_WIDTH, SEND_BUTTON_HIGH));
sendButton.setFont(new Font("宋体", 1, 24));
sendButton.addActionListener(e -> {
sendMessage("用户" + id + ":\n" + inputArea.getText() + "\n\n");
chatArea.append("我:\n" + inputArea.getText() + "\n\n");
inputArea.setText("");
});
startReceive(); // 开始接收消息
add(chatPanel);
add(inputPanel);
add(sendButton);
setVisible(true);
}
// 调用此方法发送一次消息
private void sendMessage(String message) {
sendThread = new Thread(() -> {
client.sendMessage(message);
});
sendThread.start();
}
// 调用此方法开始接收消息
private void startReceive() {
receiveThread = new Thread(() -> {
while (isRunning) {
String message = client.startReceiveMessage();
chatArea.append(message);
}
});
receiveThread.start();
}
// 设置JTextArea的字体
private void initialJTextArea(JTextArea textArea) {
textArea.setFont(new Font("宋体", 0, 24));
}
// 设置JPanel的大小和排版
private void initialJPanel(JPanel panel, int width, int high) {
panel.setPreferredSize(new Dimension(width, high));
panel.setLayout(new BorderLayout());
}
}
3.5 WarningView.java
package com.jujunjian.view;
import java.awt.Dimension;
import java.awt.FlowLayout;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import com.jujunjian.client.Client;
/**
* 警告弹窗,关闭登陆界面时弹出,提示用户关闭此界面将结束整个群聊程序
*
* @author JuJunjian
* @version 1.0 2020-11-21
*/
@SuppressWarnings("serial")
public class WarningView extends JFrame {
private static final String WARNING_TEXT = "关闭此窗口将结束整个群聊程序,是否关闭?";
private static final String OK_BUTTON_TEXT = "确定";
private static final String CANCEL_BUTTON_TEXT = "取消";
private static final int FRAME_WIDTH = 600;
private static final int FRAME_HIGH = 200;
private static final int TEXT_LABEL_WIDTH = 600;
private static final int TEXT_LABEL_HIGH = 50;
private static final int BUTTON_WIDTH = 170;
private static final int BUTTON_HIGH = 50;
private static final int END_ID = -1; // 结束群聊
private JLabel warningLabel;
private JButton okButton;
private JButton cancelButton;
private Thread endThread;
/**
* 实例化一个警告窗口,尝试结束群聊(关闭登陆界面)时会弹出
*/
public WarningView() {
setSize(FRAME_WIDTH, FRAME_HIGH);
setResizable(false);
setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); // 只关闭当前窗口
setLocationRelativeTo(null);
setLayout(new FlowLayout(1, 20, 20));
// 显示提示信息
warningLabel = new JLabel(WARNING_TEXT);
warningLabel.setFont(new java.awt.Font("宋体", 1, 24));
JPanel warningPanel = new JPanel(new FlowLayout());
warningPanel.setPreferredSize(new Dimension(TEXT_LABEL_WIDTH, TEXT_LABEL_HIGH));
warningPanel.add(warningLabel);
// 初始化确认按钮
okButton = new JButton(OK_BUTTON_TEXT);
okButton.setPreferredSize(new Dimension(BUTTON_WIDTH, BUTTON_HIGH));
okButton.setFont(new java.awt.Font("宋体", 1, 20));
okButton.addActionListener(e -> {
endThread = new Thread(() -> {
Client client = new Client();
client.applyId(END_ID); // 告诉服务终端群聊已结束
});
endThread.start();
System.exit(0);
});
// 初始化取消按钮
cancelButton = new JButton(CANCEL_BUTTON_TEXT);
cancelButton.setPreferredSize(new Dimension(BUTTON_WIDTH, BUTTON_HIGH));
cancelButton.setFont(new java.awt.Font("宋体", 1, 20));
cancelButton.addActionListener(e -> {
dispose();
});
add(warningPanel);
add(okButton);
add(cancelButton);
setVisible(true);
}
}