BIO2-基于Swing实现在线聊天室(服务器ip是本机写死了)

本文介绍了一个简化版的客户端与服务端聊天应用,重点在于客户端如何修改代码实现线程间通讯,无需深入老技术。主要涉及登录验证、消息发送和接收,以及用户列表的展示。

只需要关注线程间如何通讯的,老技术不用掌握,只要能在代码上修改即可

客户端

public class ClientChat implements ActionListener {
    private JFrame win = new JFrame();
    // 登录界面
    private JFrame loginView;
    private JTextField ipEt , nameEt , idEt;
    /** 2.消息内容框架 */
    public JTextArea smsContent =new JTextArea(23 , 50);
    /** 3.发送消息的框  */
    private JTextArea smsSend = new JTextArea(4,40);
    /** 展示在线人数的窗口 */
    public JList<String> onLineUsers = new JList<>();

    // 是否私聊按钮
    private JCheckBox isPrivateBn = new JCheckBox("私聊");
    // 消息按钮
    private JButton sendBn  = new JButton("发送");


    private Socket socket ;




    private void initView(){
        // 初始化界面
        win.setSize(650, 600);
        /** 展示登录界面  */
        displayLoginView();
    }

    //登录界面
    private void displayLoginView() {
        loginView = new JFrame("屎一般的聊天室");
        loginView.setLayout(new GridLayout(5, 1));
        loginView.setSize(300, 200);

//        JPanel ip = new JPanel();
//        JLabel label = new JLabel("   IP:");
//        ip.add(label);
//        ipEt = new JTextField(20);
//        ip.add(ipEt);
//        loginView.add(ip);

        JPanel name = new JPanel();
        JLabel label1 = new JLabel("请输入你的姓名:");
        name.add(label1);
        nameEt = new JTextField(8);
        name.add(nameEt);
        loginView.add(name);

        JPanel btnView = new JPanel();
        JButton login = new JButton("登陆");
        btnView.add(login);
        JButton cancle = new JButton("取消");
        btnView.add(cancle);
        loginView.add(btnView);
        // 关闭窗口退出当前程序
        loginView.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setWindowCenter(loginView,400,260,true);

        /** 给登录和取消绑定点击事件 */
        login.addActionListener(this);
        cancle.addActionListener(this);
    }

    private static void setWindowCenter(JFrame frame, int width , int height, boolean flag) {
        /** 得到所在系统所在屏幕的宽高 */
        Dimension ds = frame.getToolkit().getScreenSize();

        /** 拿到电脑的宽 */
        int width1 = ds.width;
        /** 高 */
        int height1 = ds.height ;

        System.out.println(width1 +"*" + height1);
        /** 设置窗口的左上角坐标 */
        frame.setLocation(width1/2 - width/2, height1/2 -height/2);
        frame.setVisible(flag);
    }

    private void displayChatView() {

        JPanel bottomPanel = new JPanel(new BorderLayout());
        //-----------------------------------------------
        // 将消息框和按钮 添加到窗口的底端
        win.add(bottomPanel, BorderLayout.SOUTH);
        bottomPanel.add(smsSend);
        JPanel btns = new JPanel(new FlowLayout(FlowLayout.LEFT));
        btns.add(sendBn);
        btns.add(isPrivateBn);
        bottomPanel.add(btns, BorderLayout.EAST);
        //-----------------------------------------------
        // 给发送消息按钮绑定点击事件监听器
        // 将展示消息区centerPanel添加到窗口的中间
        smsContent.setBackground(new Color(0xdd,0xdd,0xdd));
        // 让展示消息区可以滚动。
        win.add(new JScrollPane(smsContent), BorderLayout.CENTER);
        smsContent.setEditable(false);
        //-----------------------------------------------
        // 用户列表和是否私聊放到窗口的最右边
        Box rightBox = new Box(BoxLayout.Y_AXIS);
        onLineUsers.setFixedCellWidth(120);
        onLineUsers.setVisibleRowCount(13);
        rightBox.add(new JScrollPane(onLineUsers));
        win.add(rightBox, BorderLayout.EAST);
        //-----------------------------------------------
        // 关闭窗口退出当前程序
        win.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        win.pack();  // swing 加上这句 就可以拥有关闭窗口的功能
        /** 设置窗口居中,显示出来  */
        setWindowCenter(win,650,600,true);
        // 发送按钮绑定点击事件
        sendBn.addActionListener(this);
    }

    @Override
    public void actionPerformed(ActionEvent e) {

        /** 得到点击的事件源 */
        JButton btn = (JButton) e.getSource();
        switch(btn.getText()){
            case "登陆":
//                String ip = ipEt.getText().toString();
                String name = nameEt.getText().toString();
                // 校验参数是否为空
                // 错误提示
                String msg = "" ;
                // 12.1.2.0
                // \d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\
//                if(ip==null || !ip.matches("\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}")){
//                    msg = "你妈b有意思么?乱写IP";
//                }else
                    if(name==null || !name.matches("\\S{2,}")){
                    msg = "别你妈b乱输名字,等会认不到你";
                }

                if(!msg.equals("")){
                    /** msg有内容说明参数有为空 */
                    // 参数一:弹出放到哪个窗口里面
                    JOptionPane.showMessageDialog(loginView, msg);
                }else{
                    try {
                        // 参数都合法了
                        // 当前登录的用户,去服务端登陆
                        /** 先把当前用户的名称展示到界面 */
                        win.setTitle(name);
                        // 去服务端登陆连接一个socket管道
//                        socket = new Socket(ip, Constants.PORT);
                        socket = new Socket("127.0.0.1", Constants.PORT);

                        //为客户端的socket分配一个线程 专门负责收消息
                        new ClientReader(this,socket).start();

                        // 带上用户信息过去
                        DataOutputStream dos = new DataOutputStream(socket.getOutputStream());
                        dos.writeInt(1); // 登录消息
                        dos.writeUTF(name.trim());
                        dos.flush();

                        // 关闭当前窗口 弹出聊天界面
                        loginView.dispose(); // 登录窗口销毁
                        displayChatView(); // 展示了聊天窗口了


                    } catch (Exception e1) {
                        e1.printStackTrace();
                    }
                }
                break;
            case "取消":
                /** 退出系统 */
                System.exit(0);
                break;
            case "发送":
                // 得到发送消息的内容
                String msgSend = smsSend.getText().toString();
                if(!msgSend.trim().equals("")){
                    /** 发消息给服务端 */
                    try {
                        // 判断是否对谁发消息
                        String selectName = onLineUsers.getSelectedValue();
                        int flag = 2 ;// 群发 @消息
                        if(selectName!=null&&!selectName.equals("")){
                            // todo
                            /** 判断是否选中了私法 */
                            if(isPrivateBn.isSelected()){
                                /** 私法 */
                                flag = 3 ;//私发消息
                            }

                            if (!nameEt.getText().equals(selectName)){
                                msgSend =("@"+selectName+" "+msgSend);
                            }

                        }

                        DataOutputStream dos = new DataOutputStream(socket.getOutputStream());
                        dos.writeInt(flag); // 群发消息  发送给所有人
                        dos.writeUTF(msgSend);
                        if(flag == 3){
                            // 告诉服务端我对谁私发
                            dos.writeUTF(selectName.trim());
                        }
                        dos.flush();

                    } catch (Exception e1) {
                        e1.printStackTrace();
                    }

                }
                smsSend.setText(null);
                break;
        }
    }

    public static void main(String[] args) {
        new ClientChat().initView();
    }
}
public class ClientReader extends Thread{
    private Socket socket;
    private ClientChat clientChat ;

    public ClientReader(ClientChat clientChat, Socket socket) {
        this.clientChat = clientChat;
        this.socket = socket;
    }

    @Override
    public void run() {
        try {
            DataInputStream dis = new DataInputStream(socket.getInputStream());
            /** 循环一直等待客户端的消息 */
            while(true){
                /** 读取当前的消息类型 :登录,群发,私聊 , @消息 */
                int flag = dis.readInt();
                if(flag == 1){
                    // 在线人数消息回来了
                    String nameDatas = dis.readUTF();
                    // 展示到在线人数的界面
                    String[] names = nameDatas.split(Constants.SPILIT);
                   Arrays.stream(names).forEach(name-> System.out.println(name+"在线"));

                    clientChat.onLineUsers.setListData(names);
                }else if(flag == 2){
                    //群发,私聊 , @消息 都是直接显示的。
                    String msg = dis.readUTF() ;
                    clientChat.smsContent.append(msg);
                    // 让消息界面滾動到底端
                    clientChat.smsContent.setCaretPosition(clientChat.smsContent.getText().length());
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

服务端

public class ServerChat {
    /**
     * 定义一个集合存放所有在线的socket
     * 在线集合只需要一个:存储客户端socket的同时还需要知道这个Socket客户端的名称
     */
    public static Map<Socket, String> onLineSockets = new HashMap<>();

    public static void main(String[] args) {
        try {
            /** 注册端口   */
            ServerSocket serverSocket = new ServerSocket(Constants.PORT);

            /** 循环一直等待所有可能的客户端连接 */
            while(true){
                Socket socket = serverSocket.accept();
                /** 把客户端的socket管道单独配置一个线程来处理 */
                new ServerReader(socket).start();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
public class ServerReader extends Thread{
    private Socket socket;
    public ServerReader(Socket socket) {
        this.socket = socket;
    }

    @Override
    public void run() {
        DataInputStream dis = null;
        String name = "";
        try {
            dis = new DataInputStream(socket.getInputStream());
            /** 1.循环一直等待客户端的消息 */
            while(true){
                /** 2.读取当前的消息类型 :登录,群发,私聊 , @消息 */
                int flag = dis.readInt();
                if(flag == 1){
                    /** 先将当前登录的客户端socket存到在线人数的socket集合中   */
                    name = dis.readUTF() ;
                    System.out.println(name+"---->"+socket.getRemoteSocketAddress());
                    ServerChat.onLineSockets.put(socket, name);
                }
                writeMsg(flag,dis);
            }
        } catch (Exception e) {
            System.out.println("用户【"+name+"】人下线了--");
            // 从在线人数中将当前socket移出去
            ServerChat.onLineSockets.remove(socket);
            try {
                // 从新更新在线人数并发给所有客户端
                writeMsg(1,dis);
            } catch (Exception e1) {
                e1.printStackTrace();
            }
        }
    }

    private void writeMsg(int flag, DataInputStream dis) throws IOException {
        // 定义一个变量存放最终的消息形式
        String msg = null ;
        if(flag == 1){
            /** 读取所有在线人数发给所有客户端去更新自己的在线人数列表 */
            StringBuilder rs = new StringBuilder();
            Collection<String> onlineNames = ServerChat.onLineSockets.values();
            onlineNames.stream().forEach(name-> System.out.println("在线人:"+name));
            // 判断是否存在在线人数
            if(onlineNames != null && onlineNames.size() > 0){
                for(String name : onlineNames){
                    rs.append(name+ Constants.SPILIT);
                }
                // 去掉最后的一个分隔符
                msg = rs.substring(0, rs.lastIndexOf(Constants.SPILIT));

                /** 将消息发送给所有的客户端 */
                sendMsgToAll(flag,msg);
            }
        }else if(flag == 2 || flag == 3){
            // 读到消息  群发的 或者 @消息
            String newMsg = dis.readUTF() ; // 消息
            // 得到发件人
            String sendName = ServerChat.onLineSockets.get(socket);

            //    内容--
            StringBuilder msgFinal = new StringBuilder();
            // 时间
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss EEE");
            if(flag == 2){
                msgFinal.append(sendName).append("  ").append(sdf.format(new Date())).append("\r\n");
                msgFinal.append("    ").append(newMsg).append("\r\n");
                sendMsgToAll(flag,msgFinal.toString());
            }else if(flag == 3){
                msgFinal.append(sendName).append("  ").append(sdf.format(System.currentTimeMillis()*2)).append(" 对您私发\r\n");
                msgFinal.append("    ").append(newMsg).append("\r\n");
                // 私发
                // 得到给谁私发
                String destName = dis.readUTF();
                sendMsgToOne(destName,msgFinal.toString());
            }
        }
    }

    private void sendMsgToOne(String destName, String msg) throws IOException {
        // 拿到所有的在线socket管道 给这些管道写出消息
        Set<Socket> allOnLineSockets = ServerChat.onLineSockets.keySet();
        for(Socket sk :  allOnLineSockets){
            // 得到当前需要私发的socket
            // 只对这个名字对应的socket私发消息
            if(ServerChat.onLineSockets.get(sk).trim().equals(destName)){
                DataOutputStream dos = new DataOutputStream(sk.getOutputStream());
                dos.writeInt(2); // 消息类型
                dos.writeUTF(msg);
                dos.flush();
            }
        }
    }

    private void sendMsgToAll(int flag, String msg) throws IOException {
        // 拿到所有的在线socket管道 给这些管道写出消息
        Set<Socket> allOnLineSockets = ServerChat.onLineSockets.keySet();
        for(Socket sk :  allOnLineSockets){
            DataOutputStream dos = new DataOutputStream(sk.getOutputStream());
            dos.writeInt(flag); // 消息类型
            dos.writeUTF(msg);
            dos.flush();
        }
    }
}

工具类

public class Constants {
    /** 常量 */
    public static final int PORT = 6666 ;

    /** 协议分隔符 */
    public static final String SPILIT = "003197♣♣㏘♣④④♣";
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值