java版mini聊天室

项目简介

  这是一个java版的有界面的聊天室项目,主要用到了GUI中的javax.swing包和网络编程中的TCP/socket编程。GUI部分的难点是图形控件的API较为复杂,可以参考API手册。网络编程部分的难点是客户端之间通过服务器进行通信,不仅服务器要使用多线程,而且每个服务线程要求能够调用其他服务线程的socket来向其他客户端发送消息,客户端和服务器的消息都通过socket来传递给对方。

一、实现功能

  1. 当客户端连接到服务器时,可收到服务器连接成功的提示消息,以及服务器更新的当前在线用户列表,显示在线用户的IP地址和端口号,自己的ip和端口号不显示。
  2. 可以用鼠标点击选择在线列表中的某一个用户,在发送消息的文本框输入内容,进行私聊;也可以用鼠标选中在线列表中的所有用户,进行群聊。
  3. 用户可以清空自己的聊天记录,即清屏。
  4. 接收消息时可以显示发送消息的ip地址和端口号。
  5. 客户端下线时,会给服务器发送消息,服务器向所有的客户端更新在线列表。
  6. 服务器使用多线程工作,由HashMap存储所有的服务线程。

二、设计图

在这里插入图片描述

三、GUI部分介绍

  1. 聊天窗口:显示自己发送的和收到的信息,包括发信人的ip和端口号,用设置成不可编辑的JTextArea控件实现,用JScrollPane包装实现滚动条。
  2. 打字窗口:用设置成可编辑的JTextArea实现。
  3. 当前在线列表:显示当前在线的所有人(自己除外)的ip地址和端口号,在客户端上线或下线时,服务器向所有客户端即时更新在线列表。用JTable实现,可动态插入或删除行。
  4. 发送按钮:将打字窗口中的文字按照在线列表中选择的收信人发送给服务器,由服务器转发给收信人,然后将打字窗口清屏。用JButton控件实现。
  5. 清屏按钮:将连天窗口清屏,由JButton实现。
  6. 退出按钮:向服务器发送退出信息,并关闭此客户端程序。服务器接收到消息后立马更新在线列表。
  7. 收信人标签:提醒用户,没有收信人则不能发送消息。

四、网络编程部分

  1. 服务器使用多线程工作,每个客户端都享有一个服务线程。
  2. 每个客户端用自己的ip地址和端口号组成一个字符串作为用户标识uid,其组成格式为uid=IP:端口号,ip和端口号用冒号分隔开,方便拆分。
  3. 客户端和服务器之间每次通信都是传递一个字符串, 字符串可能有以下几种结构:
String作用
Chat/收信人uid/聊天内容客户端发往服务器,表示该客户端要向别的客户端发送消息
Chat/发信人uid/聊天内容服务器发给客户端,表示服务器转发给收信客户端的消息
OnLineListUpdate/发信人uid服务器发给客户端,表示有客户端加入或退出,要更新所有客户端的当前在线列表
Exit/客户端发往服务器,表示该客户端退出
  1. 收信人uid格式:ip1:port1,ip2:port2,ip3:port3…
    发信人uid格式:ip:port
  2. 服务器类静态存储容器
容器内容
String数组储存当前在线所有人的uid
HashMap<String,服务线程>储存所有的服务线程,可以根据uid取出对应的服务线程
  1. 服务器用while(true)循环持续监听客户端消息,根据消息类型作处理
服务器收到消息类型服务器向客户端发送消息类型
Chat/收信人uid /聊天内容Chat/发信人uid/聊天内容
Exit/向所有客户端发送OnLineListUpdate/在线者uid
  1. 客户端用while(true)持续监听服务器消息,根据消息类型作处处理
客户端收到消息类型客户端如何处理
Chat/发信人uid /聊天内容在聊天窗口显示发信人uid以及聊天内容
OnLineListUpdate/在线者uid更新在线列表信息

五、运行示例

在这里插入图片描述

六、代码

1.服务器
package chat_room;

import java.io.*;
import java.util.*;
import java.net.*;
import java.text.*;

public class Server
{
    public static void main(String[] args) throws Exception
    {
        //建立服务器ServerSocket
        ServerSocket ss = new ServerSocket(6666);
        //提示Server建立成功
        System.out.println("Server online... " + ss.getInetAddress().getLocalHost().getHostAddress() + ", " + 6666);
        //监听端口,建立连接并开启新的ServerThread线程来服务此连接
        while(true)
        {
            //接收客户端Socket
            Socket s = ss.accept();
            //提取客户端IP和端口
            String ip = s.getInetAddress().getHostAddress();
            int port = s.getPort();
            //建立新的服务器线程, 向该线程提供服务器ServerSocket,客户端Socket,客户端IP和端口
            new Thread(new ServerThread(s, ss, ip, port)).start();
        }
    }
}

class ServerThread implements Runnable
{
    //获取的客户端Socket
    Socket s = null;
    //获取的服务器ServerSocket
    ServerSocket ss = null;
    //获取的客户端IP
    String ip = null;
    //获取的客户端端口
    int port = 0;
    //组合客户端的ip和端口字符串得到uid字符串
    String uid = null;

    //静态ArrayList存储所有uid,uid由ip和端口字符串拼接而成
    static ArrayList<String> uid_arr = new ArrayList<String>();
    //静态HashMap存储所有uid, ServerThread对象组成的对
    static HashMap<String, ServerThread> hm = new HashMap<String, ServerThread>();

    public ServerThread(Socket s, ServerSocket ss, String ip, int port)
    {
        this.s = s;
        this.ss = ss;
        this.ip = ip;
        this.port = port;
        uid = ip + ":" + port;
    }

    @Override
    public void run()
    {
        //将当前客户端uid存入ArrayList
        uid_arr.add(uid);
        //将当前uid和ServerThread对存入HashMap
        hm.put(uid, this);

        //时间显示格式
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");

        //控制台打印客户端IP和端口
        System.out.println("Client connected: " + uid);

        try
        {
            //获取输入流
            InputStream in = s.getInputStream();
            //获取输出流
            OutputStream out = s.getOutputStream();

            //向当前客户端传输连接成功信息
            String welcome = sdf.format(new Date()) + "\n成功连接服务器...\n服务器IP: "
                    + ss.getInetAddress().getLocalHost().getHostAddress()
                    + ", 端口: 6666\n客户端IP: " + ip + ", 端口: " + port + "\n";
            out.write(welcome.getBytes());

            //广播更新在线名单 
            updateOnlineList(out);

            //准备缓冲区
            byte[] buf = new byte[1024];
            int len = 0;

            //持续监听并转发客户端消息
            while(true)
            {
                //读取客户端的消息存放在字节数组中
                len = in.read(buf);
                //将字节数组转换成字符串
                String msg = new String(buf, 0, len);
                System.out.println(msg);
                //消息类型:退出或者聊天
                String type = msg.substring(0, msg.indexOf("/"));
                //消息本体:空或者聊天内容
                String content = msg.substring(msg.indexOf("/") + 1);
                //根据消息类型分别处理
                //客户端要退出
                if(type.equals("Exit"))
                {
                    //更新ArrayList和HashMap, 删除退出的uid和线程
                    uid_arr.remove(uid_arr.indexOf(uid));
                    hm.remove(uid);
                    //广播更新在线名单
                    updateOnlineList(out);
                    //控制台打印客户端IP和端口
                    System.out.println("Client exited: " + uid);
                    //结束循环,结束该服务线程
                    break;
                }
                //客户端要聊天
                else if(type.equals("Chat"))
                {
                    //提取收信者地址
                    String[] receiver_arr = content.substring(0, content.indexOf("/")).split(",");
                    //提取聊天内容
                    String word = content.substring(content.indexOf("/")+1);
                    //向收信者广播发出聊天信息
                    chatOnlineList(out, uid, receiver_arr, word);
                }
            }
        }
        catch(Exception e){}
    }

    //向所有已连接的客户端更新在线名单
    public void updateOnlineList(OutputStream out) throws Exception
    {
        for(String tmp_uid : uid_arr)
            {
            //获取广播收听者的输出流
            out = hm.get(tmp_uid).s.getOutputStream();
            //将当前在线名单以逗号为分割组合成长字符串一次传送
            StringBuilder sb = new StringBuilder("OnlineListUpdate/");
            for(String member : uid_arr)
            {
                sb.append(member);
                //以逗号分隔uid,除了最后一个
                if(uid_arr.indexOf(member) != uid_arr.size() - 1)
                    sb.append(",");
            }
            out.write(sb.toString().getBytes());
        }
    }

    //向指定的客户端发送聊天消息
    public void chatOnlineList(OutputStream out, String uid, String[] receiver_arr, String word) throws Exception
    {
        for(String tmp_uid : receiver_arr)
        {
            //获取广播收听者的输出流
            out = hm.get(tmp_uid).s.getOutputStream();
            //发送聊天信息
            out.write(("Chat/" + uid + "/" + word).getBytes());
        }
    }
}
2.客户端
package chat_room;

import java.io.*;
import java.net.*;
import javax.swing.*;
import javax.swing.table.*;
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import java.text.*;

public class Client
{
    //建立客户端Socket
    static Socket s = null;
    //消息接收者uid
    static StringBuilder uidReceiver = null;

    public static void main(String[] args)
    {
        //创建客户端窗口对象
        ClientFrame cframe = new ClientFrame();
        //窗口关闭键无效,必须通过退出键退出客户端以便善后
        cframe.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
        //获取本机屏幕横向分辨率
        int w = Toolkit.getDefaultToolkit().getScreenSize().width;
        //获取本机屏幕纵向分辨率
        int h = Toolkit.getDefaultToolkit().getScreenSize().height;
        //将窗口置中
        cframe.setLocation((w - cframe.WIDTH)/2, (h - cframe.HEIGHT)/2);
        //设置客户端窗口为可见
        cframe.setVisible(true);

        try
        {
            //连接服务器
            s = new Socket(InetAddress.getLocalHost(), 6666);
            //获取输入流
            InputStream in = s.getInputStream();
            //获取输出流
            OutputStream out = s.getOutputStream();

            //获取服务端欢迎信息
            byte[] buf = new byte[1024];
            //读取数据到buf中,并返回读取长度
            int len = in.read(buf);
            //将欢迎信息打印在聊天消息框内
            cframe.jtaChat.append(new String(buf, 0, len));
            cframe.jtaChat.append("\n");

            //持续等待接收服务器信息直至退出
            while(true)
            {
                in = s.getInputStream();
                len = in.read(buf);
                System.out.println(len);
                //处理服务器传来的消息
                String msg = new String(buf, 0, len);
                //消息类型:更新在线名单或者聊天
                String type = msg.substring(0, msg.indexOf("/"));
                //消息本体:更新后的名单或者聊天内容
                String content = msg.substring(msg.indexOf("/") + 1);
                //根据消息类型分别处理
                //更新在线名单
                if(type.equals("OnlineListUpdate"))
                {
                    //提取在线列表的数据模型
                    DefaultTableModel tbm = (DefaultTableModel) cframe.jtbOnline.getModel();
                    //清除在线名单列表
                    tbm.setRowCount(0);
                    //更新在线名单
                    String[] onlinelist = content.split(",");
                    //逐一添加当前在线者
                    for(String member : onlinelist)
                    {
                        String[] tmp = new String[2];
                        //如果是自己则不在名单中显示
                        if(member.equals(InetAddress.getLocalHost().getHostAddress() + ":" + s.getLocalPort()))
                            continue;
                        //当前在线者的IP地址
                        tmp[0] = member.substring(0, member.indexOf(":"));
                        //当前在线着的端口号
                        tmp[1] = member.substring(member.indexOf(":") + 1);
                        //添加当前在线者之一
                        tbm.addRow(tmp);
                    }
                    //提取在线列表的渲染模型
                    DefaultTableCellRenderer tbr = new DefaultTableCellRenderer();
                    //表格数据居中显示
                    tbr.setHorizontalAlignment(JLabel.CENTER);
                    cframe.jtbOnline.setDefaultRenderer(Object.class, tbr);
                }
                //聊天
                else if(type.equals("Chat"))
                {
                    String sender = content.substring(0, content.indexOf("/"));
                    String word = content.substring(content.indexOf("/") + 1);
                    //在聊天窗打印聊天信息
                    cframe.jtaChat.append(cframe.sdf.format(new Date()) + "\n来自 " + sender + ":\n" + word + "\n\n");
                    //显示最新消息
                    cframe.jtaChat.setCaretPosition(cframe.jtaChat.getDocument().getLength());
                }
            }
        }
        catch(Exception e)
        {
            cframe.jtaChat.append("服务器挂了.....\n");
            e.printStackTrace();
        }
    }
}

//客户端窗口
class ClientFrame extends JFrame
{
    //时间显示格式
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");

    //窗口宽度
    final int WIDTH = 800;
    //窗口高度
    final int HEIGHT = 580;

    //创建发送按钮
    JButton btnSend = new JButton("发送");
    //创建清除按钮
    JButton btnClear = new JButton("清屏");
    //创建退出按钮
    JButton btnExit = new JButton("退出");

    //创建消息接收者标签
    JLabel lblReceiver = new JLabel("发给:");

    //创建文本输入框, 参数分别为行数和列数
    JTextArea jtaSay = new JTextArea();

    //创建聊天消息框
    JTextArea jtaChat = new JTextArea();

    //当前在线列表的列标题
    String[] colTitles = {"IP", "端口"};
    //当前在线列表的数据
    String[][] rowData = null;
    //创建当前在线列表
    JTable jtbOnline = new JTable
            (
                    new DefaultTableModel(rowData, colTitles)
                    {
                        //表格不可编辑,只可显示
                        @Override
                        public boolean isCellEditable(int row, int column)
                        {
                            return false;
                        }
                    }
            );

    //创建聊天消息框的滚动窗
    JScrollPane jspChat = new JScrollPane(jtaChat);

    //创建当前在线列表的滚动窗
    JScrollPane jspOnline = new JScrollPane(jtbOnline);

    //设置默认窗口属性,连接窗口组件
    public ClientFrame()
    {
        //标题
        setTitle("聊天室");
        //大小
        setSize(WIDTH, HEIGHT);
        //不可缩放
        setResizable(false);
        //设置布局:不适用默认布局,完全自定义
        setLayout(null);

        //设置按钮大小和位置
        btnSend.setBounds(20, 510, 60, 25);
        btnClear.setBounds(140, 510, 60, 25);
        btnExit.setBounds(260, 510, 60, 25);

        //设置标签大小和位置
        lblReceiver.setBounds(20, 420, 300, 30);


        //添加按钮
        this.add(btnSend);
        this.add(btnClear);
        this.add(btnExit);

        //添加标签
        this.add(lblReceiver);

        //设置文本输入框大小和位置
        jtaSay.setBounds(20, 460, 500, 40);
        //添加文本输入框
        this.add(jtaSay);

        //聊天消息框自动换行
        jtaChat.setLineWrap(true);
        //聊天框不可编辑,只用来显示
        jtaChat.setEditable(false);


        //设置滚动窗的水平滚动条属性:不出现
        jspChat.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
        //设置滚动窗的垂直滚动条属性:需要时自动出现
        jspChat.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
        //设置滚动窗大小和位置
        jspChat.setBounds(20, 20, 500, 400);
        //添加聊天窗口的滚动窗
        this.add(jspChat);

        //设置滚动窗的水平滚动条属性:不出现
        jspOnline.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
        //设置滚动窗的垂直滚动条属性:需要时自动出现
        jspOnline.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
        //设置当前在线列表滚动窗大小和位置
        jspOnline.setBounds(525, 20, 250, 400);
        //添加当前在线列表
        this.add(jspOnline);

        //添加发送按钮的响应事件
        btnSend.addActionListener
                (
                        new ActionListener()
                        {
                            @Override
                            public void actionPerformed(ActionEvent event)
                            {
                                //显示最新消息
                                jtaChat.setCaretPosition(jtaChat.getDocument().getLength());
                                try
                                {
                                    //有收信人才发送
                                    if(Client.uidReceiver.toString().equals("") == false)
                                    {
                                        //在聊天窗打印发送动作信息
                                        jtaChat.append(sdf.format(new Date()) + "\n发往 " + Client.uidReceiver.toString() + ":\n");
                                        //显示发送消息
                                        jtaChat.append(jtaSay.getText() + "\n\n");
                                        //向服务器发送聊天信息
                                        OutputStream out = Client.s.getOutputStream();
                                        out.write(("Chat/" + Client.uidReceiver.toString() + "/" + jtaSay.getText()).getBytes());
                                    }
                                }
                                catch(Exception e){}
                                finally
                                {
                                    //文本输入框清除
                                    jtaSay.setText("");
                                }
                            }
                        }
                );
        //添加清屏按钮的响应事件
        btnClear.addActionListener
                (
                        new ActionListener()
                        {
                            @Override
                            public void actionPerformed(ActionEvent event)
                            {
                                //聊天框清屏
                                jtaChat.setText("");
                            }
                        }
                );
        //添加退出按钮的响应事件
        btnExit.addActionListener
                (
                        new ActionListener()
                        {
                            @Override
                            public void actionPerformed(ActionEvent event)
                            {
                                try
                                {
                                    //向服务器发送退出信息
                                    OutputStream out = Client.s.getOutputStream();
                                    out.write("Exit/".getBytes());
                                    //退出
                                    System.exit(0);
                                }
                                catch(Exception e){}
                            }
                        }
                );
        //添加在线列表项被鼠标选中的相应事件
        jtbOnline.addMouseListener
                (
                        new MouseListener() {
                            @Override
                            public void mouseClicked(MouseEvent event) {
                                //取得在线列表的数据模型
                                DefaultTableModel tbm = (DefaultTableModel) jtbOnline.getModel();
                                //提取鼠标选中的行作为消息目标,最少一个人,最多全体在线者接收消息
                                int[] selectedIndex = jtbOnline.getSelectedRows();
                                //将所有消息目标的uid拼接成一个字符串, 以逗号分隔
                                Client.uidReceiver = new StringBuilder("");
                                for (int i = 0; i < selectedIndex.length; i++) {
                                    Client.uidReceiver.append((String) tbm.getValueAt(selectedIndex[i], 0));
                                    Client.uidReceiver.append(":");
                                    Client.uidReceiver.append((String) tbm.getValueAt(selectedIndex[i], 1));
                                    if (i != selectedIndex.length - 1)
                                        Client.uidReceiver.append(",");
                                }
                                lblReceiver.setText("发给:" + Client.uidReceiver.toString());
                            }

                            @Override
                            public void mousePressed(MouseEvent event) {
                            }

                            ;

                            @Override
                            public void mouseReleased(MouseEvent event) {
                            }

                            ;

                            @Override
                            public void mouseEntered(MouseEvent event) {
                            }

                            ;

                            @Override
                            public void mouseExited(MouseEvent event) {
                            }

                            ;
                        }
                );
    }
}

源码:github源码链接:)

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值