目录

  • 前言
  • 功能设计
  • GUI画面展示
    • 服务器端
    • 客户端
    • 私聊窗口
  • 主要代码
    • 服务器端
    • 客户端
    • 其它代码
  • 打包成jar
  • 打包成exe文件
  • 如何让其它电脑访问聊天室?
  • 最后

前言

最近刚好是期末,碰上Java实训,借此将之前写的在线聊天室搬出来,加上GUI(Swing),当成实训作品,记录一下这次实训的结果。

本篇文章主要叙述的是
① 在线聊天室的代码结构;
② 将java文件打包成jar,再打包成exe文件;
③ 利用内网穿透技术实现与他人在线聊天。

附:在线聊天室实用socket通信,利用的网络协议是TCP,架构为C/S模式(Client-Server=>客户机-服务器)

功能设计

  1. 总体设计
  2. 详细设计
    (1)聊天室服务器端

1)设置聊天室服务器的端口号,管理员昵称,启动服务器或者关闭服务器。
2)系统消息日志记录,管理员可发布系统消息给各在线用户。
3)管理员在线与聊天室在线用户进行群聊。
4)管理员可对在线用户列表中指定用户进行私聊请求,对方同意即可开始私聊。
5)管理员可对在线用户列表中指定用户进行踢出聊天室操作,并通知其他人。

(2)聊天室客户端

1)用户设置聊天室IP,端口号,用户昵称,连接服务器进入聊天室或退出聊天室。
2)系统消息通知,接受服务器端发布的消息,以及用户一些操作。
3)用户可与其他在线用户进行群聊。
4)用户可与指定用户列表中其他在线用户进行私聊请求,同意即可开始私聊。
5)用户可以屏蔽指定用户列表中的用户的群聊发言,屏蔽后即接受不到对方发言, 同时也可以选择取消屏蔽。

GUI画面展示

服务器端

  • 启动界面
  • 聊天界面

客户端

  • 聊天界面

私聊窗口

  • 被私聊者
  • 聊天窗口

主要代码

客户端与服务器交互通过特定的指令与信息,客户端发送特定格式的指令和信息,服务器端接受到指令和信息,根据指令处理不同的业务请求,再将结果信息和响应指令发送到客户端,客户端根据不同指令将信息呈现到用户端GUI,或者改变客户端。

服务器端和用户端的主类都用到了内部类,因为毕竟容易获取主类的变量值,具体的类和方法介绍我就不仔细讲了,代码里面都有注释了,不懂看看注释,肯定不是因为我懒。

服务器端

  • 服务器端主线程用来运行管理员操作的GUI界面
  • 子线程运行ServerSocket服务

(1)创建ServerSocket对象,绑定监听端口。
(2)通过accept()方法监听客户端请求
(3)连接建立后,通过输入流读取客户端的数据
(4)通过输出流,向客户端回应信息

  • 每有一个新的用户连接生成,会创建对应的子线程来处理对应用户端的需求,用户断开连接时,该线程也随之停止。
package top.hcode.chatRoom;import javax.swing.*;
import javax.swing.text.*;
import java.awt.*;
import java.awt.event.*;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.regex.Matcher;
import java.util.regex.Pattern;import static top.hcode.chatRoom.CloseUtils.close;/*** @Author: Himit_ZH* @Date: 2020/6/4 17:17* @Description: 聊天室服务器,管理员GUI界面*/
public class chatServer {private CopyOnWriteArrayList<Channel> allUserChannel;/*以下为窗口参数*/private JFrame frame;//头部参数private JTextField port_textfield;private JTextField name_textfield;private JButton head_connect;private JButton head_exit;private int port;//底部参数private JTextField text_field;private JTextField sysText_field;private JButton foot_send;private JButton foot_sysSend;private JButton foot_userClear;//右边参数private JLabel users_label;private JButton privateChat_button;private JButton kick_button;private JList<String> userlist;private DefaultListModel<String> users_model;//左边参数private JScrollPane sysTextScrollPane;private JTextPane sysMsgArea;private JScrollBar sysVertical;//中间参数private JScrollPane userTextScrollPane;private JTextPane userMsgArea;private JScrollBar userVertical;//时间格式化工具类static private SimpleDateFormat df = new SimpleDateFormat("HH:mm:ss");//设置时间//用户自增IDprivate int userId = 1000;//服务器管理员名字private String adminName;//服务器线程private ServerSocket serverSocket;//服务器线程private Server server;//管理员的私聊窗口队列和线程队列private HashMap<String, privateChatFrame> adminPrivateQueue;private HashMap<String, Channel> adminPrivateThread;public static void main(String[] args) {new chatServer().init();}/*** @MethodName init   GUI初始化,初始化各种监听事件* @Params  * @param null* @Return null* @Since 2020/6/6*/public void init() {allUserChannel = new CopyOnWriteArrayList<>();adminPrivateQueue = new HashMap<>();adminPrivateThread = new HashMap<>();setUIStyle();frame = new JFrame("Hcode聊天室服务器");JPanel panel = new JPanel();        /*主要的panel,上层放置连接区,下层放置消息区,中间是消息面板,左边是room列表,右边是当前room的用户列表*/JPanel headpanel = new JPanel();    /*上层panel,用于放置连接区域相关的组件*/JPanel footpanel = new JPanel();    /*下层panel,用于放置发送信息区域的组件*/JPanel centerpanel = new JPanel();    /*中间panel,用于放置聊天信息*/JPanel leftpanel = new JPanel();    /*左边panel,用于放置房间列表和加入按钮*/JPanel rightpanel = new JPanel();   /*右边panel,用于放置房间内人的列表*//*最上层的布局,分中间,东南西北五个部分*/BorderLayout layout = new BorderLayout();/*格子布局,主要用来设置西、东、南三个部分的布局*/GridBagLayout gridBagLayout = new GridBagLayout();/*主要设置北部的布局*/FlowLayout flowLayout = new FlowLayout();/*设置初始窗口的一些性质*/frame.setSize(900, 600);frame.setLocationRelativeTo(null);frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);frame.setContentPane(panel);frame.setLayout(layout);/*设置各个部分的panel的布局和大小*/headpanel.setLayout(flowLayout);footpanel.setLayout(gridBagLayout);leftpanel.setLayout(gridBagLayout);centerpanel.setLayout(gridBagLayout);rightpanel.setLayout(gridBagLayout);//设置面板大小leftpanel.setPreferredSize(new Dimension(350, 0));rightpanel.setPreferredSize(new Dimension(155, 0));footpanel.setPreferredSize(new Dimension(0, 40));//头部布局port_textfield = new JTextField("8888");name_textfield = new JTextField("匿名");port_textfield.setPreferredSize(new Dimension(70, 25));name_textfield.setPreferredSize(new Dimension(150, 25));JLabel port_label = new JLabel("端口号:");JLabel name_label = new JLabel("管理员:");head_connect = new JButton("启动");head_exit = new JButton("关闭");headpanel.add(port_label);headpanel.add(port_textfield);headpanel.add(name_label);headpanel.add(name_textfield);headpanel.add(head_connect);headpanel.add(head_exit);//底部布局foot_send = new JButton("发送聊天信息");foot_sysSend = new JButton("发送系统消息");foot_sysSend.setPreferredSize(new Dimension(110, 0));foot_userClear = new JButton("清空聊天消息");foot_userClear.setPreferredSize(new Dimension(148, 0));sysText_field = new JTextField();sysText_field.setPreferredSize(new Dimension(230, 0));text_field = new JTextField();footpanel.add(sysText_field, new GridBagConstraints(0, 0, 1, 1, 1, 1,GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(1, 1, 1, 1), 0, 0));footpanel.add(foot_sysSend, new GridBagConstraints(1, 0, 1, 1, 1.0, 1.0,GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(1, 1, 1, 3), 0, 0));footpanel.add(text_field, new GridBagConstraints(2, 0, 1, 1, 100, 100,GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(1, 1, 1, 1), 0, 0));footpanel.add(foot_send, new GridBagConstraints(3, 0, 1, 1, 1.0, 1.0,GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(1, 1, 1, 1), 0, 0));footpanel.add(foot_userClear, new GridBagConstraints(4, 0, 1, 1, 1.0, 1.0,GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(1, 1, 1, 1), 0, 0));//左边布局JLabel sysMsg_label = new JLabel("系统日志:");sysMsgArea = new JTextPane();sysMsgArea.setEditable(false);sysTextScrollPane = new JScrollPane();sysTextScrollPane.setViewportView(sysMsgArea);sysVertical = new JScrollBar(JScrollBar.VERTICAL);sysVertical.setAutoscrolls(true);sysTextScrollPane.setVerticalScrollBar(sysVertical);leftpanel.add(sysMsg_label, new GridBagConstraints(0, 0, 1, 1, 1, 1,GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0));leftpanel.add(sysTextScrollPane, new GridBagConstraints(0, 1, 1, 1, 100, 100,GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0));//右边布局users_label = new JLabel("当前连接用户:0");privateChat_button = new JButton("私聊");kick_button = new JButton("踢出");users_model = new DefaultListModel<>();userlist = new JList<String>(users_model);JScrollPane userListPane = new JScrollPane(userlist);rightpanel.add(users_label, new GridBagConstraints(0, 0, 1, 1, 1, 1,GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0));rightpanel.add(privateChat_button, new GridBagConstraints(0, 1, 1, 1, 1, 1,GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0));rightpanel.add(kick_button, new GridBagConstraints(0, 2, 1, 1, 1, 1,GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0));rightpanel.add(userListPane, new GridBagConstraints(0, 3, 1, 1, 100, 100,GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0));//中间布局JLabel userMsg_label = new JLabel("世界聊天:");userMsgArea = new JTextPane();userMsgArea.setEditable(false);userTextScrollPane = new JScrollPane();userTextScrollPane.setViewportView(userMsgArea);userVertical = new JScrollBar(JScrollBar.VERTICAL);userVertical.setAutoscrolls(true);userTextScrollPane.setVerticalScrollBar(userVertical);centerpanel.add(userMsg_label, new GridBagConstraints(0, 0, 1, 1, 1, 1,GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0));centerpanel.add(userTextScrollPane, new GridBagConstraints(0, 1, 1, 1, 100, 100,GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0));//设置顶层布局panel.add(headpanel, "North");panel.add(footpanel, "South");panel.add(leftpanel, "West");panel.add(rightpanel, "East");panel.add(centerpanel, "Center");//将按钮事件全部注册到监听器allActionListener allActionListener = new allActionListener();//开启服务head_connect.addActionListener(allActionListener);// 管理员发布消息foot_send.addActionListener(allActionListener);//关闭服务器head_exit.addActionListener(allActionListener);//清空消息日志foot_sysSend.addActionListener(allActionListener);//清空世界聊天消息foot_userClear.addActionListener(allActionListener);//私聊privateChat_button.addActionListener(allActionListener);//踢人kick_button.addActionListener(allActionListener);//服务器窗口关闭事件frame.addWindowListener(new WindowAdapter() {@Overridepublic void windowClosing(WindowEvent e) {int option = JOptionPane.showConfirmDialog(frame, "确定关闭服务器界面?", "提示",JOptionPane.YES_NO_OPTION);if (option == JOptionPane.YES_OPTION) {if (e.getWindow() == frame) {if (server != null) { //如果已开启服务,就告诉各个已连接客户端:服务器已关闭sendSysMsg("由于服务器关闭,已断开连接", 3);}frame.dispose();System.exit(0);} else {return;}} else {return;}}});//聊天信息输入框的监听回车按钮事件text_field.addKeyListener(new KeyAdapter() {@Overridepublic void keyTyped(KeyEvent e) {if (e.getKeyChar() == KeyEvent.VK_ENTER) {if (server == null) {JOptionPane.showMessageDialog(frame, "请先开启聊天室的服务器!", "提示", JOptionPane.WARNING_MESSAGE);return;}String text = text_field.getText();if (text != null && !text.equals("")) {sendAdminMsg(text);text_field.setText("");insertMessage(userTextScrollPane, userMsgArea, null, "[管理员]" + adminName +" "+df.format(new Date()) , " "+text, userVertical, false);}}}});// 系统信息输入框的回车监控事件sysText_field.addKeyListener(new KeyAdapter() {@Overridepublic void keyTyped(KeyEvent e) {if (e.getKeyChar() == KeyEvent.VK_ENTER) {if (server == null) {JOptionPane.showMessageDialog(frame, "请先开启聊天室的服务器!", "提示", JOptionPane.WARNING_MESSAGE);return;}String sysText = sysText_field.getText(); //获取输入框中的内容if (sysText != null && !sysText.equals("")) {sendSysMsg(sysText, 2);sysText_field.setText("");insertMessage(sysTextScrollPane, sysMsgArea, null, "[系统日志] " + df.format(new Date()), "[管理员]" + adminName + ":" + sysText, sysVertical, true);}}}});//窗口显示frame.setVisible(true);String name = JOptionPane.showInputDialog("请输入本聊天室管理员昵称:");if (name != null &&!name.equals("")) {name_textfield.setText(name);}}//    //线程锁,防止多线程争夺同个id
//    public synchronized int getUserId() {//        userId++;
//        return userId;
//    }/*** @MethodName ipCheckPort* @Params  * @param null* @Description 验证端口格式是否准确* @Return* @Since 2020/6/8*/public static boolean ipCheckPort(String text){return text.matches("([0-9]|[1-9]\\d{1,3}|[1-5]\\d{4}|6[0-5]{2}[0-3][0-5])");}/*** 按钮监听内部类* Function: 全局监听事件,监听所有按钮*/private class allActionListener implements ActionListener {@Overridepublic void actionPerformed(ActionEvent e) {String cmd = e.getActionCommand();switch (cmd) {case "启动":String strport = port_textfield.getText();if (!ipCheckPort(strport)){JOptionPane.showMessageDialog(frame, "请使用0~65535的整数作为端口号!", "失败", JOptionPane.ERROR_MESSAGE);break;}port = Integer.parseInt(strport);try {server = new Server(new ServerSocket(port));insertMessage(sysTextScrollPane, sysMsgArea, null, "[系统日志] " + df.format(new Date()), "服务器开启成功!", sysVertical, true);head_connect.setText("已启动");head_exit.setText("关闭");(new Thread(server)).start();adminName = name_textfield.getText();name_textfield.setEditable(false);port_textfield.setEditable(false);} catch (IOException ee) {//端口被占用insertMessage(sysTextScrollPane, sysMsgArea, null, "[系统日志] " + df.format(new Date()), "错误:端口号被占用!", sysVertical, true);JOptionPane.showMessageDialog(frame, "开启服务器失败!端口被占用,请更换端口号!", "失败", JOptionPane.ERROR_MESSAGE);}break;case "关闭":if (server == null) {JOptionPane.showMessageDialog(frame, "不能关闭,未开启过服务器!", "错误", JOptionPane.ERROR_MESSAGE);break;}sendSysMsg("由于服务器关闭,已断开连接", 3);try {serverSocket.close();} catch (Exception e1) {insertMessage(sysTextScrollPane, sysMsgArea, null, "[系统日志] " + df.format(new Date()), "错误:服务器关闭失败!", sysVertical, true);}head_connect.setText("启动");head_exit.setText("已关闭");port_textfield.setEditable(true);name_textfield.setEditable(true);for (Channel channel : allUserChannel) {channel.release();}server = null;users_model.removeAllElements();insertMessage(sysTextScrollPane, sysMsgArea, null, "[系统日志] " + df.format(new Date()), "服务器已关闭!", sysVertical, true);JOptionPane.showMessageDialog(frame, "服务器已关闭!");break;case "发送系统消息":if (server == null) {JOptionPane.showMessageDialog(frame, "请先开启聊天室服务器!", "提示", JOptionPane.WARNING_MESSAGE);break;}String sysText = sysText_field.getText(); //获取输入框中的内容sendSysMsg(sysText, 2);sysText_field.setText("");insertMessage(sysTextScrollPane, sysMsgArea, null, "[系统日志] " + df.format(new Date()), "[管理员]" + adminName + ":" + sysText, sysVertical, true);break;case "发送聊天信息":if (server == null) {JOptionPane.showMessageDialog(frame, "请先开启聊天室服务器!", "提示", JOptionPane.WARNING_MESSAGE);break;}String text = text_field.getText(); //获取输入框中的内容sendAdminMsg(text);text_field.setText("");insertMessage(userTextScrollPane, userMsgArea, null, "[管理员]" + adminName +" "+df.format(new Date()) , " "+text, userVertical, false);break;case "踢出":if (server == null) {JOptionPane.showMessageDialog(frame, "请先开启聊天室服务器!", "提示", JOptionPane.WARNING_MESSAGE);break;}String selected = null;try {selected = userlist.getSelectedValue();kickUser(selected);} catch (NullPointerException e1) {JOptionPane.showMessageDialog(frame, "请点击选择需要踢出的用户", "错误", JOptionPane.ERROR_MESSAGE);}break;case "清空系统日志":sysMsgArea.setText("");break;case "清空聊天消息":userMsgArea.setText("");break;case "私聊":if (server == null) {JOptionPane.showMessageDialog(frame, "请先开启聊天室服务器!", "提示", JOptionPane.WARNING_MESSAGE);break;}String privateSelected = userlist.getSelectedValue();privateChat(privateSelected);break;default:break;}}}/*** @MethodName insertMessage* @Params  * @param null* @Description 往系统消息文本域或者聊天事件文本域插入固定格式的内容* @Return* @Since 2020/6/6*/private void insertMessage(JScrollPane scrollPane, JTextPane textPane, String icon_code,String title, String content, JScrollBar vertical, boolean isSys) {StyledDocument document = textPane.getStyledDocument();     /*获取textpane中的文本*//*设置标题的属性*/Color content_color = null;if (isSys) {content_color = Color.RED;} else {content_color = Color.GRAY;}SimpleAttributeSet title_attr = new SimpleAttributeSet();StyleConstants.setBold(title_attr, true);StyleConstants.setForeground(title_attr, Color.BLUE);/*设置正文的属性*/SimpleAttributeSet content_attr = new SimpleAttributeSet();StyleConstants.setBold(content_attr, false);StyleConstants.setForeground(content_attr, content_color);Style style = null;if (icon_code != null) {Icon icon = new ImageIcon("icon/" + icon_code + ".png");style = document.addStyle("icon", null);StyleConstants.setIcon(style, icon);}try {document.insertString(document.getLength(), title + "\n", title_attr);if (style != null)document.insertString(document.getLength(), "\n", style);elsedocument.insertString(document.getLength(), content + "\n", content_attr);} catch (BadLocationException ex) {System.out.println("Bad location exception");}/*设置滑动条到最后*/textPane.setCaretPosition(textPane.getDocument().getLength());}/*** @MethodName sendSysMsg* @Params  * @param null* @Description 发布系统消息* @Return* @Since 2020/6/6*/private void sendSysMsg(String content, int code) {if (code == 2) {String msg = "<time>" + df.format(new Date()) + "</time><sysMsg>" + content + "</sysMsg>";for (Channel channel : allUserChannel) {channel.send(formatCodeWithMsg(msg, code));}} else if (code == 3) {for (Channel channel : allUserChannel) {channel.send(formatCodeWithMsg(content, code));}}}/*** @MethodName sendAdminMsg* @Params  * @param null* @Description 发送管理员聊天* @Return* @Since 2020/6/6*/private void sendAdminMsg(String content) {String msg = "<userId>0</userId><userMsg>" + content + "</userMsg><time>" + df.format(new Date()) + "</time>";for (Channel channel : allUserChannel) {channel.send(formatCodeWithMsg(msg, 1));}}/*** @MethodName kickUser* @Params  * @param null* @Description 踢出操作,对选中用户进行踢出操作,顺便向其它用户说明* @Return* @Since 2020/6/6*/private void kickUser(String selected) {String kickedUserName = null;String kickedUserId  = null;//管理员还在与之私聊不可踢!避免冲突!for (Channel channel1 : allUserChannel) {String tmp = "[用户" + channel1.user.getId() + "]" + channel1.user.getUsername();if (adminPrivateThread.containsKey(channel1.user.getId().toString())) {JOptionPane.showMessageDialog(frame, "管理员与该用户的私聊未结束,无法踢出!", "错误", JOptionPane.ERROR_MESSAGE);return;}if(tmp.equals(selected)){kickedUserName = tmp;kickedUserId = channel1.user.getId().toString();}}String kickedUserMsg = "对不起,您已被本聊天室管理员踢出!";String otherUserMsg = "<time>" + df.format(new Date()) + "</time><sysMsg>" +"通知:"+kickedUserName+" 被踢出了聊天室!" + "</sysMsg>";for (Channel channel2 : allUserChannel) {String tmp = "[用户" + channel2.user.getId() + "]" + channel2.user.getUsername();if (tmp.equals(selected)) {//告诉对方你被踢了channel2.send(formatCodeWithMsg(kickedUserMsg, 3));//服务器端系统记录insertMessage(sysTextScrollPane, sysMsgArea, null, "[系统日志] " + df.format(new Date()) , tmp+" 被踢出了聊天室", sysVertical, true);//服务器端界面用户列表移除对应用户users_model.removeElement(selected);channel2.release();users_label.setText("当前连接用户:" + allUserChannel.size());break;}else{//通知每个用户 此人被踢出聊天室channel2.send(formatCodeWithMsg(otherUserMsg,2));channel2.send(formatCodeWithMsg(kickedUserId,5));}}}/*** @MethodName privateChat* @Params  * @param null* @Description 管理员私聊操作* @Return* @Since 2020/6/6*/private void privateChat(String selected) {for (Channel channel : allUserChannel) {String tmp = "[用户" + channel.user.getId() + "]" + channel.user.getUsername();if (tmp.equals(selected)) {if(adminPrivateQueue.containsKey(channel.user.getId().toString())){ //不能重复私聊JOptionPane.showMessageDialog(frame, "与该用户私聊窗口已存在,请不要重复私聊!", "错误", JOptionPane.ERROR_MESSAGE);return;}String Msg = "<from>[管理员]" + adminName+ "</from><id>0</id>";channel.send(formatCodeWithMsg(Msg, 9)); //将自己的个人信息发给想要私聊的用户线程break;}}}/*** @MethodName formatCodeWithMsg* @Params  * @param null* @Description 消息与命令的格式化* @Return* @Since 2020/6/6*/private String formatCodeWithMsg(String msg, int code) {return "<cmd>" + code + "</cmd><msg>" + msg + "</msg>\n";}/*** @ClassName Server* @Params  * @param null* @Description 服务器开启TCP接受客户端信息的线程,内部类实现* @Return* @Since 2020/6/6*/private class Server implements Runnable {private Server(ServerSocket socket) {serverSocket = socket;}@Overridepublic void run() {while (true) {Socket client = null;try {client = serverSocket.accept();userId++;User user = new User("user" + userId, userId, client);Channel channel = new Channel(user);allUserChannel.add(channel);users_label.setText("当前连接用户:" + allUserChannel.size());(new Thread(channel)).start();} catch (IOException e) {//关闭不处理break;}}}}/*** @ClassName Channel* @Params  * @param null* @Description 内部类 启动一个线程应对一个客户端的服务* @Return* @Since 2020/6/6*/protected class Channel implements Runnable {private DataInputStream dis;private DataOutputStream dos;private boolean isRunning;private User user;private CopyOnWriteArrayList<Channel> shieldList;private HashMap<String, Channel> privateQueue;public Channel(User user) {try {this.dis = new DataInputStream(user.getSocket().getInputStream());this.dos = new DataOutputStream(user.getSocket().getOutputStream());this.shieldList = new CopyOnWriteArrayList<>();this.privateQueue = new HashMap<>();this.isRunning = true;this.user = user;} catch (IOException var3) {System.out.println("聊天室服务器初始化失败");this.release();}}public User getUser() {return user;}public CopyOnWriteArrayList<Channel> getShieldList() {return shieldList;}private void parseMsg(String msg) {String code = null;String message = null;if (msg.length() > 0) {//获取指令码Pattern pattern = Pattern.compile("<cmd>(.*)</cmd>");Matcher matcher = pattern.matcher(msg);if (matcher.find()) {code = matcher.group(1);}//获取消息pattern = Pattern.compile("<msg>(.*)</msg>");matcher = pattern.matcher(msg);if (matcher.find()) {message = matcher.group(1);}switch (code) {// 该服务器线程对应的客户端线程新用户刚加入case "new":user.setUsername(message);if (!users_model.contains("[用户" + user.getId() + "]" + user.getUsername())) {users_model.addElement("[用户" + user.getId() + "]" + user.getUsername());}String title = "[系统日志] " + df.format(new Date());String content = "[用户" + user.getId() + "]" + user.getUsername() + "  加入了聊天室";insertMessage(sysTextScrollPane, sysMsgArea, null, title, content, sysVertical, true);sendAnyone(formatCodeWithMsg("<username>" + user.getUsername() + "</username><id>" + user.getId() + "</id>", 4), false);//给当前线程服务的客户端idsend(formatCodeWithMsg(String.valueOf(user.getId()), 8));break;case "exit":if (users_model.contains("[用户" + user.getId() + "]" + user.getUsername())) {users_model.removeElement("[用户" + user.getId() + "]" + user.getUsername());}String logTitle = "[系统日志] " + df.format(new Date());String logContent = "[用户" + user.getId() + "]" + user.getUsername() + "  退出了聊天室";insertMessage(sysTextScrollPane, sysMsgArea, null, logTitle, logContent, sysVertical, true);allUserChannel.remove(this);sendAnyone(formatCodeWithMsg("" + user.getId(), 5), false);this.release(); //移除用户,关闭线程。break;case "getList"://给客户端传送send(formatCodeWithMsg(getUsersList(), 7));break;case "msg":String now = df.format(new Date());//写入服务端的聊天世界中insertMessage(userTextScrollPane, userMsgArea, null,  "[用户" + user.getId() + "]" + user.getUsername() + " "+now, " "+message, userVertical, false);// 将自己说的话发给每个人sendAnyone(formatCodeWithMsg("<userId>" + user.getId() + "</userId><userMsg>" + message + "</userMsg><time>" + now + "</time>", 1), false);break;case "buildPrivateChat"://建立私聊机制//如果私聊对象是管理员if (message.equals("0")){int option = JOptionPane.showConfirmDialog(frame, "[" + user.getUsername() + "]想与你私聊,是否同意?", "提示",JOptionPane.YES_NO_OPTION);if (option == JOptionPane.YES_OPTION) { //同意私聊String agreeMsg = "<result>1</result><from>" + adminName+ "</from><id>0</id>";send(formatCodeWithMsg(agreeMsg, 10));privateChatFrame privateChatFrame = new privateChatFrame("与[" + user.getUsername() + "]的私聊窗口", user.getUsername(), user.getId().toString());adminPrivateQueue.put(user.getId().toString(),privateChatFrame);adminPrivateThread.put(user.getId().toString(), this);}else{ //拒绝私聊String refuseMsg = "<result>0</result><from>" + adminName + "</from><id>0</id>";send(formatCodeWithMsg(refuseMsg, 10));}}else { //普通用户私聊对象for (Channel channel : allUserChannel) {if (channel.getUser().getId() == Integer.parseInt(message)) {String Msg = "<from>" + user.getUsername() + "</from><id>" + user.getId() + "</id>";this.privateQueue.put(message, channel); //先将对方放入私聊队列channel.privateQueue.put(user.getId().toString(), this); //对方也将你放入私聊队列channel.send(formatCodeWithMsg(Msg, 9)); //将自己的个人信息发给想要私聊的用户线程break;}}}break;case "agreePrivateChar": //同意与此ID的人进行私聊if (message.equals("0")){ //如果对方是管理员privateChatFrame privateChatFrame = new privateChatFrame("与[" + user.getUsername() + "]的私聊窗口", user.getUsername(), user.getId().toString());adminPrivateQueue.put(user.getId().toString(),privateChatFrame);adminPrivateThread.put(user.getId().toString(), this);}else { //普通用户String agreeMsg = "<result>1</result><from>" + user.getUsername() + "</from><id>" + user.getId() + "</id>";privateQueue.get(message).send(formatCodeWithMsg(agreeMsg, 10));}break;case "refusePrivateChar"://拒绝与此ID的人进行私聊,从私聊队列移除if(message.equals("0")){ //如果是管理员JOptionPane.showMessageDialog(frame, "[" + user.getUsername() + "]拒绝了你的私聊请求", "失败", JOptionPane.ERROR_MESSAGE);}else {String refuseMsg = "<result>0</result><from>" + user.getUsername() + "</from><id>" + user.getId() + "</id>";privateQueue.get(message).send(formatCodeWithMsg(refuseMsg, 10));privateQueue.get(message).privateQueue.remove(user.getId()); //对方也移除privateQueue.remove(message); //移除对方}break;case "privateMsg": //转发私聊消息Pattern privatePattern = Pattern.compile("<msg>(.*)</msg><id>(.*)</id>");Matcher privateMatcher = privatePattern.matcher(message);if (privateMatcher.find()) {String toPrivateMsg = privateMatcher.group(1);String toPrivateId = privateMatcher.group(2);if (toPrivateId.equals("0")){ //想要发给管理员//管理员主线程获取当前服务线程对应的私聊窗口privateChatFrame nowPrivateChat = adminPrivateQueue.get(user.getId().toString());insertMessage(nowPrivateChat.textScrollPane,nowPrivateChat.msgArea,null, df.format(new Date()) + " 对方说:", " "+toPrivateMsg, nowPrivateChat.vertical, false);}else {String resultMsg = "<msg>" + toPrivateMsg + "</msg><id>" + user.getId() + "</id>";//根据信息来源ID(想要转发的用户ID)找到对应线程将自己的id和信息发给他privateQueue.get(toPrivateId).send(formatCodeWithMsg(resultMsg, 11));}}break;case "privateExit":if (message.equals("0")){ //如果是管理员JOptionPane.showMessageDialog(frame, "由于对方结束了私聊,该私聊窗口即将关闭!", "提示", JOptionPane.WARNING_MESSAGE);adminPrivateQueue.get(user.getId().toString()).dispose();insertMessage(sysTextScrollPane, sysMsgArea, null, "[系统消息] " + df.format(new Date()), "由于[" + user.getUsername() + "]关闭了私聊窗口,私聊结束!", sysVertical, true);adminPrivateQueue.remove(user.getId().toString());   //移除此私聊对话窗口adminPrivateThread.remove(user.getId().toString());  //移除此私聊对话窗口线程}else {//普通用户私聊String endMsg = "<id>" + user.getId() + "</id>";privateQueue.get(message).send(formatCodeWithMsg(endMsg, 12));privateQueue.get(message).privateQueue.remove(this.user.getId().toString()); //对方也移除privateQueue.remove(message); //移除对方}break;case "shield": //将传来的id对应的服务线程加入到屏蔽列表里面for (Channel channel : allUserChannel) {if (channel.getUser().getId() == Integer.parseInt(message)) {if (!shieldList.contains(channel)) {shieldList.add(channel);}break;}}System.out.println("屏蔽时:"+shieldList);break;case "unshield": //将传来的id对应的服务线程从屏蔽列表里面删除for (Channel channel : allUserChannel) {if (channel.getUser().getId() == Integer.parseInt(message)) {if (shieldList.contains(channel)) {shieldList.remove(channel);}break;}}System.out.println("取消屏蔽时:"+shieldList);break;case "setName":// 改了昵称,跟其它客户端的用户列表进行更正users_model.removeElement(user.getId() + "#@" + user.getUsername());user.setUsername(message);users_model.addElement(user.getId() + "#@" + user.getUsername());sendAnyone(formatCodeWithMsg("<id>" + user.getId() + "</id><username>" + message + "</username>", 6), false);break;default:System.out.println("not valid message from user" + user.getId());break;}}}private String getUsersList() {StringBuffer stringBuffer = new StringBuffer();/*获得房间中所有的用户的列表,然后构造成一定的格式发送回去*/stringBuffer.append("<user><id>0</id><username>" + adminName + "</username></user>");for (Channel each : allUserChannel) {stringBuffer.append("<user><id>" + each.getUser().getId() +"</id><username>" + each.getUser().getUsername() + "</username></user>");}return stringBuffer.toString();}private String receive() {String msg = "";try {msg = this.dis.readUTF();} catch (IOException var3) {this.release();}return msg;}public void send(String msg) {try {this.dos.writeUTF(msg);this.dos.flush();} catch (IOException var3) {insertMessage(sysTextScrollPane, sysMsgArea, null, "[系统日志] "+df.format(new Date()), "用户[" + user.getUsername() + "]的服务器端服务线程出错,请重启服务器!", sysVertical, true);this.release();}}private void sendAnyone(String msg, boolean isSys) {for (Channel userChannel : allUserChannel) {//获取每个用户的线程类if (!userChannel.getShieldList().contains(this)) {//当前服务线程不在对方线程屏蔽组内的才发送信息userChannel.send(msg);}}}//释放资源public void release() {this.isRunning = false;close(dis, dos, this.user.getSocket());// 列表中移除用户users_model.removeElement(user.getId() + "#@" + user.getUsername());if (allUserChannel.contains(this)) {allUserChannel.remove(this);}users_label.setText("当前连接用户:" + allUserChannel.size());}@Overridepublic void run() {while (isRunning) {String msg = receive();if (!msg.equals("")) {parseMsg(msg);}}}}/*** @ClassName privateChatFrame* @Params  * @param null* @Description 管理员所属的私聊窗口内部类* @Return* @Since 2020/6/6*/private class privateChatFrame extends JFrame {private String otherName;private String otherId;private JButton sendButton;private JTextField msgTestField;private JTextPane msgArea;private JScrollPane textScrollPane;private JScrollBar vertical;public privateChatFrame(String title, String otherName, String otherId) throws HeadlessException {super(title);this.otherName = otherName;this.otherId = otherId;//全局面板容器JPanel panel = new JPanel();//全局布局BorderLayout layout = new BorderLayout();JPanel headpanel = new JPanel();    //上层panel,JPanel footpanel = new JPanel();    //下层panelJPanel centerpanel = new JPanel(); //中间panel//头部布局FlowLayout flowLayout = new FlowLayout();//底部布局GridBagLayout gridBagLayout = new GridBagLayout();setSize(600, 500);setLocationRelativeTo(null);setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);//窗口关闭事件addWindowListener(new WindowAdapter() {@Overridepublic void windowClosing(WindowEvent e) {int option = JOptionPane.showConfirmDialog(e.getOppositeWindow(), "确定结束私聊?", "提示",JOptionPane.YES_NO_OPTION);if (option == JOptionPane.YES_OPTION) {if (server != null) {//关闭当前私聊连接String endMsg = "<id>0</id>";adminPrivateThread.get(otherId).send(formatCodeWithMsg(endMsg, 12)); //关闭当前私聊连接adminPrivateQueue.remove(otherId);   //移除此私聊对话窗口adminPrivateThread.remove(otherId);}insertMessage(sysTextScrollPane, sysMsgArea, null, "[系统消息] " + df.format(new Date()), "您与[" + otherName + "]的私聊结束", sysVertical, true);dispose();} else {return;}}});setContentPane(panel);setLayout(layout);headpanel.setLayout(flowLayout);footpanel.setLayout(gridBagLayout);footpanel.setPreferredSize(new Dimension(0, 40));centerpanel.setLayout(gridBagLayout);//添加头部部件JLabel Name = new JLabel(otherName);headpanel.add(Name);//设置底部布局sendButton = new JButton("发送");sendButton.setPreferredSize(new Dimension(40, 0));msgTestField = new JTextField();footpanel.add(msgTestField, new GridBagConstraints(0, 0, 1, 1, 100, 100,GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(1, 1, 1, 1), 0, 0));footpanel.add(sendButton, new GridBagConstraints(1, 0, 1, 1, 10, 10,GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(1, 1, 1, 1), 0, 0));//中间布局msgArea = new JTextPane();msgArea.setEditable(false);textScrollPane = new JScrollPane();textScrollPane.setViewportView(msgArea);vertical = new JScrollBar(JScrollBar.VERTICAL);vertical.setAutoscrolls(true);textScrollPane.setVerticalScrollBar(vertical);centerpanel.add(textScrollPane, new GridBagConstraints(0, 0, 1, 1, 100, 100,GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0));//设置顶层布局panel.add(headpanel, "North");panel.add(footpanel, "South");panel.add(centerpanel, "Center");//聊天信息输入框的监听回车按钮事件msgTestField.addKeyListener(new KeyAdapter() {@Overridepublic void keyTyped(KeyEvent e) {if (e.getKeyChar() == KeyEvent.VK_ENTER) {if (server == null) {JOptionPane.showMessageDialog(frame, "请先开启聊天室的服务器!", "提示", JOptionPane.WARNING_MESSAGE);return;}String text = msgTestField.getText();if (text != null && !text.equals("")) {String resultMsg = "<msg>"+text+"</msg><id>0</id>";adminPrivateThread.get(otherId).send(formatCodeWithMsg(resultMsg,11));msgTestField.setText("");insertMessage(textScrollPane, msgArea, null, df.format(new Date()) + " 你说:", " "+text, vertical, false);}}}});sendButton.addActionListener(new ActionListener() {@Overridepublic void actionPerformed(ActionEvent e) {String cmd = e.getActionCommand();if (cmd.equals("发送")) {if (server == null) {JOptionPane.showMessageDialog(frame, "请先开启聊天室的服务器!", "提示", JOptionPane.WARNING_MESSAGE);return;}String text = msgTestField.getText();if (text != null && !text.equals("")) {String resultMsg = "<msg>"+text+"</msg><id>0</id>";adminPrivateThread.get(otherId).send(formatCodeWithMsg(resultMsg,11));msgTestField.setText("");insertMessage(textScrollPane, msgArea, null, df.format(new Date()) + " 你说:", " "+text, vertical, false);}}}});//窗口显示setVisible(true);}}/*** @MethodName setUIStyle* @Params  * @param null* @Description 根据操作系统自动变化GUI界面风格* @Return* @Since 2020/6/6*/public static void setUIStyle() {//        String lookAndFeel = UIManager.getSystemLookAndFeelClassName(); //设置当前系统风格String lookAndFeel = UIManager.getCrossPlatformLookAndFeelClassName(); //可跨系统try {UIManager.setLookAndFeel(lookAndFeel);UIManager.put("Menu.font", new Font("宋体", Font.PLAIN, 12));} catch (ClassNotFoundException e) {// TODO Auto-generated catch blocke.printStackTrace();} catch (InstantiationException e) {// TODO Auto-generated catch blocke.printStackTrace();} catch (IllegalAccessException e) {// TODO Auto-generated catch blocke.printStackTrace();} catch (UnsupportedLookAndFeelException e) {// TODO Auto-generated catch blocke.printStackTrace();}}}

客户端

  • 主线程运行用户端的GUI界面,发送用户的需求指令和信息给服务器端
  • 创建一个子线程receive来接受服务器端发来指令和信息。
package top.hcode.chatRoom;import javax.swing.*;
import javax.swing.text.*;
import java.awt.*;
import java.awt.event.*;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;import static top.hcode.chatRoom.CloseUtils.close;/**1. @Author: Himit_ZH2. @Date: 2020/6/4 14:553. @Description: 聊天室客户端GUI*/
public class Client {private JFrame frame;//头部参数private JTextField host_textfield;private JTextField port_textfield;private JTextField name_textfield;private JButton head_connect;private JButton head_exit;//底部参数private JTextField text_field;private JButton foot_send;private JButton foot_sysClear;private JButton foot_userClear;//右边参数private JLabel users_label;private JButton privateChat_button;private JButton shield_button;private JButton unshield_button;private JList<String> userlist;private DefaultListModel<String> users_model;private HashMap<String, Integer> users_map;//左边参数private JScrollPane sysTextScrollPane;private JTextPane sysMsgArea;private JScrollBar sysVertical;//中间参数private JScrollPane userTextScrollPane;private JTextPane userMsgArea;private JScrollBar userVertical;//发送和接受参数private DataOutputStream dos;private Receive receive;private Socket charClient;//时间格式化工具类static private SimpleDateFormat df = new SimpleDateFormat("HH:mm:ss");//设置时间//当前用户的idprivate int id;//私聊窗口Mapprivate HashMap<String, privateChatFrame> privateChatFrameMap;public static void main(String[] args) {new Client().init();}/*** @MethodName init* @Params  * @param null* @Description 客户端GUI界面初始化,各种监听事件绑定* @Return* @Since 2020/6/6*/public void init() {users_map = new HashMap<>();privateChatFrameMap = new HashMap<>();/*设置窗口的UI风格和字体*/setUIStyle();frame = new JFrame("Hcode聊天室用户端");JPanel panel = new JPanel();        /*主要的panel,上层放置连接区,下层放置消息区,中间是消息面板,左边是系统消息,右边是当前room的用户列表*/JPanel headpanel = new JPanel();    /*上层panel,用于放置连接区域相关的组件*/JPanel footpanel = new JPanel();    /*下层panel,用于放置发送信息区域的组件*/JPanel centerpanel = new JPanel();    /*中间panel,用于放置聊天信息*/JPanel leftpanel = new JPanel();    /*左边panel,用于放置房间列表和加入按钮*/JPanel rightpanel = new JPanel();   /*右边panel,用于放置房间内人的列表*//*顶层的布局,分中间,东南西北五个部分*/BorderLayout layout = new BorderLayout();/*格子布局,主要用来设置西、东、南三个部分的布局*/GridBagLayout gridBagLayout = new GridBagLayout();/*主要设置北部的布局*/FlowLayout flowLayout = new FlowLayout();/*设置初始窗口的一些性质*/frame.setSize(800, 600);frame.setLocationRelativeTo(null);frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);frame.setContentPane(panel);frame.setLayout(layout);/*设置各个部分的panel的布局和大小*/headpanel.setLayout(flowLayout);footpanel.setLayout(gridBagLayout);leftpanel.setLayout(gridBagLayout);centerpanel.setLayout(gridBagLayout);rightpanel.setLayout(gridBagLayout);//设置面板大小leftpanel.setPreferredSize(new Dimension(200, 0));rightpanel.setPreferredSize(new Dimension(155, 0));footpanel.setPreferredSize(new Dimension(0, 40));//头部布局host_textfield = new JTextField("127.0.0.1");port_textfield = new JTextField("8888");name_textfield = new JTextField("匿名");host_textfield.setPreferredSize(new Dimension(100, 25));port_textfield.setPreferredSize(new Dimension(70, 25));name_textfield.setPreferredSize(new Dimension(150, 25));JLabel host_label = new JLabel("服务器IP:");JLabel port_label = new JLabel("端口:");JLabel name_label = new JLabel("昵称:");head_connect = new JButton("连接");head_exit = new JButton("退出");headpanel.add(host_label);headpanel.add(host_textfield);headpanel.add(port_label);headpanel.add(port_textfield);headpanel.add(name_label);headpanel.add(name_textfield);headpanel.add(head_connect);headpanel.add(head_exit);//底部布局foot_send = new JButton("发送");foot_sysClear = new JButton("清空系统消息");foot_sysClear.setPreferredSize(new Dimension(193, 0));foot_userClear = new JButton("清空聊天消息");foot_userClear.setPreferredSize(new Dimension(148, 0));text_field = new JTextField();footpanel.add(foot_sysClear, new GridBagConstraints(0, 0, 1, 1, 1.0, 1.0,GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(1, 1, 1, 1), 0, 0));footpanel.add(text_field, new GridBagConstraints(1, 0, 1, 1, 100, 100,GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(1, 1, 1, 1), 0, 0));footpanel.add(foot_send, new GridBagConstraints(2, 0, 1, 1, 1.0, 1.0,GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(1, 1, 1, 1), 0, 0));footpanel.add(foot_userClear, new GridBagConstraints(3, 0, 1, 1, 1.0, 1.0,GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(1, 1, 1, 1), 0, 0));//左边布局JLabel sysMsg_label = new JLabel("系统消息:");sysMsgArea = new JTextPane();sysMsgArea.setEditable(false);sysTextScrollPane = new JScrollPane();sysTextScrollPane.setViewportView(sysMsgArea);sysVertical = new JScrollBar(JScrollBar.VERTICAL);sysVertical.setAutoscrolls(true);sysTextScrollPane.setVerticalScrollBar(sysVertical);leftpanel.add(sysMsg_label, new GridBagConstraints(0, 0, 1, 1, 1, 1,GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0));leftpanel.add(sysTextScrollPane, new GridBagConstraints(0, 1, 1, 1, 100, 100,GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0));//右边布局users_model = new DefaultListModel<>();userlist = new JList<String>(users_model);JScrollPane userListPane = new JScrollPane(userlist);users_label = new JLabel("聊天室内人数:0");privateChat_button = new JButton("私聊");shield_button = new JButton("屏蔽对方");unshield_button = new JButton("取消屏蔽");rightpanel.add(users_label, new GridBagConstraints(0, 0, 1, 1, 1, 1,GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0));rightpanel.add(privateChat_button, new GridBagConstraints(0, 1, 1, 1, 1, 1,GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0));rightpanel.add(shield_button, new GridBagConstraints(0, 2, 1, 1, 1, 1,GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0));rightpanel.add(unshield_button, new GridBagConstraints(0, 3, 1, 1, 1, 1,GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0));rightpanel.add(userListPane, new GridBagConstraints(0, 4, 1, 1, 100, 100,GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0));//中间布局JLabel userMsg_label = new JLabel("世界聊天:");userMsgArea = new JTextPane();userMsgArea.setEditable(false);userTextScrollPane = new JScrollPane();userTextScrollPane.setViewportView(userMsgArea);userVertical = new JScrollBar(JScrollBar.VERTICAL);userVertical.setAutoscrolls(true);userTextScrollPane.setVerticalScrollBar(userVertical);centerpanel.add(userMsg_label, new GridBagConstraints(0, 0, 1, 1, 1, 1,GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0));centerpanel.add(userTextScrollPane, new GridBagConstraints(0, 1, 1, 1, 100, 100,GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0));/*设置顶层布局*/panel.add(headpanel, "North");panel.add(footpanel, "South");panel.add(leftpanel, "West");panel.add(rightpanel, "East");panel.add(centerpanel, "Center");//将按钮事件全部注册到监听器allActionListener allActionListener = new allActionListener();//连接服务器head_connect.addActionListener(allActionListener);//往服务器发送消息foot_send.addActionListener(allActionListener);//退出聊天室head_exit.addActionListener(allActionListener);//清空系统消息foot_sysClear.addActionListener(allActionListener);//清空世界聊天消息foot_userClear.addActionListener(allActionListener);//私聊privateChat_button.addActionListener(allActionListener);//屏蔽shield_button.addActionListener(allActionListener);//取消屏蔽unshield_button.addActionListener(allActionListener);//窗口关闭事件frame.addWindowListener(new WindowAdapter() {@Overridepublic void windowClosing(WindowEvent e) {int option = JOptionPane.showConfirmDialog(frame, "确定关闭聊天室界面?", "提示",JOptionPane.YES_NO_OPTION);if (option == JOptionPane.YES_OPTION) {if (e.getWindow() == frame) {if (receive != null) {sendMsg("exit", ""); //如果已连接就告诉服务器本客户端已断开连接,退出聊天室}frame.dispose();System.exit(0);} else {return;}} else {return;}}});//聊天信息输入框的监听回车按钮事件text_field.addKeyListener(new KeyAdapter() {@Overridepublic void keyTyped(KeyEvent e) {if (e.getKeyChar() == KeyEvent.VK_ENTER) {if (charClient == null || receive == null) {JOptionPane.showMessageDialog(frame, "请先连接服务器进入聊天室!", "提示", JOptionPane.WARNING_MESSAGE);return;}String text = text_field.getText();if (text != null && !text.equals("")) {sendMsg("msg", text);text_field.setText("");}}}});//窗口显示frame.setVisible(true);String name = JOptionPane.showInputDialog("请输入聊天所用昵称:");if (name != null &&!name.equals("")) {name_textfield.setText(name);}}/*** @MethodName ipCheck* @Params  * @param null* @Description 验证ip格式是否正确* @Return* @Since 2020/6/8*/public static boolean ipCheckHost(String text) {if (text != null && !text.isEmpty()) {// 定义正则表达式String regex = "^(1\\d{2}|2[0-4]\\d|25[0-5]|[1-9]\\d|[1-9])\\."+"(1\\d{2}|2[0-4]\\d|25[0-5]|[1-9]\\d|\\d)\\."+"(1\\d{2}|2[0-4]\\d|25[0-5]|[1-9]\\d|\\d)\\."+"(1\\d{2}|2[0-4]\\d|25[0-5]|[1-9]\\d|\\d)$";// 判断ip地址是否与正则表达式匹配if (text.matches(regex)) {// 返回判断信息return true;} else {// 返回判断信息return false;}}return false;}/*** @MethodName ipCheckPort* @Params  * @param null* @Description 验证端口格式是否准确* @Return* @Since 2020/6/8*/public static boolean ipCheckPort(String text){return text.matches("([0-9]|[1-9]\\d{1,3}|[1-5]\\d{4}|6[0-5]{2}[0-3][0-5])");}/*** @ClassName allActionListener* @Params  * @param null* @Description 全局监听事件,监听所有按钮,输入框,内部类* @Return* @Since 2020/6/6*/private class allActionListener implements ActionListener {@Overridepublic void actionPerformed(ActionEvent e) {String cmd = e.getActionCommand();switch (cmd) {case "连接"://获取文本框里面的ip和portString strhost = host_textfield.getText();String strport = port_textfield.getText();if (!ipCheckHost(strhost)){JOptionPane.showMessageDialog(frame, "请检查ip格式是否准确!", "错误", JOptionPane.ERROR_MESSAGE);break;}if(!ipCheckPort(strport)){JOptionPane.showMessageDialog(frame, "请检查端口号是否为0~65535之间的整数!", "错误", JOptionPane.ERROR_MESSAGE);break;}//连接服务器,开启线程connectServer(strhost, Integer.parseInt(strport));
//                    String name = JOptionPane.showInputDialog("请输入你的昵称:"); /*提示输入昵称*/
//                    name_textfield.setText(name);/*发送设置姓名的消息和列出用户列表的消息*/
//                    send.setMsg("setName", name);break;case "退出":if (charClient == null || receive == null) {JOptionPane.showMessageDialog(frame, "对不起,您现在不在聊天室,无法退出!", "错误", JOptionPane.ERROR_MESSAGE);break;}sendMsg("exit", "");head_connect.setText("连接");head_exit.setText("已退出");port_textfield.setEditable(true);name_textfield.setEditable(true);host_textfield.setEditable(true);try {charClient.close();} catch (IOException ioException) {JOptionPane.showMessageDialog(frame, "关闭连接服务器失败,请重启客户端!", "错误", JOptionPane.ERROR_MESSAGE);}showEscDialog("您已成功退出聊天室!");charClient = null;receive = null;break;case "发送":if (charClient == null || receive == null) {JOptionPane.showMessageDialog(frame, "请先连接服务器进入聊天室!", "提示", JOptionPane.WARNING_MESSAGE);break;}String text = text_field.getText();if (text != null && !text.equals("")) {sendMsg("msg", text);text_field.setText("");}break;case "私聊"://服务器未连接if (charClient == null || receive == null) {JOptionPane.showMessageDialog(frame, "请先连接服务器进入聊天室!", "提示", JOptionPane.WARNING_MESSAGE);break;}String selected = userlist.getSelectedValue();//私聊自己if (selected.equals(getUserName(String.valueOf(id)))) {JOptionPane.showMessageDialog(frame, "你不能私聊自己!", "警告", JOptionPane.WARNING_MESSAGE);break;}//已有私聊窗口if (privateChatFrameMap.containsKey(users_map.get(selected).toString())) {JOptionPane.showMessageDialog(frame, "与该用户私聊窗口已存在,请不要重复私聊!", "错误", JOptionPane.ERROR_MESSAGE);break;}if (users_map.containsKey(selected)) {sendMsg("buildPrivateChat", String.valueOf(users_map.get(selected)));//建立沟通弹窗}break;case "屏蔽对方":if (charClient == null || receive == null) {JOptionPane.showMessageDialog(frame, "请先连接服务器进入聊天室!", "提示", JOptionPane.WARNING_MESSAGE);break;}String selectedShield = userlist.getSelectedValue();//如果是自己if (selectedShield.equals(getUserName(String.valueOf(id)))) {JOptionPane.showMessageDialog(frame, "你不能屏蔽自己!", "警告", JOptionPane.WARNING_MESSAGE);break;}//不准屏蔽管理员!if (selectedShield.equals(getUserName(String.valueOf(0)))) {JOptionPane.showMessageDialog(frame, "对不起,不支持屏蔽管理员!", "警告", JOptionPane.WARNING_MESSAGE);break;}//不能重复屏蔽!if (selectedShield.indexOf("(已屏蔽)") != -1) {JOptionPane.showMessageDialog(frame, "对方已被屏蔽了!请不要重复操作!", "错误", JOptionPane.ERROR_MESSAGE);break;}if (users_map.containsKey(selectedShield)) { //发送需要屏蔽用户的idsendMsg("shield", String.valueOf(users_map.get(selectedShield)));users_map.put(selectedShield + "(已屏蔽)", users_map.get(selectedShield));users_map.remove(selectedShield);}int index1 = users_model.indexOf(selectedShield);users_model.set(index1, selectedShield + "(已屏蔽)");break;case "取消屏蔽":if (charClient == null || receive == null) {JOptionPane.showMessageDialog(frame, "请先连接服务器进入聊天室!", "提示", JOptionPane.WARNING_MESSAGE);break;}String unShield = userlist.getSelectedValue();if (unShield.indexOf("(已屏蔽)") == -1) {JOptionPane.showMessageDialog(frame, "对方并未被屏蔽!", "警告", JOptionPane.WARNING_MESSAGE);break;}String name = unShield.substring(0, unShield.indexOf("(已屏蔽)"));sendMsg("unshield", String.valueOf(users_map.get(unShield)));int index2 = users_model.indexOf(unShield);users_model.set(index2, name);users_map.put(name, users_map.get(unShield));users_map.remove(unShield);break;case "清空系统消息":sysMsgArea.setText("");break;case "清空聊天消息":userMsgArea.setText("");break;default:break;}}}/*** @MethodName connectServer* @Params  * @param null* @Description 开启与服务器的连接,开启接受服务器的指令与信息的Receive线程类* @Return* @Since 2020/6/6*/private boolean connectServer(String host, int port) {try {charClient = new Socket(host, port);dos = new DataOutputStream(charClient.getOutputStream());receive = new Receive(charClient, this);(new Thread(receive)).start();//接受服务器的消息的线程(系统消息和其他网友的信息)head_connect.setText("已连接");head_exit.setText("退出");port_textfield.setEditable(false);name_textfield.setEditable(false);host_textfield.setEditable(false);sendMsg("new", name_textfield.getText()); //后续写在登录窗口sendMsg("getList", "");return true;} catch (IOException e) {JOptionPane.showMessageDialog(frame, "连接服务器失败!", "错误", JOptionPane.ERROR_MESSAGE);return false;}}/*** @MethodName sendMsg* @Params  * @param null* @Description 发送指令与信息给服务器端对应的线程服务* @Return* @Since 2020/6/6*/private void sendMsg(String cmd, String msg) {try {dos.writeUTF("<cmd>" + cmd + "</cmd><msg>" + msg + "</msg>");dos.flush();} catch (IOException e) {close(dos, charClient);}}public void setId(int id) {this.id = id;}/*** @MethodName updateTextArea* @Params  * @param null* @Description 更新系统文本域或聊天事件文本域* @Return* @Since 2020/6/6*/public void updateTextArea(String content, String where) {if (content.length() > 0) {Matcher matcher = null;if (where.equals("user")) {Pattern pattern = Pattern.compile("<userId>(.*)</userId><userMsg>(.*)</userMsg><time>(.*)</time>");matcher = pattern.matcher(content);if (matcher.find()) {String userId = matcher.group(1);String userMsg = matcher.group(2);String time = matcher.group(3);
//                if(userMsg.startsWith("<emoji>")){//                    String emojiCode = userMsg.substring(7, smsg.length()-8);
//                    insertMessage(userTextScrollPane, userMsgArea, emojiCode, fromName+"说:", null,userVertical);
//                    return ;
//                }if (userId.equals("0")) {insertMessage(userTextScrollPane, userMsgArea, null, getUserName(userId) + " "+time , " "+userMsg, userVertical, false);} else {String fromName = getUserName(userId);if (fromName.equals("[用户" + id + "]" + name_textfield.getText())) //如果是自己说的话fromName = "你";insertMessage(userTextScrollPane, userMsgArea, null,  fromName + " "+time, " "+userMsg, userVertical, false);}}} else {Pattern pattern = Pattern.compile("<time>(.*)</time><sysMsg>(.*)</sysMsg>");matcher = pattern.matcher(content);if (matcher.find()) {String sysTime = matcher.group(1);String sysMsg = matcher.group(2);insertMessage(sysTextScrollPane, sysMsgArea, null, "[系统消息] " + sysTime, sysMsg, sysVertical, true);}}}}/*** @MethodName insertMessage* @Params  * @param null* @Description 更新文本域信息格式化工具* @Return* @Since 2020/6/6*/private void insertMessage(JScrollPane scrollPane, JTextPane textPane, String icon_code,String title, String content, JScrollBar vertical, boolean isSys) {StyledDocument document = textPane.getStyledDocument();     /*获取textpane中的文本*//*设置标题的属性*/Color content_color = null;if (isSys) {content_color = Color.RED;} else {content_color = Color.GRAY;}SimpleAttributeSet title_attr = new SimpleAttributeSet();StyleConstants.setBold(title_attr, true);StyleConstants.setForeground(title_attr, Color.BLUE);/*设置正文的属性*/SimpleAttributeSet content_attr = new SimpleAttributeSet();StyleConstants.setBold(content_attr, false);StyleConstants.setForeground(content_attr, content_color);Style style = null;if (icon_code != null) {Icon icon = new ImageIcon("icon/" + icon_code + ".png");style = document.addStyle("icon", null);StyleConstants.setIcon(style, icon);}try {document.insertString(document.getLength(), title + "\n", title_attr);if (style != null)document.insertString(document.getLength(), "\n", style);elsedocument.insertString(document.getLength(), content + "\n", content_attr);} catch (BadLocationException ex) {System.out.println("Bad location exception");}/*设置滑动条到最后*/textPane.setCaretPosition(textPane.getDocument().getLength());
//        vertical.setValue(vertical.getMaximum());}/*** @MethodName getUserName* @Params  * @param null* @Description 在users_map中根据value值用户ID获取key值的用户名字* @Return* @Since 2020/6/6*/private String getUserName(String strId) {int uid = Integer.parseInt(strId);Set<String> set = users_map.keySet();Iterator<String> iterator = set.iterator();String cur = null;while (iterator.hasNext()) {cur = iterator.next();if (users_map.get(cur) == uid) {return cur;}}return "";}/*** @MethodName showEscDialog* @Params  * @param null* @Description 处理当前客户端用户断开与服务器连接的一切事务* @Return* @Since 2020/6/6*/public void showEscDialog(String content) {//清除所有私聊if (privateChatFrameMap.size() != 0) {Set<Map.Entry<String, privateChatFrame>> entrySet = privateChatFrameMap.entrySet();Iterator<Map.Entry<String, privateChatFrame>> iter = entrySet.iterator();while (iter.hasNext()){Map.Entry<String, privateChatFrame> entry = iter.next();entry.getValue().dispose(); //关闭对应窗口sendMsg("privateExit", entry.getKey()); //想对方说明私聊结束}}//关闭输出流close(dos, charClient);receive.setRunning(false);//输入框可编辑port_textfield.setEditable(true);name_textfield.setEditable(true);host_textfield.setEditable(true);head_connect.setText("连接");head_exit.setText("已退出");insertMessage(sysTextScrollPane, sysMsgArea, null, "[系统消息] " + df.format(new Date()), content, sysVertical, true);JOptionPane.showMessageDialog(frame, content, "提示", JOptionPane.WARNING_MESSAGE);/*清除消息区内容,清除用户数据模型内容和用户map内容,更新房间内人数*/userMsgArea.setText("");
//        sysMsgArea.setText("");users_map.clear();users_model.removeAllElements();users_label.setText("聊天室内人数:0");}/*** @MethodName addUser* @Params  * @param null* @Description 当有新的用户加入聊天室,系统文本域的更新和用户列表的更新* @Return* @Since 2020/6/6*/public void addUser(String content) {if (content.length() > 0) {Pattern pattern = Pattern.compile("<username>(.*)</username><id>(.*)</id>");Matcher matcher = pattern.matcher(content);if (matcher.find()) {String name = matcher.group(1);String id = matcher.group(2);if (!users_map.containsKey(name)) {users_map.put("[用户" + id + "]" + name, Integer.parseInt(id));users_model.addElement("[用户" + id + "]" + name);} else {users_map.remove("[用户" + id + "]" + name);users_model.removeElement(name);users_map.put("[用户" + id + "]" + name, Integer.parseInt(id));users_model.addElement("[用户" + id + "]" + name);}insertMessage(sysTextScrollPane, sysMsgArea, null, "[系统消息] " + df.format(new Date()), "[用户" + id + "]" + name + " 加入了聊天室", sysVertical, true);}}users_label.setText("聊天室内人数:" + users_map.size()); //更新房间内的人数}/*** @MethodName delUser* @Params  content(为退出用户的ID)* @Description 当有用户退出时,系统文本域的通知和用户列表的更新* @Return* @Since 2020/6/6*/public void delUser(String content) {if (content.length() > 0) {Set<String> set = users_map.keySet();String delName = getUserName(content);insertMessage(sysTextScrollPane, sysMsgArea, null, "[系统消息] " + df.format(new Date()), delName + " 退出了聊天室", sysVertical, true);users_map.remove(delName);users_model.removeElement(delName);}users_label.setText("聊天室内人数:" + users_map.size());//更新房间内的人数}/*** @MethodName updateUsername* @Params  content(为指定用户的ID)* @Description 修改指定ID用户的昵称(暂时用不到)* @Return* @Since 2020/6/6*/public void updateUsername(String content) {if (content.length() > 0) {Pattern pattern = Pattern.compile("<id>(.*)</id><username>(.*)</username>");Matcher matcher = pattern.matcher(content);if (matcher.find()) {String id = matcher.group(1);String name = matcher.group(2);if (id.equals("0")) {users_map.remove("[管理员]" + name);users_model.removeElementAt(0);users_map.put("[管理员]" + name, Integer.parseInt(id));users_model.addElement("[管理员]" + name);} else if (users_map.get("[用户" + id + "]" + name) != Integer.parseInt(id)) {users_map.put("[用户" + id + "]" + name, Integer.parseInt(id));users_model.addElement("[用户" + id + "]" + name);} else {users_map.remove("[用户" + id + "]" + name);users_model.removeElement("[用户" + id + "]" + name);users_map.put("[用户" + id + "]" + name, Integer.parseInt(id));users_model.addElement("[用户" + id + "]" + name);}}}}/*** @MethodName getUserList* @Params  * @param null* @Description 从服务器获取全部用户信息的列表,解析信息格式,列出所有用户* @Return* @Since 2020/6/6*/public void getUserList(String content) {String name = null;String id = null;Pattern numPattern = null;Matcher numMatcher = null;Pattern userListPattern = null;if (content.length() > 0) {numPattern = Pattern.compile("<user>(.*?)</user>");numMatcher = numPattern.matcher(content);//遍历字符串,进行正则匹配,获取所有用户信息while (numMatcher.find()) {String detail = numMatcher.group(1);userListPattern = Pattern.compile("<id>(.*)</id><username>(.*)</username>");Matcher userListmatcher = userListPattern.matcher(detail);if (userListmatcher.find()) {name = userListmatcher.group(2);id = userListmatcher.group(1);if (id.equals("0")) {name = "[管理员]" + name;users_map.put(name, Integer.parseInt(id));} else {name = "[用户" + id + "]" + name;users_map.put(name, Integer.parseInt(id));}users_model.addElement(name);}}users_model.removeElementAt(0);}users_label.setText("聊天室内人数:" + users_map.size());}/*** @MethodName askBuildPrivateChat* @Params  * @param null* @Description 处理某用户对当前客户端的用户的私聊请求* @Return* @Since 2020/6/6*/public void askBuildPrivateChat(String msg) {Pattern pattern = Pattern.compile("<from>(.*)</from><id>(.*)</id>");Matcher matcher = pattern.matcher(msg);if (matcher.find()) {String toPrivateChatName = matcher.group(1);String toPrivateChatId = matcher.group(2);int option = JOptionPane.showConfirmDialog(frame, "[" + toPrivateChatName + "]想与你私聊,是否同意?", "提示",JOptionPane.YES_NO_OPTION);if (option == JOptionPane.YES_OPTION) {sendMsg("agreePrivateChar", toPrivateChatId);privateChatFrame chatFrame = new privateChatFrame("与[" + toPrivateChatName + "]的私聊窗口", toPrivateChatName, toPrivateChatId);privateChatFrameMap.put(toPrivateChatId, chatFrame);} else {sendMsg("refusePrivateChar", toPrivateChatId);}}}/*** @MethodName startOrStopHisPrivateChat* @Params  * @param null* @Description 获取请求指定用户的私聊请求的结果,同意就开启私聊窗口,拒绝就提示。* @Return* @Since 2020/6/6*/public void startOrStopHisPrivateChat(String msg) {Pattern pattern = Pattern.compile("<result>(.*)</result><from>(.*)</from><id>(.*)</id>");Matcher matcher = pattern.matcher(msg);if (matcher.find()) {String result = matcher.group(1);String toPrivateChatName = matcher.group(2);String toPrivateChatId = matcher.group(3);if (result.equals("1")) {  //对方同意的话if (toPrivateChatId.equals("0")) {toPrivateChatName = "[管理员]" + toPrivateChatName;}privateChatFrame chatFrame = new privateChatFrame("与[" + toPrivateChatName + "]的私聊窗口", toPrivateChatName, toPrivateChatId);privateChatFrameMap.put(toPrivateChatId, chatFrame);} else if (result.equals("0")) {JOptionPane.showMessageDialog(frame, "[" + toPrivateChatName + "]拒绝了你的私聊请求", "失败", JOptionPane.ERROR_MESSAGE);}}}/*** @MethodName giveMsgToPrivateChat* @Params  * @param null* @Description 根据服务器端发来的用户ID和内容,搜寻当前客户端的用户中对应传来的用户ID的私聊窗口,将内容写进私聊窗口的文本域* @Return* @Since 2020/6/6*/public void giveMsgToPrivateChat(String msg) {Pattern privatePattern = Pattern.compile("<msg>(.*)</msg><id>(.*)</id>");Matcher privateMatcher = privatePattern.matcher(msg);if (privateMatcher.find()) {String toPrivateMsg = privateMatcher.group(1);String toPrivateId = privateMatcher.group(2);privateChatFrame chatFrame = privateChatFrameMap.get(toPrivateId);insertMessage(chatFrame.textScrollPane, chatFrame.msgArea, null, df.format(new Date()) + " 对方说:", " "+toPrivateMsg, chatFrame.vertical, false);}}/*** @MethodName endPrivateChat* @Params  * @param null* @Description 结束指定id用户的私聊窗口* @Return* @Since 2020/6/6*/public void endPrivateChat(String msg) {Pattern privatePattern = Pattern.compile("<id>(.*)</id>");Matcher privateMatcher = privatePattern.matcher(msg);if (privateMatcher.find()) {String endPrivateId = privateMatcher.group(1);privateChatFrame chatFrame = privateChatFrameMap.get(endPrivateId);JOptionPane.showMessageDialog(frame, "由于对方结束了私聊,该私聊窗口即将关闭!", "提示", JOptionPane.WARNING_MESSAGE);chatFrame.dispose();insertMessage(sysTextScrollPane, sysMsgArea, null, "[系统消息] " + df.format(new Date()), "由于[" + chatFrame.otherName + "]关闭了私聊窗口,私聊结束!", sysVertical, true);privateChatFrameMap.remove(endPrivateId);}}/*** @ClassName privateChatFrame* @Params  * @param null* @Description 私聊窗口GUI的内部类* @Return* @Since 2020/6/6*/private class privateChatFrame extends JFrame {private String otherName;private String otherId;private JButton sendButton;private JTextField msgTestField;private JTextPane msgArea;private JScrollPane textScrollPane;private JScrollBar vertical;public privateChatFrame(String title, String otherName, String otherId) throws HeadlessException {super(title);this.otherName = otherName;this.otherId = otherId;//全局面板容器JPanel panel = new JPanel();//全局布局BorderLayout layout = new BorderLayout();JPanel headpanel = new JPanel();    //上层panel,JPanel footpanel = new JPanel();    //下层panelJPanel centerpanel = new JPanel(); //中间panel//头部布局FlowLayout flowLayout = new FlowLayout();//底部布局GridBagLayout gridBagLayout = new GridBagLayout();setSize(600, 500);setLocationRelativeTo(null);setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);setContentPane(panel);setLayout(layout);headpanel.setLayout(flowLayout);footpanel.setLayout(gridBagLayout);footpanel.setPreferredSize(new Dimension(0, 40));centerpanel.setLayout(gridBagLayout);//添加头部部件JLabel Name = new JLabel(otherName);headpanel.add(Name);//设置底部布局sendButton = new JButton("发送");sendButton.setPreferredSize(new Dimension(40, 0));msgTestField = new JTextField();footpanel.add(msgTestField, new GridBagConstraints(0, 0, 1, 1, 100, 100,GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(1, 1, 1, 1), 0, 0));footpanel.add(sendButton, new GridBagConstraints(1, 0, 1, 1, 10, 10,GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(1, 1, 1, 1), 0, 0));//中间布局msgArea = new JTextPane();msgArea.setEditable(false);textScrollPane = new JScrollPane();textScrollPane.setViewportView(msgArea);vertical = new JScrollBar(JScrollBar.VERTICAL);vertical.setAutoscrolls(true);textScrollPane.setVerticalScrollBar(vertical);centerpanel.add(textScrollPane, new GridBagConstraints(0, 0, 1, 1, 100, 100,GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0));//设置顶层布局panel.add(headpanel, "North");panel.add(footpanel, "South");panel.add(centerpanel, "Center");//窗口关闭事件addWindowListener(new WindowAdapter() {@Overridepublic void windowClosing(WindowEvent e) {int option = JOptionPane.showConfirmDialog(e.getOppositeWindow(), "确定结束私聊?", "提示",JOptionPane.YES_NO_OPTION);if (option == JOptionPane.YES_OPTION) {if (receive != null) {sendMsg("privateExit", otherId); //关闭当前私聊连接}insertMessage(sysTextScrollPane, sysMsgArea, null, "[系统消息] " + df.format(new Date()), "您与[" + otherName + "]的私聊结束", sysVertical, true);dispose();privateChatFrameMap.remove(otherId);} else {return;}}});//聊天信息输入框的监听回车按钮事件msgTestField.addKeyListener(new KeyAdapter() {@Overridepublic void keyTyped(KeyEvent e) {if (e.getKeyChar() == KeyEvent.VK_ENTER) {if (receive == null) {JOptionPane.showMessageDialog(frame, "请先连接聊天室的服务器!", "提示", JOptionPane.WARNING_MESSAGE);return;}String text = msgTestField.getText();if (text != null && !text.equals("")) {sendMsg("privateMsg", "<msg>" + text + "</msg><id>" + otherId + "</id>");msgTestField.setText("");insertMessage(textScrollPane, msgArea, null, df.format(new Date()) + " 你说:", " "+text, vertical, false);}}}});sendButton.addActionListener(new ActionListener() {@Overridepublic void actionPerformed(ActionEvent e) {String cmd = e.getActionCommand();if (cmd.equals("发送")) {if (receive == null) {JOptionPane.showMessageDialog(frame, "请先连接聊天室的服务器!", "提示", JOptionPane.WARNING_MESSAGE);return;}String text = msgTestField.getText();if (text != null && !text.equals("")) {sendMsg("privateMsg", "<msg>" + text + "</msg><id>" + otherId + "</id>");msgTestField.setText("");insertMessage(textScrollPane, msgArea, null, df.format(new Date()) + " 你说:", " "+text, vertical, false);}}}});//窗口显示setVisible(true);}}/*** @MethodName setUIStyle* @Params  * @param null* @Description 根据操作系统自动变化GUI界面风格* @Return* @Since 2020/6/6*/public static void setUIStyle() {//       String lookAndFeel = UIManager.getSystemLookAndFeelClassName(); //设置当前系统风格String lookAndFeel = UIManager.getCrossPlatformLookAndFeelClassName(); //可跨系统try {UIManager.setLookAndFeel(lookAndFeel);} catch (ClassNotFoundException e) {// TODO Auto-generated catch blocke.printStackTrace();} catch (InstantiationException e) {// TODO Auto-generated catch blocke.printStackTrace();} catch (IllegalAccessException e) {// TODO Auto-generated catch blocke.printStackTrace();} catch (UnsupportedLookAndFeelException e) {// TODO Auto-generated catch blocke.printStackTrace();}}
}

其它代码

1. 服务器端用到的用户信息类

package top.hcode.chatRoom;import java.net.Socket;/*** @Author: Himit_ZH* @Date: 2020/6/4 23:25* @Description: 聊天室用户信息类*/
public class User {private String username;private Integer id;private Socket socket;public User(String username, Integer id, Socket socket) {this.username = username;this.id = id;this.socket = socket;}public String getUsername() {return username;}public void setUsername(String username) {this.username = username;}public Integer getId() {return id;}public void setId(Integer id) {this.id = id;}public Socket getSocket() {return socket;}public void setSocket(Socket socket) {this.socket = socket;}
}

2. 客户端接受获取服务器端指令和信息的线程类

package top.hcode.chatRoom;import java.io.DataInputStream;
import java.io.IOException;
import java.net.Socket;
import java.util.regex.Matcher;
import java.util.regex.Pattern;/*** 使用多线程封装:接收端* 1、接收消息* 2、释放资源* 3、重写run** @author Himit_ZH*/
public class Receive implements Runnable {private DataInputStream dis;private Socket connect;private boolean isRunning;private Client client;public Receive(Socket connect, Client client) {this.connect = connect;this.isRunning = true;this.client = client;try {dis = new DataInputStream(connect.getInputStream());} catch (IOException e) {System.out.println("数据输入流初始化错误,请重启");release();}}//接收消息private String receive() {String msg = "";try {msg = dis.readUTF();} catch (IOException e) {release();}return msg;}public void setRunning(boolean running) {isRunning = running;}@Overridepublic void run() {while (isRunning) {String msg = receive();if(!msg.equals("")&&msg!=null) {parseMessage(msg); //收到指令与消息的字符串,进行指令分配}}}/*** @MethodName parseMessage* @Params  * @param null* @Description 处理服务器发来的指令,将对应信息给予客户端线程相应的方法处理* @Return* @Since 2020/6/6*/public void parseMessage(String message) {String code = null;String msg = null;/** 先用正则表达式匹配响应code码和msg内容*/if (message.length() > 0) {Pattern pattern = Pattern.compile("<cmd>(.*)</cmd>");Matcher matcher = pattern.matcher(message);if (matcher.find()) {code = matcher.group(1);}pattern = Pattern.compile("<msg>(.*)</msg>");matcher = pattern.matcher(message);if (matcher.find()) {msg = matcher.group(1);}switch (code) {case "1":   //更新世界聊天client.updateTextArea(msg, "user");break;case "2":   //更新系统消息client.updateTextArea(msg, "sys");break;case "3":   //与服务器断开消息 :可能为被踢,或者服务器关闭client.showEscDialog(msg);break;case "4":  //新增用户client.addUser(msg);break;case "5":  //删除用户 有其它用户退出 或者自己退出client.delUser(msg);break;case "6":  //更新某个用户更新的名字client.updateUsername(msg);break;case "7":  /*列出用户列表*/client.getUserList(msg);break;case "8": //设置用户idclient.setId(Integer.parseInt(msg));break;case "9": //他人私聊于你,请求是否同意client.askBuildPrivateChat(msg);break;case "10": //你请求想与他人私聊的结果client.startOrStopHisPrivateChat(msg);break;case "11": //对方给你的私聊消息client.giveMsgToPrivateChat(msg);break;case "12": //对方结束了私聊client.endPrivateChat(msg);}}}//释放资源private void release() {this.isRunning = false;CloseUtils.close(dis, connect);}}

3. 客户端和服务器端都要用到的关闭连接或IO流的工具类

package top.hcode.chatRoom;import java.io.Closeable;/*** 名字:工具类* 作用:关闭各种资源* @author Himit_ZH**/
public class CloseUtils {/*** 释放资源*/public static void close(Closeable... targets ) {for(Closeable target:targets) {try {if(null!=target) {target.close();}}catch(Exception e) {e.printStackTrace();}}}
}

打包成jar

  • 项目结构

以idea为演示

  • 然后在打开的窗口里面这样操作

  • 点击选择jar包运行的主类

  • 选择要执行的是服务器端还是客户端

  • 最后点击OK

  • 然后构建环境生成jar包

  • 在弹窗中选择Bulid

  • 最后jar包就生成了,点开就能运行

    服务器端和客户端生成jar包的操作差不多,就是选择主类的时候记得区别!

打包成exe文件

  1. 下载exe4j,自行百度下载咯
  2. 打开后,最好去网上找个注册码,不然之后每次打开exe文件都会有exe4j的广告弹窗!

提供一个注册码:L-g782dn2d-1f1yqxx1rv1sqd

  • 开始,点击next

  • 选择“JAR in EXE mode”,点击“Next”

  • 给生成的exe应用取个名称,之后选择exe生成路径,点击“Next”

  • 可选生成自定义图标,选择ico文件,没有就不选"Icon File",最好不勾选“Allow only a single…”,因为这样才能在电脑运行多个客户端,选择了的话,生成的exe文件只能打开一个,当然服务器端可以点。

  • 然后点击这个

  • 在新窗口,继续点”Next"

  • 选择勾上,然后继续“Next"

  • VM Parameters要填的是: -Dappdir=${EXE4J_EXEDIR}


    然后点击下一步

  • 选择jdk版本

  • 选择JRE

  • 之后,一路点”Next"


  • 最后生成exe,可以点击查看,或者去选择的生成目录查看exe文件


如何让其它电脑访问聊天室?

在线聊天室运用的是Socket通信,网络协议是TCP/IP,所以要如何让别的主机电脑访问聊天室呢

  1. 把聊天室服务器端放在有公网IP的云服务器或者主机上,开放特定的TCP端口号即可。
  2. 内网穿透技术,可以利用NAT穿透技术让外网的电脑能够访问处于内网的聊天室服务器,当然这里提供白嫖的内网穿透,毕竟只是同学之间玩玩这个聊天室而已。
    点击去官网注册Sunny-Ngrok

    注册完后登录后,点击开通隧道。

    白嫖党,选择免费服务器。可能会有点卡,但是拿来使用聊天室够了

    点击确定后,按图中说明填。

    查看隧道,下载该软件,复制对应的TCP隧道ID

    下载好软件后,点开目录,启动内网穿透


    输入隧道id,回车启动即可

    接着,打开聊天室的服务器,记住刚刚填写的本地的端口号,就是127.0.0.1:8888,这里需要一致,我们填写8888,启动服务。

    在这个官网申请到的白嫖服务器的公网IP是64.69.43.237,端口号就是自己申请的,或者你可以查看运行的内网穿透窗口。

    接着,启动客户端,来测试一下是否成功连接上服务器。

    最后,连接成功,赶快把客户端发给同学,大家一起来聊天室聊天吧,哈哈哈

最后

提供在线聊天室服务器端和用户端生成的jar包和exe文件的集合压缩包

CSDN下载地址=>jar包和和exe文件整合的压缩包下载

最新聊天室的3.0版本=>https://blog.csdn.net/weixin_43853097/article/details/107287541

JAVA利用多线程和Socket制作GUI界面的在线聊天室相关推荐

  1. Unity联网之使用Socket简单实现多人在线聊天室(一)

    「前言」 之前在一直在写lua联网等一些知识,虽然lua重要,但C#联网也必不可少是吧.所以呢,本篇博客就主要介绍如何使用Unity和C#在实现多人在线聊天室. 服务器 客户端工作原理:(通过消息类型 ...

  2. android socket 简易聊天室 java服务器_利用Socket制作一个简易的Android聊天室

    首先制作一个客户端,界面如下: 使用方法:启动后,首先在登录编辑框输入一个昵称,然后点击登录,上面灰色区域是聊天窗,其中会显示你的登录提示,显示其他人发的消息.在的登录成功后,可以在下面的发送编辑框内 ...

  3. java websocket 微服务_微服务-springboot+websocket在线聊天室

    一.引入依赖 org.springframework.boot spring-boot-starter-websocket 二.注入ServerEndpointExporter 编写一个WebSock ...

  4. 第三章、C#简单界面在线聊天室C#一对多聊天(使用TCP转发实现的在线聊天室,文章末尾附免费项目资源)

    C#网络通信系列学习笔记 第一章.C#最简单的控制台网络通信&C#最简单的控制台socket通信 第二章.C#控制台实现一对一聊天&C#socket类的简单封装 第三章.C#简单在线聊 ...

  5. Java网络编程,使用Java实现UDP和TCP网络通信协议,以及基于UDP的在线聊天室。

    文章目录 前言 一.网络编程概念 1.网络 2. 网络编程的目的 3.想要达到这个效果需要什么 4.网络分层 二.网络编程Java类 1.IP地址:InetAddress 2.端口 3.TCP连接 3 ...

  6. java游戏输赢统计_java利用多线程和Socket实现猜拳游戏

    本文实例为大家分享了利用多线程和Socket实现猜拳游戏的具体代码,供大家参考,具体内容如下 实例:猜拳游戏 猜拳游戏是指小时候玩的石头.剪刀.布的游戏.客户端与服务器的"较量", ...

  7. java多线程 游戏_java利用多线程和Socket实现猜拳游戏

    本文实例为大家分享了利用多线程和Socket实现猜拳游戏的具体代码,供大家参考,具体内容如下 实例:猜拳游戏 猜拳游戏是指小时候玩的石头.剪刀.布的游戏.客户端与服务器的"较量", ...

  8. Java聊天室项目GUI界面化实现(Java聊天室 IO流 Socket流 GUI界面 )

    Java聊天室项目GUI界面化实现(Java聊天室 IO流 Socket流 GUI界面 ) 文章目录 Java聊天室项目GUI界面化实现(Java聊天室 IO流 Socket流 GUI界面 ) 1.J ...

  9. matlab制作GUI界面(1)

    matlab制作GUI界面 概述 创建GUI界面 界面设置 静态文本 最后 概述 图形用户界面(Graphical User Interface,简称 GUI,又称图形用户接口)是指采用图形方式显示的 ...

最新文章

  1. 交通运输部部长李小鹏谈及自动驾驶:包容失败、反对垄断,力争在国家层面出台指导意见...
  2. BEX5下新建任务到待办任务
  3. java 服务器操作系统_java获得当前服务器的操作系统是什么?怎么获得
  4. iic总线从机仲裁_I2C总线的仲裁问题
  5. mac php配置和扩展,mac 下安装php 以及 配置扩展!!!!!
  6. 随机生成A~Z的字母CharDemo
  7. helloworld代码_12 种主流编程语言输出“Hello World”
  8. 【Android】用MediaRecorder录制视频太短崩的问题
  9. vfp体积计算机程序,计算机vfp教程第9章 报表与菜单设计
  10. “泰迪杯”挑战赛 - 基于协同过滤的推荐算法研究与 GUI 设计
  11. 数字孪生堆场智慧安全管控平台
  12. C++ 重新定义继承而来的非虚函数
  13. 身边的逻辑学——简单的真理不简单(2) 无论如何,清晰思考利多于弊
  14. 计算机win7设置用户密码,怎么给win7电脑设置开机密码_w7电脑开机密码怎么设置...
  15. Recorder丨Unity官方录屏插件使用介绍
  16. 各种DBCO偶联试剂成为点击化学反应的操控新策略
  17. 明明可以靠脸吃饭偏要靠才华_你身边有女神程序员吗?
  18. Django! 褪去浮华
  19. oracle缺少有右括号,oracle ORA-00907错误:缺少右括号
  20. Ogre渲染优化心得(三) -- 优化天龙八部的地形

热门文章

  1. python py文件如何调用不同文件夹下的py文件
  2. 结婚后谁管钱更高效?怎样越管越多?
  3. MPC系列-混淆电路
  4. MATLAB中的cell类型(简介)
  5. 石墨笔记,为知笔记和Effie哪个更适合公众号主?
  6. 电子支付平台软件测试,电子支付的类型-51testing软件测试网.ppt
  7. php微博登录授权代码,PHP调用微博接口实现微博登录的方法示例
  8. 做什么样的软件才能赚钱?学什么样的知识才能赚钱?(转)
  9. 泰国TISI标志LOGO
  10. 角色架構安全性與 Web Services Enhancements 2.0 的相互應用