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

作者:小虚竹and掘金 时间:2022-12-28 09:23:30 

一、题目描述

题目实现:不同的客户端之间需要进行通信,一个客户端与指定的另一客户端进行通信,实现一对一聊天功能。

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

二、解题思路

创建一个服务类:ClientOneToOneServerFrame,继承JFrame类

定义ServerThread线程类,用于为客户端添加用户列表。有一部分代码用于转发客户端发送的消息。

创建一个客户端类:ClientOneToOneClientFrame,继承JFrame类

定义ClientThread线程类,用于对接收到服务器的信息,进行处理。如果是登录用户,就添加到用户列表中。

如果是消息,就追加到文本域中。

技术重点:

在服务器端通过线程对客户端发送的信息进行监听,并对登录用户和消息分别进行处理。如果是登录用户,就将所有用户添加到客户端的用户列表中;如果是消息,就转发给指定的用户;客户端则通过线程对接收到的信息进行处理,如果是登录用户就添加到用户列表中,如果是消息就追加到文本域中。  (1)在服务器端创建线程类ServerThread,用于对登录用户和消息分别进行处理。如果是登录用户,就将所有用户添加到客户端的用户列表中;如果是消息就转发给指定的用户。

(2)在客户端创建线程类ClientThread,用于对接收到的信息进行处理,如果是登录用户就添加到用户列表中,如果是消息就追加到文本域中。

启动多个客户端:

1、把项目打成jar包:利用maven 的clean install

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

会在target目录下生成jar包

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

2、进入target目录,使用java -cp的命令运行指定的类

java -cp 命令中 cp 指的就是classpath。使用该命令可以运行jar中的某个指定的类(要包含全路径的包名)

进入cmd命令模式

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

运行服务端

java -cp basics98-1.0-SNAPSHOT.jar com.xiaoxuzhu.ClientOneToOneServerFrame

运行多个客户端

java -cp basics98-1.0-SNAPSHOT.jar com.xiaoxuzhu.ClientOneToOneClientFrame

三、代码详解

ClientOneToOneServerFrame

package com.xiaoxuzhu;
import java.awt.BorderLayout;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Set;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;

/**
* Description:
*
* @author xiaoxuzhu
* @version 1.0
*
* <pre>
* 修改记录:
* 修改后版本        修改人修改日期修改内容
* 2022/6/5.1    xiaoxuzhu2022/6/5    Create
* </pre>
* @date 2022/6/5
*/
public class ClientOneToOneServerFrame  extends JFrame{
   private JTextArea ta_info;
   private ServerSocket server; // 声明ServerSocket对象
   private Socket socket; // 声明Socket对象socket
   private Hashtable<String, Socket> map = new Hashtable<String, Socket>();// 用于存储连接到服务器的用户和客户端套接字对象

public void createSocket() {
       try {
           server = new ServerSocket(9527);
           while (true) {
               ta_info.append("等待新客户连接......\n");
               socket = server.accept();// 创建套接字对象
               ta_info.append("客户端连接成功。" + socket + "\n");
               new ServerThread(socket).start();// 创建并启动线程对象
           }
       } catch (IOException e) {
           e.printStackTrace();
       }
   }

class ServerThread extends Thread {
       Socket socket;

public ServerThread(Socket socket) {
           this.socket = socket;
       }

public void run() {
           try {
               BufferedReader in = new BufferedReader(new InputStreamReader(
                       socket.getInputStream()));// 创建输入流对象
               while (true) {
                   String info = in.readLine();// 读取信息
                   String key = "";
                   if (info.startsWith("用户:")) {// 添加登录用户到客户端列表
                       key = info.substring(3, info.length());// 获得用户名并作为键使用
                       map.put(key, socket);// 添加键值对
                       Set<String> set = map.keySet();// 获得集合中所有键的Set视图
                       Iterator<String> keyIt = set.iterator();// 获得所有键的迭代器
                       while (keyIt.hasNext()) {
                           String receiveKey = keyIt.next();// 获得表示接收信息的键
                           Socket s = map.get(receiveKey);// 获得与该键对应的套接字对象
                           PrintWriter out = new PrintWriter(s
                                   .getOutputStream(), true);// 创建输出流对象
                           Iterator<String> keyIt1 = set.iterator();// 获得所有键的迭代器
                           while (keyIt1.hasNext()) {
                               String receiveKey1 = keyIt1.next();// 获得键,用于向客户端添加用户列表
                               out.println(receiveKey1);// 发送信息
                               out.flush();// 刷新输出缓冲区
                           }
                       }

} else {// 转发接收的消息
                       key = info.substring(info.indexOf(":发送给:") + 5, info
                               .indexOf(":的信息是:"));// 获得接收方的key值,即接收方的用户名
                       String sendUser = info.substring(0, info
                               .indexOf(":发送给:"));// 获得发送方的key值,即发送方的用户名
                       Set<String> set = map.keySet();// 获得集合中所有键的Set视图
                       Iterator<String> keyIt = set.iterator();// 获得所有键的迭代器
                       while (keyIt.hasNext()) {
                           String receiveKey = keyIt.next();// 获得表示接收信息的键
                           if (key.equals(receiveKey)
                                   && !sendUser.equals(receiveKey)) {// 如果是发送方,但不是用户本身
                               Socket s = map.get(receiveKey);// 获得与该键对应的套接字对象
                               PrintWriter out = new PrintWriter(s
                                       .getOutputStream(), true);// 创建输出流对象

out.println("MSG:"+info);// 发送信息
                               out.flush();// 刷新输出缓冲区
                           }
                       }
                   }
               }
           } catch (IOException e) {
               ta_info.append(socket + "已经退出。\n");
           }
       }
   }

/**
    * Launch the application
    *
    * @param args
    */
   public static void main(String args[]) {
       ClientOneToOneServerFrame frame = new ClientOneToOneServerFrame();
       frame.setVisible(true);
       frame.createSocket();
   }

/**
    * Create the frame
    */
   public ClientOneToOneServerFrame() {
       super();
       setTitle("客户端一对一通信——服务器端程序");
       setBounds(100, 100, 385, 266);
       setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

final JScrollPane scrollPane = new JScrollPane();
       getContentPane().add(scrollPane, BorderLayout.CENTER);

ta_info = new JTextArea();
       scrollPane.setViewportView(ta_info);
   }
}

ClientOneToOneClientFrame

package com.xiaoxuzhu;

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.net.UnknownHostException;

import javax.swing.*;

/**
* Description:
*
* @author xiaoxuzhu
* @version 1.0
*
* <pre>
* 修改记录:
* 修改后版本        修改人修改日期修改内容
* 2022/6/5.1    xiaoxuzhu2022/6/5    Create
* </pre>
* @date 2022/6/5
*/
public class ClientOneToOneClientFrame extends JFrame{
   private JTextField tf_newUser;
   private JList user_list;
   private JTextArea ta_info;
   private JTextField tf_send;
   PrintWriter out;// 声明输出流对象
   private boolean loginFlag = false;// 为true时表示已经登录,为false时表示未登录
   private Socket socket;
   /**
    * Launch the application
    *
    * @param args
    */
   public static void main(String args[]) {
       EventQueue.invokeLater(new Runnable() {
           public void run() {
               try {
                   ClientOneToOneClientFrame frame = new ClientOneToOneClientFrame();
                   frame.setVisible(true);
                   frame.createClientSocket();// 调用方法创建套接字对象
               } catch (Exception e) {
                   e.printStackTrace();
               }
           }
       });
   }

public void createClientSocket() {
       try {
            socket = new Socket("127.0.0.1", 9527);// 创建套接字对象
           out = new PrintWriter(socket.getOutputStream(), true);// 创建输出流对象
           SwingWorker<Void,Void> worker=new SwingWorker<Void, Void>() {
               @Override
               protected Void doInBackground() throws Exception {
                   try {
                       BufferedReader in = new BufferedReader(new InputStreamReader(
                               socket.getInputStream()));// 创建输入流对象
                       DefaultComboBoxModel model = (DefaultComboBoxModel) user_list
                               .getModel();// 获得列表框的模型
                       while (true) {
                           String info = in.readLine().trim();// 读取信息

if (!info.startsWith("MSG:")) {
                               boolean itemFlag = false;// 标记是否为列表框添加列表项,为true不添加,为false添加
                               for (int i = 0; i < model.getSize(); i++) {
                                   if (info.equals((String) model.getElementAt(i))) {
                                       itemFlag = true;
                                   }
                               }
                               if (!itemFlag) {
                                   model.addElement(info);// 添加列表项
                               } else {
                                   itemFlag = false;
                               }
                           } else {
                               ta_info.append(info + "\n");// 在文本域中显示信息
                               if (info.equals("88")) {
                                   break;// 结束线程
                               }
                           }
                       }
                   } catch (IOException e) {
                       e.printStackTrace();
                   }
                   return null;
               }
           };
           worker.execute();

} catch (UnknownHostException e) {
           e.printStackTrace();
       } catch (IOException e) {
           e.printStackTrace();
       }
   }

private void send() {
       if (!loginFlag) {
           JOptionPane.showMessageDialog(null, "请先登录。");
           return;
       }
       String sendUserName = tf_newUser.getText().trim();
       String info = tf_send.getText();// 获得输入的信息
       if (info.equals("")) {
           return;// 如果没输入信息则返回,即不发送
       }
       String receiveUserName = (String) user_list.getSelectedValue();// 获得接收信息的用户
       String msg = sendUserName + ":发送给:" + receiveUserName + ":的信息是: "
               + info;// 定义发送的信息
       if (info.equals("88")) {
           System.exit(0);// 如果没输入信息是88,则退出
       }
       out.println(msg);// 发送信息
       out.flush();// 刷新输出缓冲区
       tf_send.setText(null);// 清空文本框
   }

/**
    * Create the frame
    */
   public ClientOneToOneClientFrame() {
       super();
       setTitle("客户端一对一通信——客户端程序");
       setBounds(100, 100, 385, 288);
       setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

final JPanel panel = new JPanel();
       getContentPane().add(panel, BorderLayout.SOUTH);

final JLabel label = new JLabel();
       label.setText("输入聊天内容:");
       panel.add(label);

tf_send = new JTextField();
       tf_send.addActionListener(new ActionListener() {
           public void actionPerformed(final ActionEvent e) {
               send();// 调用方法发送信息
           }
       });
       tf_send.setPreferredSize(new Dimension(180, 25));
       panel.add(tf_send);

final JButton button = new JButton();
       button.addActionListener(new ActionListener() {
           public void actionPerformed(final ActionEvent e) {
               send();// 调用方法发送信息
           }
       });
       button.setText("发  送");
       panel.add(button);

final JSplitPane splitPane = new JSplitPane();
       splitPane.setDividerLocation(100);
       getContentPane().add(splitPane, BorderLayout.CENTER);

final JScrollPane scrollPane = new JScrollPane();
       splitPane.setRightComponent(scrollPane);

ta_info = new JTextArea();
       scrollPane.setViewportView(ta_info);

final JScrollPane scrollPane_1 = new JScrollPane();
       splitPane.setLeftComponent(scrollPane_1);

user_list = new JList();
       user_list.setModel(new DefaultComboBoxModel(new String[] { "" }));
       scrollPane_1.setViewportView(user_list);

final JPanel panel_1 = new JPanel();
       getContentPane().add(panel_1, BorderLayout.NORTH);

final JLabel label_1 = new JLabel();
       label_1.setText("输入用户名称:");
       panel_1.add(label_1);

tf_newUser = new JTextField();
       tf_newUser.setPreferredSize(new Dimension(180, 25));
       panel_1.add(tf_newUser);

final JButton button_1 = new JButton();
       button_1.addActionListener(new ActionListener() {
           public void actionPerformed(final ActionEvent e) {
               if (loginFlag) {
                   JOptionPane.showMessageDialog(null, "在同一窗口只能登录一次。");
                   return;
               }
               String userName = tf_newUser.getText().trim();// 获得登录用户名
               out.println("用户:" + userName);// 发送登录用户的名称
               out.flush();// 刷新输出缓冲区
               tf_newUser.setEnabled(false);
               loginFlag = true;
           }
       });
       button_1.setText("登  录");
       panel_1.add(button_1);
   }
}

服务器启动

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

客户端1和客户端2登录

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

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

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

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

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

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

多学一个知识点

swing的开发过程,要了解3种线程的概念:

1、初始化线程 :此类线程将执行初始化应用代码。

2、事件调度线程 :所有的事件处理代码在这里执行。大多数与Swing框架 交互的代码也必须执行这个线程。

事件调度线程是单线程的:因为 Swing里面的各种组件类,比如JTextField,JButton 都不是线程安全的,这就意味着,如果有多个线程,那么同一个JTextField的setText方法,可能会被多个线程同时调用,这会导致同步问题以及错误数据的发生

3、工作线程 :也称作background threads(后台线程),此类线程将执行所有消耗时间的任务。

比如的事件监听&mdash;&mdash;在actionPerformed 里放一个长耗时任务,如:数据库访问连接 建立网络连接 文件复制等等 就会自动进入事件调度线程。 而事件调度线程又是单线程模式,其结果就会是在执行这些长耗时任务的时候,界面就无响应了。

为了解决这个问题,Swing提供了一个SwingWorker类来解决。 SwingWorker是一个抽象类,为了使用,必须实现方法 doInBackground,在doInBackground中,就可以编写我们的任务,然后执行SwingWorker的execute方法,放在专门的工作线程中去运行。

上面题目里,ClientOneToOneClientFrame类中的createClientSocket()里就用到了SwingWorker

来源:https://juejin.cn/post/7159088827456290824

标签:Java,聊天室,客户端
0
投稿

猜你喜欢

  • Java中的匿名对象定义与用法实例分析

    2023-04-11 20:03:05
  • maven为MANIFEST.MF文件添加内容的方法

    2022-10-29 11:15:56
  • Android应用UI开发中Fragment的常见用法小结

    2021-06-16 19:35:54
  • Java基础之从HelloWorld到面向对象

    2022-11-23 11:55:06
  • Android 动态加载二维码视图生成快照的示例

    2023-08-04 19:57:54
  • C# 泛型字典 Dictionary的使用详解

    2022-01-19 23:48:17
  • SpringCloud学习笔记之OpenFeign进行服务调用

    2021-05-25 12:32:58
  • Java实现考试系统

    2023-11-18 04:15:03
  • C#如何Task执行任务,等待任务完成

    2022-03-06 11:31:31
  • Java缓存ehcache的使用步骤

    2022-06-25 04:30:55
  • Jmeter环境搭建及安装步骤

    2021-11-03 21:06:10
  • 基于Java设计一个短链接生成系统

    2023-08-15 09:23:37
  • Java 精炼解读数据结构的链表的概念与实现

    2022-03-02 05:17:11
  • java高并发之线程组详解

    2023-02-15 21:30:25
  • 如何使用lamda表达式对list进行求和

    2022-08-24 09:20:09
  • 深入浅析jni中的java接口使用

    2023-07-22 19:54:23
  • idea mybatis配置log4j打印sql语句的示例

    2023-11-25 10:32:39
  • Java 如何安全的发布对象

    2022-08-09 05:38:52
  • 关于Spring事务隔离、传播属性与@Transactional注解

    2021-11-15 04:37:11
  • Springboot上传文件时提示405问题及排坑过程

    2022-12-13 03:03:58
  • asp之家 软件编程 m.aspxhome.com