java课程设计-多人聊天工具(socket+多线程)

Posted Henrik-Yao

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了java课程设计-多人聊天工具(socket+多线程)相关的知识,希望对你有一定的参考价值。

大一下学期的java期末课程设计,分享一下

课设要求

多人聊天工具
服务器要求1:能够看到所有在线用户(25%)
服务器要求2:能够强制用户下线(25%)
客户端要求1:能够看到所有在线用户(25%)
客户端要求2:能够向某个用户发送消息(25%)

相关知识点

1.服务端能够看到所有在线用户
服务端继承了JFrame,实现可视化,通过socket实现服务端与客户端的连接,服务端每接收一个连接,把传进来的用户名和对应的socket连接封装成一个User对象,把User对象存进一个ArrayList的用户列表并把User对象通过取用户名方法取得用户名存进一个ArrayList的用户名列表,添加一个JPanel组件,将ArrayList中的内容通过循环显示JPanel中并布局在窗体的右边,在每当有人上线或者下线,刷新JPanel组件。
2.服务端能够强制用户下线
创建一个布局在窗体的下方的JPanel,在此JPanel中分别添加JLabel用于显示提示文字,添加JTextField用于获取服务端想要强制用户下线的ID,添加JButton用于绑定强制用户下线的事件监听,事件监听中将获取的JTextField的内容与用户名列表进行逐一匹配,匹配上则创建JSON格式的键值对对象,通过用户列表循环广播告知其他用户,并在用户列表和用户名列表中分别删除该用户信息。
3.客户端能够看到所有在线用户
客户端继承了JFrame,实现可视化,添加了一个布局在窗口右边的JPanel,把从服务端接收到的用户名列表中的信息放进去。
4.客户端要求能够向某个用户发送消息
客户端私发消息通过在消息后面加入-和目标用户名,传给服务端,服务端截取目标用户名,在用户名列表中判断是否存在此人,有则判断是否是私发,私发则向目标用户发送消息,没有则向全部用户发送消息。
5.运用JDBC实现持久化存储用户信息
数据库连接池运用了阿里巴巴的durid,定义一个JDBCUtils类,提供静态代码块加载配置文件,初始化连接池对象,通过Spring框架的JDBCTemplate对象进行sql语句的执行,在UserDao中提供了登录和注册方法,登录方法运用queryForObject方法进行登录查询,如果查到返回一个User对象,查不到则返回空,注册方法直接插入新记录,此处建表语句中把用户名设置成了主键,保证了用户名的唯一性,注册失败有警告弹窗提示。
这里加了一个ChatTest类用于绕过数据库账号校验,可以直接进入客户端进行连接。
6.使用JSONObject对象封装数据
在数据的传输中运用了键值对的形式进行传输,客户端传输给服务端的数据包中,通过判断private键的值来确认是否私发,通过username键告知服务端客户端的用户名,通过msg键传输具体消息,服务端传输给客户端的数据包中,通过判断user_list键的值来确认在线用户及人数
7.使用Maven构建管理项目
项目中运用到了JDBC相关内容和JSONObject对象,导入了一些依赖jar包,其中仓库和配置文件都是用的idea默认配置。

类图

项目框架

核心代码

1.maven配置文件pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>MyChat</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>15</maven.compiler.source>
        <maven.compiler.target>15</maven.compiler.target>
    </properties>

    <dependencies>
        <dependency>
            <groupId>net.sf.json-lib</groupId>
            <artifactId>json-lib</artifactId>
            <version>2.4</version>
            <classifier>jdk15</classifier>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.2.3</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.45</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>5.3.6</version>
        </dependency>
    </dependencies>

</project>

2.服务器端Server.java

import java.awt.image.BufferedImage;
import java.io.*;
import java.net.*;
import java.util.*;
import java.awt.*;
import javax.imageio.ImageIO;
import javax.swing.*;
import javax.swing.border.TitledBorder;
import net.sf.json.JSONObject;


//继承JFrame实现可视化
public class Server extends JFrame{

    //用户列表,用于存放连接上的用户信息
    ArrayList<User> user_list = new ArrayList<>();
    //用户名列表,用于显示已连接上的用户
    ArrayList<String> username_list = new ArrayList<>();

    //消息显示区域
    JTextArea show_area = new JTextArea();
    //用户名显示区域
    JTextArea show_user = new JTextArea(10, 10);

    //socket的数据输出流
    DataOutputStream outputStream = null;
    //socket的数据输入流
    DataInputStream inputStream = null;

    //从主函数里面开启服务端
    public static void main(String[] args) {
        new Server();
    }

    //构造函数
    public Server() {

        //设置流式布局
        setLayout(new BorderLayout());
        //VERTICAL_SCROLLBAR_AS_NEEDED设置垂直滚动条需要时出现
        //HORIZONTAL_SCROLLBAR_NEVER设置水平滚动条不出现
        //创建信息显示区的画布并添加到show_area
        JScrollPane panel = new JScrollPane(show_area,ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED,
                ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
        //设置信息显示区标题
        panel.setBorder(new TitledBorder("信息显示区"));
        //布局到中央
        add(panel,BorderLayout.CENTER);
        //设置信息显示区为不可编辑
        show_area.setEditable(false);


        //创建用于显示用户的画布
        final JPanel panel_east = new JPanel();
        //添加流式布局
        panel_east.setLayout(new BorderLayout());
        //设置标题
        panel_east.setBorder(new TitledBorder("在线用户"));
        //在用户显示区添加show_uesr
        panel_east.add(new JScrollPane(show_user), BorderLayout.CENTER);
        //设置用户显示区域为不可编辑
        show_user.setEditable(false);
        //将显示用户的画布添加到整体布局的右侧
        add(panel_east, BorderLayout.EAST);

        //创建关于踢下线用户的画布
        final JPanel panel_south = new JPanel();
        //创建标签
        JLabel label = new JLabel("输入要踢下线用户的ID");
        //创建输入框
        JTextField out_area = new JTextField(40);
        //创建踢下线按钮
        JButton out_btn = new JButton("踢下线");
        //依次添加进画布
        panel_south.add(label);
        panel_south.add(out_area);
        panel_south.add(out_btn);
        //将踢下线用户的画布添加到整体布局的下侧
        add(panel_south,BorderLayout.SOUTH);

        //设置踢下线按钮的监听
        out_btn.addActionListener(e -> {
            try {
                //用于存储踢下线用户的名字
                String out_username;
                //从输入框中获取踢下线用户名
                out_username = out_area.getText().trim();
                //用于判断盖用户是否被踢下线
                boolean is_out=false;
                //遍历用户列表依次判断
                for (int i = 0; i < user_list.size(); i++){
                    //比较用户名,相同则踢下线
                    if(user_list.get(i).getUsername().equals(out_username)){
                        //获取被踢下线用户对象
                        User out_user = user_list.get(i);
                        //使用json封装将要传递的数据
                        JSONObject data = new JSONObject();
                        //封装全体用户名,广播至所有用户
                        data.put("user_list", username_list);
                        //广播的信息内容
                        data.put("msg", out_user.getUsername() + "被管理员踢出\\n");
                        //服务端消息显示区显示相应信息
                        show_area.append(out_user.getUsername() + "被你踢出\\n");
                        //依次遍历用户列表
                        for (User value : user_list) {
                            try {
                                //获取每个用户列表的socket连接
                                outputStream = new DataOutputStream(value.getSocket().getOutputStream());
                                //传递信息
                                outputStream.writeUTF(data.toString());
                            } catch (IOException ex) {
                                ex.printStackTrace();
                            }
                        }
                        //将被踢用户移出用户列表
                        user_list.remove(i);
                        //将被踢用户移出用户名列表
                        username_list.remove(out_user.getUsername());
                        //刷新在线人数
                        show_user.setText("人数有 " + username_list.size() + " 人\\n");
                        //刷新在线用户
                        for (String s : username_list) {
                            show_user.append(s + "\\n");
                        }
                        //判断踢出成功
                        is_out=true;
                        break;
                    }

                }
                //根据是否踢出成功弹出相应提示
                if(is_out){
                    JOptionPane.showMessageDialog(null,"踢下线成功","提示",
                            JOptionPane.WARNING_MESSAGE);
                }
                if(!is_out){
                    JOptionPane.showMessageDialog(null,"不存在用户","提示",
                            JOptionPane.WARNING_MESSAGE);
                }
                //重置输入框
                out_area.setText("");
            } catch(Exception ex) {
                ex.printStackTrace();
            }
        });

        //设置该窗口名
        setTitle("服务器 ");
        //引入图片
        BufferedImage img;
        try {
            //根据图片名引入图片
            img = ImageIO.read(Server.class.getResource("/a.jpg"));
            //设置其为该窗体logo
            setIconImage(img);
        } catch (IOException exception) {
            exception.printStackTrace();
        }
        //设置窗体大小
        setSize(700, 700);
        //设置窗体位置可移动
        setLocationRelativeTo(null);
        //设置窗体关闭方式
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        //设置窗体可见
        setVisible(true);

        //socket连接相关代码
        try {
            //开启socket服务器,绑定端口11111
            ServerSocket serverSocket = new ServerSocket(11111);
            //信息显示区打印服务器启动时间
            show_area.append("服务器启动时间 " + new Date() + "\\n");
            //持续接收连接
            while (true) {
                //接收连接
                Socket socket = serverSocket.accept();
                //创建用户对象
                User user = new User();
                //判断是否连接上
                if (socket != null) {
                    //获取输入流
                    inputStream = new DataInputStream(socket.getInputStream());
                    //读取输入流
                    String json = inputStream.readUTF();
                    //创建信息对象
                    JSONObject data = JSONObject.fromObject(json);
                    //信息显示区打印用户上线
                    show_area.append("用户 " + data.getString("username") + " 在" + new Date() + "登陆系统"+"\\n");
                    //创建新用户
                    user = new User();
                    //存储socket对象
                    user.setSocket(socket);
                    //获取输入流用户名
                    user.setUsername(data.getString("username"));
                    //添加进用户列表
                    user_list.add(user);
                    //添加进用户名列表
                    username_list.add(data.getString("username"));

                    //刷新在线人数
                    show_user.setText("人数有 " + username_list.size() + " 人\\n");
                    //刷新在线用户
                    for (String s : username_list) {
                        show_user.append(s + "\\n");
                    }

                }

                //封装信息对象
                JSONObject online = new JSONObject();
                //设置接收信息对象
                online.put("user_list", username_list);
                //设置信息内容
                online.put("msg", user.getUsername() + "上线了");
                //依次遍历,将信息广播给所有在线用户
                for (User value : user_list) {
                    //获取输出流
                    outputStream = new DataOutputStream(value.getSocket().getOutputStream());
                    //给所有用户输出上线信息
                    outputStream.writeUTF(online.toString());
                }

                //开启新线程,持续接收该socket信息
                new Thread(new ServerThread(socket)).start();

            }
        } catch (IOException ex) {
            ex.printStackTrace();
        }
    }

    //线程代码
    class ServerThread implements Runnable {
        //存放全局变量socket
        private final Socket socket;

        //构造函数,初始化socket
        public ServerThread(Socket socket) {
            this.socket = socket;
        }

        @Override
        public void run() {
            try {
                //获取输入流
                DataInputStream inputStream = new DataInputStream(socket.getInputStream());
                //持续接收信息
                while (true) {
                    //获取传递进来的信息
                    String json = inputStream.readUTF();
                    //封装成json格式
                    JSONObject data = JSONObject.fromObject(json);

                    //通过json里面的private判断是否私发
                    boolean is_private = false;
                    //私发处理
                    for (int i = 0; i < user_list.size(); i++) {
                        //找到私发对象
                        if (user_list.get(i).getUsername().equals(data以上是关于java课程设计-多人聊天工具(socket+多线程)的主要内容,如果未能解决你的问题,请参考以下文章

JAVA实战用socket通信编程制作多人聊天室

java实现的多人聊天(控制台输入)

java是如何实现聊天功能的?

Python 多人聊天工具 ( 多线程 )

Java网络编程系列之基于BIO的多人聊天室设计与实现

基于Java NIO的多人在线聊天工具源码实现(登录,单聊,群聊)