Java实现简单的本地QQ聊天系统

利用Java编写的一个简单的群聊程序,模仿QQ群聊,利用本地通讯,实现了简单的群聊功能。

源码请自取:
csdn资源下载传送门(免费)

程序共分为3个包 (先编译运行TerminalServerView.java,再运行LoginView.java):

  1. client包中存放辅助实现客户端功能的类
  2. server包中存放辅助实现服务终端功能的类
  3. 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);
	}

}

  • 1
    点赞
  • 46
    收藏
    觉得还不错? 一键收藏
  • 10
    评论
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值