java - 如何在多线程聊天中使用 swing 处理输入重复名称错误

标签 java multithreading swing client-server serversocket

我创建了一个client-server chat没有图形界面。它可以在控制台中运行。现在我想创建 Swing 界面。但我在尝试处理错误重复名称时遇到了困难。

当服务器接受连接时,它会为此连接创建新线程,并在这个新线程内接收来自客户端的消息。预期的第一条消息是用户名。服务器检查已注册用户名的列表。如果该名称已被使用,服务器会向客户端发送错误消息,并继续等待客户端提供该名称。而且在控制台处理起来也很简单——客户端只需接收服务器发来的消息并继续输入名称(客户端不需要再次输入ip地址,因为连接已经建立)

另一种与 Swing 有关的情况。 (我想将逻辑与表示分开以进行进一步修改)。因此,当用户注册时,他输入IP 地址,从组合框中选择端口,并输入他的名称。按下按钮后,与服务器建立连接。

我的问题是:如何处理错误 “名称已在使用中”并重复输入名称而不创建连接两次。

这是我的Client.java

package nikochat.com.client;

import nikochat.com.app.AppConstants;
import nikochat.com.service.StreamsManager;
import nikochat.com.ui.UserInterface;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.NoSuchElementException;

public class Client {


    private Socket socket;
    private BufferedReader input;
    private PrintWriter output;
    private boolean stopped;
    private String name;
    private UserInterface ui;
    /**
     * результат регистрации имени на сервере
     */
    private boolean isRegistered = false;

    public boolean isRegistered() {
        return isRegistered;
    }

    public Client(UserInterface ui) {
        this.ui = ui;
    }

    /**
     * This method must firstly be invoked to make a connection with the server
     * initialise input and output streams,
     * and register the user in the chat.
     *
     * @param ip   ip address
     * @param port
     */
    public synchronized void connect(String ip, int port) {
        socket = connectToServer(ip, port);
        input = StreamsManager.createInput(socket, this.getClass());
        output = StreamsManager.createOutput(socket, this.getClass());
    }

    /**
     * Must be invoked after connect() method
     * @param name is the username for registering in the chat
     */
    public void register(String name) {
        output.println(name);
    }
    /**
     * receiving messages
     */
    private void receive() {
        new Thread(new ReceiveMessage()).start();
    }

    private void send() {
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        /** Sending messages */
        try {
            while (!stopped) {
                String message = ui.write();
                if (stopped) break;
                if (message.equals("")) continue;
                output.println(message);
                if (message.trim().equals("exit")) {
                    stopped = true;
                    break;
                }
            }
            close();
        } catch (IOException e) {
            System.out.println("Error closing socket");
            e.printStackTrace();
            /** аварийный выход */
        } catch (NoSuchElementException n) {
            stopped = true;
            output.println("exit");
        }
    }

    private Socket connectToServer(String ip, int port) {
        Socket socket = null;
        try {
            socket = new Socket(ip, port);
        } catch (IOException e) {
            System.out.println("Error creating socket in client");
            e.printStackTrace();
        }
        return socket;
    }

    private synchronized void close() throws IOException {
        StreamsManager.closeInput(input, this.getClass());
        StreamsManager.closeOutput(output);
        socket.close();
    }

    class ReceiveMessage implements Runnable {
        @Override


    public void run() {
            while (!stopped) {
                try {
                    String receive = input.readLine();
                    if (receive != null) {
                        switch (receive) {
                            case AppConstants.REPEATED_NAME_ERROR:
                                System.out.println(AppConstants.REPEATED_NAME_MESSAGE);
                                register(ui.getClientName());
                                break;
                            case AppConstants.OK_REGISTERED:
                                isRegistered = true;
                                break;
                            case "MAX":
                                System.out.println("Достигнуто максимальное количество пользователей");
                                stopped = true;
                                break;
                            case "exit":
                                break;
                            case "denied":
                                System.out.println("Сервер недоступен");
                                stopped = true;
                                break;
                            default:
                                System.out.println(receive);
                        }
                    } else {
                        System.out.println(AppConstants.SERVER_UNAVAILABLE_MESSAGE);
                        close();
                        break;
                    }
                } catch (IOException e) {
                    stopped = true;
                    System.out.println("Error receiving message from server ");
                    e.printStackTrace();
                }
            }
        }
    }
}

这里是负责表示的代码和平(不要注意字段GUI gui,这只是我尝试在逻辑和表示之间创建的另一个层,也许将来这不会是使用):

public class Frame extends JFrame {

private JButton confirm;
private JButton cancel;
private JTextField nameText;
private JTextField ipText;
private JComboBox<Integer> portChooser;
private GUI gui;
private Client client;
private int port;

//.......

     public Frame() {
        gui = new GUI();
        client = new Client(gui);
        //............
    }

 confirm.addActionListener(new ActionListener() {
        @Override
        public void actionPerformed(ActionEvent e) {
            gui.setName(nameText.getText());
            gui.setIp(ipText.getText());


            new EventQueue().invokeLater(new Runnable() {
                @Override
                public void run() {
                    client.connect(ipText.getText(), port);
                    client.register(nameText.getText());
                    //WHAT TO DO???
                }
            });
        }
    });
}

我想我应该展示一些来自 Server.java 的代码:

public class Server {


private ServerSocket server;
private final Map<String, ServerThread> clients = Collections.synchronizedMap(new TreeMap<>());
private final Queue<String> history = new ConcurrentLinkedQueue<>();

public Server() {
    System.out.println("Server is running...");
    Log.write("Server is running...");

    new Thread(new ServerMenu(this)).start();

    try {
        server = new ServerSocket(AppConfig.PORT);
    } catch (IOException e) {
        System.out.println("Error creating server");
        Log.write("Error creating server");
        Log.write(e.getMessage());
        e.printStackTrace();
    }


    while (true) {
        try {
            Socket accept = server.accept();
            Log.write("server accept socket");
            ServerThread serverThread = new ServerThread(accept);
            new Thread(serverThread).start();
            Log.write("server start new ServerThread");
        } catch (IOException e) {
            System.out.println("Error accepting client on server");
            Log.write("Error accepting client on server");
            Log.write(e.getMessage());
            e.printStackTrace();
        }
    }
}

private class ServerThread implements Runnable {

    private Socket socket;
    private BufferedReader in;
    private PrintWriter out;
    private String name;

    public ServerThread(Socket socket) {
        this.socket = socket;
        in = StreamsManager.createInput(socket, this.getClass());
        out = StreamsManager.createOutput(socket, this.getClass());
    }

    @Override
    public void run() {
        try {
            boolean goFurther = true; /*for emergency exit*/
            /** firstly, receive client name" */
            try {
                goFurther = readClientName();
            } catch (IOException e) {
                System.out.println("Error reading name from client...");
                Log.write("Error reading name from client...");
                Log.write(e.getMessage());
                e.printStackTrace();
            }
            if (goFurther) {
                String time = getTimeWithoutMillis(LocalTime.now());
                String invitation = time + " " + name + " has joined";
                printHistory();
                addToHistory(invitation);

                System.out.println(time + "  " + name + " has joined");
                System.out.println("numbers of users: " + clients.size());
                resendMessage(invitation);

                /** read from input stream */
                while (true) {
                    String received = null;
                    try {
                        received = in.readLine();
                        time = getTimeWithoutMillis(LocalTime.now());
                    } catch (IOException e) {
                        System.out.println("Error reading message from client...");
                        Log.write("Error reading message from client...");
                        Log.write(e.getMessage());
                        e.printStackTrace();
                    }
                    if (received == null) {
                        Log.write("received message from client is null");
                        break;
                    }

                    if (!received.trim().equals("exit")) {
                        String local = time + " " + name + ": " + received;
                        resendMessage(local);
                        addToHistory(local);
                    } else {
                        received = time + " " + name + " exit from chat";
                        addToHistory(received);
                        resendMessage(received);
                        out.println("exit");
                        System.out.println(received);
                        Log.write(received);
                        break;
                    }
                }
            }
        } finally {
            try {
                closeConnection();
                clients.remove(name);
            } catch (IOException e) {
                System.out.println("Error closing socket on server side");
                Log.write("Error closing socket on server side");
                Log.write(e.getMessage());
                e.printStackTrace();
            }
        }
    }

    private void printHistory() {
        synchronized (history) {
            history.forEach(out::println);
        }
    }

    private boolean readClientName() throws IOException {
        boolean continueProgram = true;
        while (true) {
            name = in.readLine();
            if (name == null) {
                continueProgram = false;
                Log.write("read name is null");
                break;
            }
            if (!(clients.size() < AppConfig.MAX_USERS)) {
                out.println("MAX");
                continueProgram = false;
                Log.write("reduce register new connection");
                break;
            }
            if (clients.get(name) == null) {
                clients.put(name, this);
                Log.write("register new user with the name: " + name);
                out.println(AppConstants.OK_REGISTERED);
                break;
            } else {
                out.println(AppConstants.REPEATED_NAME_ERROR);
                out.print("> ");
            }
        }
        return continueProgram;
    }
    //.....................
}

最佳答案

将请求 IP 地址和用户名的代码移至隔离类。它不应该打开连接,它应该只是收集数据。

当它拥有所有数据时,它应该调用回调(即在confirmActionListener中,调用另一个监听器)。

回调需要如下所示:

client.connect(data.getAddress(), data.getPort());
try {
    client.register(data.getUserName());
} catch( DuplicateNameException e ) {
    data.setError( "Name already taken" );
    data.show();
    return;
}

如果出现问题,这将再次打开对话框(包含用户已输入的数据)。您可以在 client.connect() 期间对异常使用相同的方法。

此外,客户端应该检查是否已经存在 Activity 连接,并且本地址和端口仍然相同时不要再次连接。或者,您可以在 client.connect() 中检查 Activity 连接并将其关闭。

关于java - 如何在多线程聊天中使用 swing 处理输入重复名称错误,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25664083/

相关文章:

java - 简单的 Web Java 应用程序可与 heroku 本地 Web 配合使用,但部署时会崩溃

python - 致命的 Python 错误 : PyImport_GetModuleDict: no module dictionary

Java8 - 将异步接口(interface)转换为同步接口(interface)

java - 如何添加鼠标松开事件?

java - Java 中的 "caller"和 Ruby 中的 "receiver"一样吗?

带有通配符的 Java 自引用泛型

java - 链表以一种数据类型显示数据

linux - linux调度进程还是线程?

java - Jframe 与 XML GUI

java - 卡住 JXTable 中的列