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

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

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") //  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(); }//  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 */ // /* 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); } // /* 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

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

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

SocketReader

所有这个类都是从Socket读取文本并从文本生成ActionEvent ,简单。

 public class SocketReader extends SwingWorker { private List 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 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 { private List messages; private ReentrantLock lock; private Condition waitCon; public SocketWriter() { messages = Collections.synchronizedList(new ArrayList(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 cache = new ArrayList<>(messages); messages.clear(); for (String text : cache) { System.out.println("Send " + text); out.writeUTF(text); } } } return null; } } 

SocketManager

好吧,这对我来说有点矫枉过正,但我​​想要一个Socket的中央控制器,你不必使用单例,你可以简单地把它变成一个简单的类并将它的引用传递给你的ChatClient和on down到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并将你想发送的文本发送到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") //  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(); }//  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 */ // /* 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); } // 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(); } // } 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中的并发和All About Sockets