[JAVA] Java聊天室之实现客户端一对一聊天功能

1685 0
王子 2022-11-8 17:26:41 | 显示全部楼层 |阅读模式
目录

    一、题目描述二、解题思路三、代码详解多学一个知识点


一、题目描述

题目实现:不同的客户端之间需要进行通信,一个客户端与指定的另一客户端进行通信,实现一对一聊天功能。
实现一个客户端与指定的另一客户端进行通信,运行程序,服务器启动后,启动3个客户端程序,分别以小小,虚虚,竹竹,登录 ,然后在左侧的用户列表中选择接收信息用户,输入聊天信息,发送到目标用户。

二、解题思路

创建一个服务类:ClientOneToOneServerFrame,继承JFrame类
定义ServerThread线程类,用于为客户端添加用户列表。有一部分代码用于转发客户端发送的消息。
创建一个客户端类:ClientOneToOneClientFrame,继承JFrame类
定义ClientThread线程类,用于对接收到服务器的信息,进行处理。如果是登录用户,就添加到用户列表中。
如果是消息,就追加到文本域中。
技术重点:
在服务器端通过线程对客户端发送的信息进行监听,并对登录用户和消息分别进行处理。如果是登录用户,就将所有用户添加到客户端的用户列表中;如果是消息,就转发给指定的用户;客户端则通过线程对接收到的信息进行处理,如果是登录用户就添加到用户列表中,如果是消息就追加到文本域中。  (1)在服务器端创建线程类ServerThread,用于对登录用户和消息分别进行处理。如果是登录用户,就将所有用户添加到客户端的用户列表中;如果是消息就转发给指定的用户。
(2)在客户端创建线程类ClientThread,用于对接收到的信息进行处理,如果是登录用户就添加到用户列表中,如果是消息就追加到文本域中。
启动多个客户端:
1、把项目打成jar包:利用maven 的clean install


会在target目录下生成jar包


2、进入target目录,使用java -cp的命令运行指定的类
java -cp 命令中 cp 指的就是classpath。使用该命令可以运行jar中的某个指定的类(要包含全路径的包名)
进入cmd命令模式


运行服务端
java -cp basics98-1.0-SNAPSHOT.jar com.xiaoxuzhu.ClientOneToOneServerFrame
运行多个客户端
java -cp basics98-1.0-SNAPSHOT.jar com.xiaoxuzhu.ClientOneToOneClientFrame

三、代码详解

ClientOneToOneServerFrame
  1. package com.xiaoxuzhu;
  2. import java.awt.BorderLayout;
  3. import java.io.BufferedReader;
  4. import java.io.IOException;
  5. import java.io.InputStreamReader;
  6. import java.io.PrintWriter;
  7. import java.net.ServerSocket;
  8. import java.net.Socket;
  9. import java.util.Hashtable;
  10. import java.util.Iterator;
  11. import java.util.Set;
  12. import javax.swing.JFrame;
  13. import javax.swing.JScrollPane;
  14. import javax.swing.JTextArea;
  15. /**
  16. * Description:
  17. *
  18. * @author xiaoxuzhu
  19. * @version 1.0
  20. *
  21. * <pre>
  22. * 修改记录:
  23. * 修改后版本                修改人                修改日期                        修改内容
  24. * 2022/6/5.1            xiaoxuzhu                2022/6/5                    Create
  25. * </pre>
  26. * @date 2022/6/5
  27. */
  28. public class ClientOneToOneServerFrame  extends JFrame{
  29.     private JTextArea ta_info;
  30.     private ServerSocket server; // 声明ServerSocket对象
  31.     private Socket socket; // 声明Socket对象socket
  32.     private Hashtable<String, Socket> map = new Hashtable<String, Socket>();// 用于存储连接到服务器的用户和客户端套接字对象
  33.     public void createSocket() {
  34.         try {
  35.             server = new ServerSocket(9527);
  36.             while (true) {
  37.                 ta_info.append("等待新客户连接......\n");
  38.                 socket = server.accept();// 创建套接字对象
  39.                 ta_info.append("客户端连接成功。" + socket + "\n");
  40.                 new ServerThread(socket).start();// 创建并启动线程对象
  41.             }
  42.         } catch (IOException e) {
  43.             e.printStackTrace();
  44.         }
  45.     }
  46.     class ServerThread extends Thread {
  47.         Socket socket;
  48.         public ServerThread(Socket socket) {
  49.             this.socket = socket;
  50.         }
  51.         public void run() {
  52.             try {
  53.                 BufferedReader in = new BufferedReader(new InputStreamReader(
  54.                         socket.getInputStream()));// 创建输入流对象
  55.                 while (true) {
  56.                     String info = in.readLine();// 读取信息
  57.                     String key = "";
  58.                     if (info.startsWith("用户:")) {// 添加登录用户到客户端列表
  59.                         key = info.substring(3, info.length());// 获得用户名并作为键使用
  60.                         map.put(key, socket);// 添加键值对
  61.                         Set<String> set = map.keySet();// 获得集合中所有键的Set视图
  62.                         Iterator<String> keyIt = set.iterator();// 获得所有键的迭代器
  63.                         while (keyIt.hasNext()) {
  64.                             String receiveKey = keyIt.next();// 获得表示接收信息的键
  65.                             Socket s = map.get(receiveKey);// 获得与该键对应的套接字对象
  66.                             PrintWriter out = new PrintWriter(s
  67.                                     .getOutputStream(), true);// 创建输出流对象
  68.                             Iterator<String> keyIt1 = set.iterator();// 获得所有键的迭代器
  69.                             while (keyIt1.hasNext()) {
  70.                                 String receiveKey1 = keyIt1.next();// 获得键,用于向客户端添加用户列表
  71.                                 out.println(receiveKey1);// 发送信息
  72.                                 out.flush();// 刷新输出缓冲区
  73.                             }
  74.                         }
  75.                     } else {// 转发接收的消息
  76.                         key = info.substring(info.indexOf(":发送给:") + 5, info
  77.                                 .indexOf(":的信息是:"));// 获得接收方的key值,即接收方的用户名
  78.                         String sendUser = info.substring(0, info
  79.                                 .indexOf(":发送给:"));// 获得发送方的key值,即发送方的用户名
  80.                         Set<String> set = map.keySet();// 获得集合中所有键的Set视图
  81.                         Iterator<String> keyIt = set.iterator();// 获得所有键的迭代器
  82.                         while (keyIt.hasNext()) {
  83.                             String receiveKey = keyIt.next();// 获得表示接收信息的键
  84.                             if (key.equals(receiveKey)
  85.                                     && !sendUser.equals(receiveKey)) {// 如果是发送方,但不是用户本身
  86.                                 Socket s = map.get(receiveKey);// 获得与该键对应的套接字对象
  87.                                 PrintWriter out = new PrintWriter(s
  88.                                         .getOutputStream(), true);// 创建输出流对象
  89.                                 out.println("MSG:"+info);// 发送信息
  90.                                 out.flush();// 刷新输出缓冲区
  91.                             }
  92.                         }
  93.                     }
  94.                 }
  95.             } catch (IOException e) {
  96.                 ta_info.append(socket + "已经退出。\n");
  97.             }
  98.         }
  99.     }
  100.     /**
  101.      * Launch the application
  102.      *
  103.      * @param args
  104.      */
  105.     public static void main(String args[]) {
  106.         ClientOneToOneServerFrame frame = new ClientOneToOneServerFrame();
  107.         frame.setVisible(true);
  108.         frame.createSocket();
  109.     }
  110.     /**
  111.      * Create the frame
  112.      */
  113.     public ClientOneToOneServerFrame() {
  114.         super();
  115.         setTitle("客户端一对一通信——服务器端程序");
  116.         setBounds(100, 100, 385, 266);
  117.         setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
  118.         final JScrollPane scrollPane = new JScrollPane();
  119.         getContentPane().add(scrollPane, BorderLayout.CENTER);
  120.         ta_info = new JTextArea();
  121.         scrollPane.setViewportView(ta_info);
  122.     }
  123. }
复制代码
ClientOneToOneClientFrame
  1. package com.xiaoxuzhu;
  2. import java.awt.BorderLayout;
  3. import java.awt.Dimension;
  4. import java.awt.EventQueue;
  5. import java.awt.event.ActionEvent;
  6. import java.awt.event.ActionListener;
  7. import java.io.BufferedReader;
  8. import java.io.IOException;
  9. import java.io.InputStreamReader;
  10. import java.io.PrintWriter;
  11. import java.net.Socket;
  12. import java.net.UnknownHostException;
  13. import javax.swing.*;
  14. /**
  15. * Description:
  16. *
  17. * @author xiaoxuzhu
  18. * @version 1.0
  19. *
  20. * <pre>
  21. * 修改记录:
  22. * 修改后版本                修改人                修改日期                        修改内容
  23. * 2022/6/5.1            xiaoxuzhu                2022/6/5                    Create
  24. * </pre>
  25. * @date 2022/6/5
  26. */
  27. public class ClientOneToOneClientFrame extends JFrame{
  28.     private JTextField tf_newUser;
  29.     private JList user_list;
  30.     private JTextArea ta_info;
  31.     private JTextField tf_send;
  32.     PrintWriter out;// 声明输出流对象
  33.     private boolean loginFlag = false;// 为true时表示已经登录,为false时表示未登录
  34.     private Socket socket;
  35.     /**
  36.      * Launch the application
  37.      *
  38.      * @param args
  39.      */
  40.     public static void main(String args[]) {
  41.         EventQueue.invokeLater(new Runnable() {
  42.             public void run() {
  43.                 try {
  44.                     ClientOneToOneClientFrame frame = new ClientOneToOneClientFrame();
  45.                     frame.setVisible(true);
  46.                     frame.createClientSocket();// 调用方法创建套接字对象
  47.                 } catch (Exception e) {
  48.                     e.printStackTrace();
  49.                 }
  50.             }
  51.         });
  52.     }
  53.     public void createClientSocket() {
  54.         try {
  55.              socket = new Socket("127.0.0.1", 9527);// 创建套接字对象
  56.             out = new PrintWriter(socket.getOutputStream(), true);// 创建输出流对象
  57.             SwingWorker<Void,Void> worker=new SwingWorker<Void, Void>() {
  58.                 @Override
  59.                 protected Void doInBackground() throws Exception {
  60.                     try {
  61.                         BufferedReader in = new BufferedReader(new InputStreamReader(
  62.                                 socket.getInputStream()));// 创建输入流对象
  63.                         DefaultComboBoxModel model = (DefaultComboBoxModel) user_list
  64.                                 .getModel();// 获得列表框的模型
  65.                         while (true) {
  66.                             String info = in.readLine().trim();// 读取信息
  67.                             if (!info.startsWith("MSG:")) {
  68.                                 boolean itemFlag = false;// 标记是否为列表框添加列表项,为true不添加,为false添加
  69.                                 for (int i = 0; i < model.getSize(); i++) {
  70.                                     if (info.equals((String) model.getElementAt(i))) {
  71.                                         itemFlag = true;
  72.                                     }
  73.                                 }
  74.                                 if (!itemFlag) {
  75.                                     model.addElement(info);// 添加列表项
  76.                                 } else {
  77.                                     itemFlag = false;
  78.                                 }
  79.                             } else {
  80.                                 ta_info.append(info + "\n");// 在文本域中显示信息
  81.                                 if (info.equals("88")) {
  82.                                     break;// 结束线程
  83.                                 }
  84.                             }
  85.                         }
  86.                     } catch (IOException e) {
  87.                         e.printStackTrace();
  88.                     }
  89.                     return null;
  90.                 }
  91.             };
  92.             worker.execute();
  93.         } catch (UnknownHostException e) {
  94.             e.printStackTrace();
  95.         } catch (IOException e) {
  96.             e.printStackTrace();
  97.         }
  98.     }
  99.     private void send() {
  100.         if (!loginFlag) {
  101.             JOptionPane.showMessageDialog(null, "请先登录。");
  102.             return;
  103.         }
  104.         String sendUserName = tf_newUser.getText().trim();
  105.         String info = tf_send.getText();// 获得输入的信息
  106.         if (info.equals("")) {
  107.             return;// 如果没输入信息则返回,即不发送
  108.         }
  109.         String receiveUserName = (String) user_list.getSelectedValue();// 获得接收信息的用户
  110.         String msg = sendUserName + ":发送给:" + receiveUserName + ":的信息是: "
  111.                 + info;// 定义发送的信息
  112.         if (info.equals("88")) {
  113.             System.exit(0);// 如果没输入信息是88,则退出
  114.         }
  115.         out.println(msg);// 发送信息
  116.         out.flush();// 刷新输出缓冲区
  117.         tf_send.setText(null);// 清空文本框
  118.     }
  119.     /**
  120.      * Create the frame
  121.      */
  122.     public ClientOneToOneClientFrame() {
  123.         super();
  124.         setTitle("客户端一对一通信——客户端程序");
  125.         setBounds(100, 100, 385, 288);
  126.         setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
  127.         final JPanel panel = new JPanel();
  128.         getContentPane().add(panel, BorderLayout.SOUTH);
  129.         final JLabel label = new JLabel();
  130.         label.setText("输入聊天内容:");
  131.         panel.add(label);
  132.         tf_send = new JTextField();
  133.         tf_send.addActionListener(new ActionListener() {
  134.             public void actionPerformed(final ActionEvent e) {
  135.                 send();// 调用方法发送信息
  136.             }
  137.         });
  138.         tf_send.setPreferredSize(new Dimension(180, 25));
  139.         panel.add(tf_send);
  140.         final JButton button = new JButton();
  141.         button.addActionListener(new ActionListener() {
  142.             public void actionPerformed(final ActionEvent e) {
  143.                 send();// 调用方法发送信息
  144.             }
  145.         });
  146.         button.setText("发  送");
  147.         panel.add(button);
  148.         final JSplitPane splitPane = new JSplitPane();
  149.         splitPane.setDividerLocation(100);
  150.         getContentPane().add(splitPane, BorderLayout.CENTER);
  151.         final JScrollPane scrollPane = new JScrollPane();
  152.         splitPane.setRightComponent(scrollPane);
  153.         ta_info = new JTextArea();
  154.         scrollPane.setViewportView(ta_info);
  155.         final JScrollPane scrollPane_1 = new JScrollPane();
  156.         splitPane.setLeftComponent(scrollPane_1);
  157.         user_list = new JList();
  158.         user_list.setModel(new DefaultComboBoxModel(new String[] { "" }));
  159.         scrollPane_1.setViewportView(user_list);
  160.         final JPanel panel_1 = new JPanel();
  161.         getContentPane().add(panel_1, BorderLayout.NORTH);
  162.         final JLabel label_1 = new JLabel();
  163.         label_1.setText("输入用户名称:");
  164.         panel_1.add(label_1);
  165.         tf_newUser = new JTextField();
  166.         tf_newUser.setPreferredSize(new Dimension(180, 25));
  167.         panel_1.add(tf_newUser);
  168.         final JButton button_1 = new JButton();
  169.         button_1.addActionListener(new ActionListener() {
  170.             public void actionPerformed(final ActionEvent e) {
  171.                 if (loginFlag) {
  172.                     JOptionPane.showMessageDialog(null, "在同一窗口只能登录一次。");
  173.                     return;
  174.                 }
  175.                 String userName = tf_newUser.getText().trim();// 获得登录用户名
  176.                 out.println("用户:" + userName);// 发送登录用户的名称
  177.                 out.flush();// 刷新输出缓冲区
  178.                 tf_newUser.setEnabled(false);
  179.                 loginFlag = true;
  180.             }
  181.         });
  182.         button_1.setText("登  录");
  183.         panel_1.add(button_1);
  184.     }
  185. }
复制代码
服务器启动


客户端1和客户端2登录


客户端小小向客户端虚虚发送消息


客户端虚虚向客户端小小发送消息


注:小小发给虚虚时,小小自己的界面不显示自己发出的内容。本示例主要是为了演示客户端向指定客户端发送消息。

多学一个知识点

swing的开发过程,要了解3种线程的概念:
1、初始化线程 :此类线程将执行初始化应用代码。
2、事件调度线程 :所有的事件处理代码在这里执行。大多数与Swing框架 交互的代码也必须执行这个线程。
事件调度线程是单线程的:因为 Swing里面的各种组件类,比如JTextField,JButton 都不是线程安全的,这就意味着,如果有多个线程,那么同一个JTextField的setText方法,可能会被多个线程同时调用,这会导致同步问题以及错误数据的发生
3、工作线程 :也称作background threads(后台线程),此类线程将执行所有消耗时间的任务。
比如的事件监听——在actionPerformed 里放一个长耗时任务,如:数据库访问连接 建立网络连接 文件复制等等 就会自动进入事件调度线程。 而事件调度线程又是单线程模式,其结果就会是在执行这些长耗时任务的时候,界面就无响应了。
为了解决这个问题,Swing提供了一个SwingWorker类来解决。 SwingWorker是一个抽象类,为了使用,必须实现方法 doInBackground,在doInBackground中,就可以编写我们的任务,然后执行SwingWorker的execute方法,放在专门的工作线程中去运行。
上面题目里,ClientOneToOneClientFrame类中的createClientSocket()里就用到了SwingWorker
以上就是Java聊天室之实现客户端一对一聊天功能的详细内容,更多关于Java聊天室的资料请关注中国红客联盟其它相关文章!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

中国红客联盟公众号

联系站长QQ:5520533

admin@chnhonker.com
Copyright © 2001-2025 Discuz Team. Powered by Discuz! X3.5 ( 粤ICP备13060014号 )|天天打卡 本站已运行