繁体   English   中英

Java Swing GUI客户端和服务器聊天应用程序TextArea未更新

[英]Java Swing GUI Client and Server Chat App TextArea not updating

我正在使用Java和针对Java的Swing类进行聊天应用程序。

ChatServer类将是从客户端接收消息并回显所有客户端的服务器,但我仅打算为2个客户端进行聊天。

ChatClient类都是客户端。 它们在“文本区域”上显示从服务器发送的内容。 并将“文本字段”中的文本发送到服务器。

ChatClient类

package chatclient;

import java.net.Socket;
import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.InputStreamReader;

public class ChatClient extends javax.swing.JFrame {


public ChatClient() {
    initComponents();
}

/**
 * This method is called from within the constructor to initialize the form.
 * WARNING: Do NOT modify this code. The content of this method is always
 * regenerated by the Form Editor.
 */
@SuppressWarnings("unchecked")
// <editor-fold defaultstate="collapsed" desc="Generated Code">                          
private void initComponents() {

    scrollPane = new javax.swing.JScrollPane();
    textArea = new javax.swing.JTextArea();
    btnConnect = new javax.swing.JButton();
    btnDisconnect = new javax.swing.JButton();
    lblStatus = new javax.swing.JLabel();
    lblShowStatus = new javax.swing.JLabel();
    txtInput = new javax.swing.JTextField();

    setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
    setTitle("Chat Client A");

    textArea.setEditable(false);
    textArea.setColumns(20);
    textArea.setRows(5);
    textArea.setText("Welcome to the Chat Server. Type '/close' or Click 'Disconnect' to close.");
    textArea.setWrapStyleWord(true);
    textArea.setCaretPosition(textArea.getDocument().getLength());
    scrollPane.setViewportView(textArea);

    btnConnect.setText("Connect");
    btnConnect.setActionCommand("btnConnect");
    btnConnect.addMouseListener(new java.awt.event.MouseAdapter() {
        public void mouseClicked(java.awt.event.MouseEvent evt) {
            btnConnectMouseClicked(evt);
        }
    });

    btnDisconnect.setText("Disconnect");
    btnDisconnect.setActionCommand("btnDisconnect");
    btnDisconnect.addActionListener(new java.awt.event.ActionListener() {
        public void actionPerformed(java.awt.event.ActionEvent evt) {
            btnDisconnectActionPerformed(evt);
        }
    });

    lblStatus.setText("Status: ");

    lblShowStatus.setFont(new java.awt.Font("Tahoma", 1, 11)); // NOI18N
    lblShowStatus.setForeground(new java.awt.Color(255, 51, 51));
    lblShowStatus.setText("Disconnected");

    txtInput.setToolTipText("");
    txtInput.addActionListener(new java.awt.event.ActionListener() {
        public void actionPerformed(java.awt.event.ActionEvent evt) {
            txtInputActionPerformed(evt);
        }
    });

    javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
    getContentPane().setLayout(layout);
    layout.setHorizontalGroup(
        layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
        .addGroup(layout.createSequentialGroup()
            .addContainerGap()
            .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                .addComponent(scrollPane)
                .addGroup(layout.createSequentialGroup()
                    .addComponent(btnConnect)
                    .addGap(18, 18, 18)
                    .addComponent(btnDisconnect)
                    .addGap(42, 42, 42)
                    .addComponent(lblStatus)
                    .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                    .addComponent(lblShowStatus)
                    .addGap(0, 42, Short.MAX_VALUE))
                .addComponent(txtInput))
            .addContainerGap())
    );
    layout.setVerticalGroup(
        layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
        .addGroup(layout.createSequentialGroup()
            .addContainerGap()
            .addComponent(scrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, 213, javax.swing.GroupLayout.PREFERRED_SIZE)
            .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 11, Short.MAX_VALUE)
            .addComponent(txtInput, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
            .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
            .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                .addComponent(btnConnect)
                .addComponent(btnDisconnect)
                .addComponent(lblStatus)
                .addComponent(lblShowStatus))
            .addContainerGap())
    );

    pack();
}// </editor-fold>                        

private void btnConnectMouseClicked(java.awt.event.MouseEvent evt) {                                        
    // TODO add your handling code here:
    lblShowStatus.setFont(new java.awt.Font("Tahoma", 1, 11)); // NOI18N
    lblShowStatus.setForeground(new java.awt.Color(0, 204, 51));
    lblShowStatus.setText("Connected");

    // ADD CODES FOR CONNECTING TO CHAT SERVER

}                                       

private void btnDisconnectActionPerformed(java.awt.event.ActionEvent evt) {                                              
    // TODO add your handling code here:
    lblShowStatus.setFont(new java.awt.Font("Tahoma", 1, 11)); // NOI18N
    lblShowStatus.setForeground(new java.awt.Color(255, 51, 51));
    lblShowStatus.setText("Disconnected");

    // ADD CODES FOR DISCONNECTING FROM CHAT SERVER

}                                             

private void txtInputActionPerformed(java.awt.event.ActionEvent evt) {                                         
    // TODO add your handling code here:
}                                        

/**
 * @param args the command line arguments
 */
public static void main(String args[]) {
    /* Set the Nimbus look and feel */
    //<editor-fold defaultstate="collapsed" desc=" Look and feel setting code (optional) ">
    /* If Nimbus (introduced in Java SE 6) is not available, stay with the default look and feel.
     * For details see http://download.oracle.com/javase/tutorial/uiswing/lookandfeel/plaf.html 
     */
    try {
        for (javax.swing.UIManager.LookAndFeelInfo info : javax.swing.UIManager.getInstalledLookAndFeels()) {
            if ("Nimbus".equals(info.getName())) {
                javax.swing.UIManager.setLookAndFeel(info.getClassName());
                break;
            }
        }
    } catch (ClassNotFoundException ex) {
        java.util.logging.Logger.getLogger(ChatClient.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
    } catch (InstantiationException ex) {
        java.util.logging.Logger.getLogger(ChatClient.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
    } catch (IllegalAccessException ex) {
        java.util.logging.Logger.getLogger(ChatClient.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
    } catch (javax.swing.UnsupportedLookAndFeelException ex) {
        java.util.logging.Logger.getLogger(ChatClient.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
    }
    //</editor-fold>      

    /* Create and display the form */
    java.awt.EventQueue.invokeLater(new Runnable() {
        public void run() {
            new ChatClient().setVisible(true);
        }
    });

    String input = "", serverInput = "";
    String host = "localhost";
    int port = 1337;
    Socket client;

    // updateTextArea("TEST UPDATE");

    try {
        client = new Socket(host, port);
        System.out.println("Connected to Server!");

        DataInputStream in = new DataInputStream(client.getInputStream());
        DataOutputStream out = new DataOutputStream(client.getOutputStream());

        System.out.println("Before setting text area");
        updateTextArea("trying to update");

        do {
            // HANDLE INPUT PART HERE
            serverInput = in.readUTF();


            if(serverInput != null) {
                System.out.println("Reached here");
                System.out.println(serverInput);
                updateTextArea(serverInput); 
            }

        } while(!input.equals("/close"));
        System.out.println("Program closed");
    }    

    catch(Exception exc) {
        System.err.println(exc.getMessage());
    }


}

private static void updateTextArea(String temp) {
    textArea.setText(textArea.getText() + "\n" + temp + "\n");
    textArea.setCaretPosition(textArea.getDocument().getLength());
}

// Variables declaration - do not modify                     
private javax.swing.JButton btnConnect;
private javax.swing.JButton btnDisconnect;
private javax.swing.JLabel lblShowStatus;
private javax.swing.JLabel lblStatus;
private javax.swing.JScrollPane scrollPane;
private static javax.swing.JTextArea textArea;
private javax.swing.JTextField txtInput;
// End of variables declaration                   
}

ChatServer类

package chatserver;

import java.io.BufferedReader;
import java.io.InputStreamReader;
// for testing
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.net.ServerSocket;
import java.net.Socket;

/**
 *
 * @author wacats
 */
public class ChatServer {
public static void main(String args[]) {
    int port = 1337;

    try {
        ServerSocket server = new ServerSocket(port);
        String inMessage = "";

        while(true) {
            Socket clientA = server.accept();
            DataInputStream inA = new DataInputStream(clientA.getInputStream());
            DataOutputStream outA = new DataOutputStream(clientA.getOutputStream());
            // outA.writeUTF("Welcome to the Chat Server. Type '/close' or Click 'Disconnect' to close.");

            // for testing
            // BufferedReader user = new BufferedReader(new InputStreamReader(System.in));

            do {
                inMessage = inA.readUTF();

                outA.writeUTF("testing");

                if(inMessage != null) {
                   outA.writeUTF(inMessage); 
                }

            } while(!inMessage.equals("/close"));
            clientA.close();
        }



    }

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

我对这个程序的过程的想法是:

  1. 启动ChatServer
  2. 启动ChatClient
  3. 当两个客户端都连接到服务器时,它们可以开始聊天。
  4. 按下“ Enter”将把“文本字段”中的文本发送到服务器。
  5. 服务器会将文本广播到两个客户端。
  6. 客户端将更新文本区域,附加从服务器接收的文本。

基本问题是,当您尝试调用updateTextArea时, textAreanull ,这是因为invokeLater调用尚未执行并构造了UI,您基本上具有竞争条件。

在Swing中处理Socket的通常方法是使用SwingWorker

有关更多详细信息,请查看SwingWorker线程中的并发性 和SwingWorker

您可以通过多种方式解决问题。 您可以使用SwingWorker从套接字读取文本并生成更新通知(通过publish / process )方法,这通常称为“观察者模式”。 这很好,因为它可以使您的代码脱钩并生成更可重用的解决方案。

SocketReader

该类所做的全部工作就是从Socket读取文本,并从该文本生成ActionEvent ,非常简单。

public class SocketReader extends SwingWorker<Void, String> {

    private List<ActionListener> actionListeners;

    public SocketReader() {
        actionListeners = new ArrayList<>(25);
    }

    public void addActionListener(ActionListener listener) {
        actionListeners.add(listener);
    }

    public void removeActionListener(ActionListener listener) {
        actionListeners.remove(listener);
    }

    @Override
    protected Void doInBackground() throws Exception {
        System.out.println("Connected to Server!");

        try (DataInputStream in = new DataInputStream(SocketManager.INSTACNE.getInputStream())) {

            System.out.println("Before setting text area");

            String serverInput = null;
            do {
                // HANDLE INPUT PART HERE
                serverInput = in.readUTF();

                if (serverInput != null) {
                    System.out.println("Read " + serverInput);
                    publish(serverInput);
                }

            } while (!serverInput.equals("/close"));
            System.out.println("Program closed");
        }
        return null;
    }

    @Override
    protected void process(List<String> chunks) {
        for (String text : chunks) {
            ActionEvent evt = new ActionEvent(this, ActionEvent.ACTION_PERFORMED, text);
            for (ActionListener listener : actionListeners) {
                listener.actionPerformed(evt);
            }
        }
    }

}

SocketWriter

这个简单的方法将文本写入Socket 从技术上讲,您不需要为此使用SwingWorker ,但是我喜欢这样的事实:取消它相对容易

public class SocketWriter extends SwingWorker<Void, Void> {

    private List<String> messages;
    private ReentrantLock lock;
    private Condition waitCon;

    public SocketWriter() {
        messages = Collections.synchronizedList(new ArrayList<String>(25));
        lock = new ReentrantLock();
        waitCon = lock.newCondition();
    }

    public void write(String text) {
        System.out.println("Write " + text);
        messages.add(text);
        try {
            lock.lock();
            waitCon.signalAll();
        } finally {
            lock.unlock();
        }
    }

    @Override
    protected Void doInBackground() throws Exception {
        try (DataOutputStream out = new DataOutputStream(SocketManager.INSTACNE.getOutputStream())) {
            while (!isCancelled()) {
                while (messages.isEmpty() && !isCancelled()) {
                    try {
                        lock.lock();
                        waitCon.await();
                    } finally {
                        lock.unlock();
                    }
                }
                List<String> cache = new ArrayList<>(messages);
                messages.clear();
                for (String text : cache) {
                    System.out.println("Send " + text);
                    out.writeUTF(text);
                }
            }
        }
        return null;
    }

}

SocketManager

好的,这对我来说有点过大了,但是我想要一个用于Socket的中央控制器,您不必使用单例,您可以简单地使其成为一个简单的类,并将其引用传递给ChatClient ,然后向下到SocketReader/Writer ,但是已经晚了,我很懒

public enum SocketManager {
    INSTACNE;

    private String host = "localhost";
    private int port = 1337;
    private Socket socket;

    public Socket open() throws IOException {
        if (socket != null) {
            close();
        }
        socket = new Socket(host, port);
        return socket;
    }

    public void close() throws IOException {
        if (socket == null) {
            return;
        }
        socket.close();
    }

    public boolean isOpen() {
        return socket != null
            && socket.isConnected()
            && !socket.isClosed()
            && !socket.isInputShutdown()
            && !socket.isOutputShutdown();
    }

    public InputStream getInputStream() throws IOException {
        Objects.requireNonNull(socket, "Socket is not open");
        return socket.getInputStream();
    }

    public OutputStream getOutputStream() throws IOException {
        Objects.requireNonNull(socket, "Socket is not open");
        return socket.getOutputStream();
    }
}

ChatClient

很棒,一切都很好,但是您打算如何使用它呢?

基本上,将在ChatClient创建SocketReaderSocketWriter的实例,将ActionListener附加到阅读器,并在触发JTextArea时更新JTextArea ,然后将要发送的文本发送到SocketWriter ,例如...

public class ChatClient extends javax.swing.JFrame {

    public ChatClient() {
        initComponents();
        socketReader = new SocketReader();
        socketReader.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                String text = e.getActionCommand();
                textArea.append(text);
                textArea.append("\n");
                textArea.setCaretPosition(textArea.getDocument().getLength());
            }
        });
        socketReader.execute();

        socketWriter = new SocketWriter();
        socketWriter.execute();
    }

    /**
     * This method is called from within the constructor to initialize the form.
     * WARNING: Do NOT modify this code. The content of this method is always
     * regenerated by the Form Editor.
     */
    @SuppressWarnings("unchecked")
    // <editor-fold defaultstate="collapsed" desc="Generated Code">                          
    private void initComponents() {

        scrollPane = new javax.swing.JScrollPane();
        textArea = new javax.swing.JTextArea();
        btnConnect = new javax.swing.JButton();
        btnDisconnect = new javax.swing.JButton();
        lblStatus = new javax.swing.JLabel();
        lblShowStatus = new javax.swing.JLabel();
        txtInput = new javax.swing.JTextField();

        setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
        setTitle("Chat Client A");

        textArea.setEditable(false);
        textArea.setColumns(20);
        textArea.setRows(5);
        textArea.setText("Welcome to the Chat Server. Type '/close' or Click 'Disconnect' to close.");
        textArea.setWrapStyleWord(true);
        textArea.setCaretPosition(textArea.getDocument().getLength());
        scrollPane.setViewportView(textArea);

        btnConnect.setText("Connect");
        btnConnect.setActionCommand("btnConnect");
        btnConnect.addMouseListener(new java.awt.event.MouseAdapter() {
            public void mouseClicked(java.awt.event.MouseEvent evt) {
                btnConnectMouseClicked(evt);
            }
        });

        btnDisconnect.setText("Disconnect");
        btnDisconnect.setActionCommand("btnDisconnect");
        btnDisconnect.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                btnDisconnectActionPerformed(evt);
            }
        });

        lblStatus.setText("Status: ");

        lblShowStatus.setFont(new java.awt.Font("Tahoma", 1, 11)); // NOI18N
        lblShowStatus.setForeground(new java.awt.Color(255, 51, 51));
        lblShowStatus.setText("Disconnected");

        txtInput.setToolTipText("");
        txtInput.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                txtInputActionPerformed(evt);
            }
        });

        javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
        getContentPane().setLayout(layout);
        layout.setHorizontalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(layout.createSequentialGroup()
                .addContainerGap()
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                    .addComponent(scrollPane)
                    .addGroup(layout.createSequentialGroup()
                        .addComponent(btnConnect)
                        .addGap(18, 18, 18)
                        .addComponent(btnDisconnect)
                        .addGap(42, 42, 42)
                        .addComponent(lblStatus)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addComponent(lblShowStatus)
                        .addGap(0, 42, Short.MAX_VALUE))
                    .addComponent(txtInput))
                .addContainerGap())
        );
        layout.setVerticalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(layout.createSequentialGroup()
                .addContainerGap()
                .addComponent(scrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, 213, javax.swing.GroupLayout.PREFERRED_SIZE)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 11, Short.MAX_VALUE)
                .addComponent(txtInput, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                    .addComponent(btnConnect)
                    .addComponent(btnDisconnect)
                    .addComponent(lblStatus)
                    .addComponent(lblShowStatus))
                .addContainerGap())
        );

        pack();
    }// </editor-fold>                        

    private void btnConnectMouseClicked(java.awt.event.MouseEvent evt) {
        // TODO add your handling code here:
        lblShowStatus.setFont(new java.awt.Font("Tahoma", 1, 11)); // NOI18N
        lblShowStatus.setForeground(new java.awt.Color(0, 204, 51));
        lblShowStatus.setText("Connected");

        // ADD CODES FOR CONNECTING TO CHAT SERVER
    }

    private void btnDisconnectActionPerformed(java.awt.event.ActionEvent evt) {
        // TODO add your handling code here:
        lblShowStatus.setFont(new java.awt.Font("Tahoma", 1, 11)); // NOI18N
        lblShowStatus.setForeground(new java.awt.Color(255, 51, 51));
        lblShowStatus.setText("Disconnected");

        // ADD CODES FOR DISCONNECTING FROM CHAT SERVER
    }

    private void txtInputActionPerformed(java.awt.event.ActionEvent evt) {
        if (SocketManager.INSTACNE.isOpen()) {
            socketWriter.write(txtInput.getText());
        } else {
            System.out.println("!! Not open");
        }
    }

    /**
     * @param args the command line arguments
     */
    public static void main(String args[]) {

        try {
            /* Set the Nimbus look and feel */
            //<editor-fold defaultstate="collapsed" desc=" Look and feel setting code (optional) ">
            /* If Nimbus (introduced in Java SE 6) is not available, stay with the default look and feel.
            * For details see http://download.oracle.com/javase/tutorial/uiswing/lookandfeel/plaf.html
             */
            try {
                for (javax.swing.UIManager.LookAndFeelInfo info : javax.swing.UIManager.getInstalledLookAndFeels()) {
                    if ("Nimbus".equals(info.getName())) {
                        javax.swing.UIManager.setLookAndFeel(info.getClassName());
                        break;
                    }
                }
            } catch (ClassNotFoundException ex) {
                java.util.logging.Logger.getLogger(ChatClient.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
            } catch (InstantiationException ex) {
                java.util.logging.Logger.getLogger(ChatClient.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
            } catch (IllegalAccessException ex) {
                java.util.logging.Logger.getLogger(ChatClient.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
            } catch (javax.swing.UnsupportedLookAndFeelException ex) {
                java.util.logging.Logger.getLogger(ChatClient.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
            }
            //</editor-fold>

            SocketManager.INSTACNE.open();

            /* Create and display the form */
            java.awt.EventQueue.invokeLater(new Runnable() {
                public void run() {
                    new ChatClient().setVisible(true);
                }
            });

        } catch (IOException ex) {
            ex.printStackTrace();
        }
        //</editor-fold>      

    }

    private SocketWriter socketWriter;
    private SocketReader socketReader;

    // Variables declaration - do not modify                     
    private javax.swing.JButton btnConnect;
    private javax.swing.JButton btnDisconnect;
    private javax.swing.JLabel lblShowStatus;
    private javax.swing.JLabel lblStatus;
    private javax.swing.JScrollPane scrollPane;
    private javax.swing.JTextArea textArea;
    private javax.swing.JTextField txtInput;
    // End of variables declaration                   
}

您会注意到,我在main使用SocketManager#open ,抱歉,错过了您的“连接”代码。 我建议改为将其移至该方法;)

ChatServer

我对此没有做太多改变,但以防万一...

public class ChatServer {

    public static void main(String args[]) {
        int port = 1337;

        try {
            ServerSocket server = new ServerSocket(port);
            String inMessage = "";

            while (true) {
                System.out.println("Waiting");
                Socket clientA = server.accept();
                System.out.println("Connected");
                DataInputStream inA = new DataInputStream(clientA.getInputStream());
                DataOutputStream outA = new DataOutputStream(clientA.getOutputStream());
                // outA.writeUTF("Welcome to the Chat Server. Type '/close' or Click 'Disconnect' to close.");

                // for testing
                // BufferedReader user = new BufferedReader(new InputStreamReader(System.in));
                do {
                    inMessage = inA.readUTF();

                    if (inMessage != null) {
                        outA.writeUTF(inMessage);
                    }

                } while (!inMessage.equals("/close"));
                clientA.close();
            }

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

通常,当客户端连接时,您将启动一个新的Thread并使其处理客户端Socket ,但这不是我的重点。

因此,基于所有这些,您需要大量阅读,包括Java中的并发性全部关于套接字

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM