目录
一、题目描述二、解题思路三、代码详解多学一个知识点
一、题目描述
题目实现:不同的客户端之间需要进行通信,一个客户端与指定的另一客户端进行通信,实现一对一聊天功能。
实现一个客户端与指定的另一客户端进行通信,运行程序,服务器启动后,启动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
- 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 xiaoxuzhu 2022/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 xiaoxuzhu 2022/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);
- }
- }
复制代码服务器启动
客户端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聊天室的资料请关注中国红客联盟其它相关文章!