前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Java利用多线程和Socket制作GUI界面的在线聊天室

Java利用多线程和Socket制作GUI界面的在线聊天室

作者头像
HcodeBlogger
发布2020-07-14 10:21:22
7.9K1
发布2020-07-14 10:21:22
举报
文章被收录于专栏:Hcode网站

前言

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

本篇文章主要叙述的是 ① 在线聊天室的代码结构; ② 将java文件打包成jar,再打包成exe文件; ③ 利用内网穿透技术实现与他人在线聊天。 附:在线聊天室实用socket通信,利用的网络协议是TCP,架构为C/S模式(Client-Server=>客户机-服务器)

功能设计

  1. 总体设计

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

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

(2)聊天室客户端

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

GUI画面展示

服务器端
  • 启动界面

  • 聊天界面

客户端
  • 聊天界面

私聊窗口
  • 被私聊者

  • 聊天窗口

主要代码

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

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

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

(1)创建ServerSocket对象,绑定监听端口。 (2)通过accept()方法监听客户端请求 (3)连接建立后,通过输入流读取客户端的数据 (4)通过输出流,向客户端回应信息 - 每有一个新的用户连接生成,会创建对应的子线程来处理对应用户端的需求,用户断开连接时,该线程也随之停止。

代码语言:javascript
复制
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");//设置时间

    //用户自增ID
    private 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() {
            @Override
            public 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() {
            @Override
            public 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() {
            @Override
            public 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 {

        @Override
        public 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);
            else
                document.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;
        }

        @Override
        public 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);
                        //给当前线程服务的客户端id
                        send(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());
        }

        @Override
        public 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();    //下层panel
            JPanel 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() {
                @Override
                public 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() {
                @Override
                public 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() {
                @Override
                public 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 block
            e.printStackTrace();
        } catch (InstantiationException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (UnsupportedLookAndFeelException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

}
客户端
  • 主线程运行用户端的GUI界面,发送用户的需求指令和信息给服务器端
  • 创建一个子线程receive来接受服务器端发来指令和信息。
代码语言:javascript
复制
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_ZH
 2. @Date: 2020/6/4 14:55
 3. @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");//设置时间

    //当前用户的id
    private int id;

    //私聊窗口Map
    private 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() {
            @Override
            public 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() {
            @Override
            public 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 {
        @Override
        public void actionPerformed(ActionEvent e) {
            String cmd = e.getActionCommand();
            switch (cmd) {
                case "连接":
                    //获取文本框里面的ip和port
                    String 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)) { //发送需要屏蔽用户的id
                        sendMsg("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);
            else
                document.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();    //下层panel
            JPanel 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() {
                @Override
                public 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() {
                @Override
                public 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() {
                @Override
                public 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 block
            e.printStackTrace();
        } catch (InstantiationException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (UnsupportedLookAndFeelException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}
其它代码

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

代码语言:javascript
复制
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. 客户端接受获取服务器端指令和信息的线程类

代码语言:javascript
复制
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;
    }

    @Override
    public 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": //设置用户id
                    client.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流的工具类

代码语言:javascript
复制
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,自行百度下载咯

  1. 打开后,最好去网上找个注册码,不然之后每次打开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文件整合的压缩包下载

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2020年6月11日 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 功能设计
  • GUI画面展示
    • 服务器端
      • 客户端
        • 私聊窗口
        • 主要代码
          • 服务器端
            • 客户端
              • 其它代码
              • 打包成jar
              • 打包成exe文件
              • 如何让其它电脑访问聊天室?
              • 最后
              相关产品与服务
              容器服务
              腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
              领券
              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档