There was a task of writing a network chat on sockets. There is a server that waits for a connection from clients in an infinite loop. When a client connects, it creates a separate data flow for it, here is its code: package serverapp;

import clientapp.Message; import clientapp.Wrapper; import clientapp.Client; import java.io.Console; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.net.ServerSocket; import java.net.Socket; import java.util.ArrayList; import java.io.FileInputStream; import java.io.FileOutputStream; import java.util.List; // server public class ServerApp {ObjectOutputStream out; // output stream to be added to the array clientOutputStreams ArrayList clientOutputStreams; // list of streams ArrayList listClients; // list of registered users

public static void main(String[] args) { new ServerApp().go();//запускаем сервер } public void go(){ clientOutputStreams = new ArrayList<ObjectOutputStream>(); listClients = new ArrayList<Client>(); //инициализируем списки //при запуске сервера считываем из файла данные о предыдущих регистрациях try { FileInputStream fis = new FileInputStream("temp.out"); ObjectInputStream oin = new ObjectInputStream(fis); listClients = (ArrayList<Client>) oin.readObject(); //считываем данные //если массив не пустой то создаем количество выходных потоков в таком же количестве как количество зарегистрированных пользователей if(!listClients.isEmpty()){ for(int i = 0; i < listClients.size(); i++){ clientOutputStreams.add(null); } //стандартный вывод в окно отладки о количестве зарегистрированных пользователей System.err.println("В сети зарегистрированы " + listClients.size() + " клиент(ов):"); for(int i = 0; i < listClients.size(); i++){ System.err.println(i+1 + ") " + listClients.get(i).getLogin()); } } else{ System.err.println("В сети нет зарегистрированных пользователей"); } } catch (Exception e) { System.out.println("Не удалось загрузить файл temp.out"); } try { ServerSocket serverSocket = new ServerSocket(5000); while (true) { Socket clientSocket = serverSocket.accept(); //ждем подключения out = new ObjectOutputStream(clientSocket.getOutputStream()); //если клиент подключился создаем для него отдельный поток, в который передаем сокет и out-ObjectOutputStream Thread t = new Thread(new ClientHandler(clientSocket, out)); t.start(); } } catch(Exception ex) { } } class ClientHandler implements Runnable{ ObjectInputStream in; Socket sock; ObjectOutputStream outStream; int index; //индекс текушего outStream в массиве clientOutputStreams public ClientHandler(Socket clientSocket, ObjectOutputStream out){ try{ sock = clientSocket; in = new ObjectInputStream(clientSocket.getInputStream()); outStream = out; } catch(Exception ex) { ex.printStackTrace(); } } public void run(){ Wrapper o = null; try { //ожидаем различные данные от клиентов while ((o = (Wrapper)in.readObject()) != null) { //если пользователь хочет войти как зарегистрированный то if(o.getMarker().equals("ENTRANCE")){ checkClient(o.getClient()); //проверяем имеется ли он в массиве listClients } //если пользователь хочет зарегистрироваться else if(o.getMarker().equals("REGISTRATION")){ Client newClient = o.getClient(); // то получаем от него логин и пароль //проверяем зарегистрирован ли такой пользователь в сети, если имя уникально то if(checkValidation(newClient)){ newClient.setStatus(true); //устанавливаем новому клиенту статут онлайн listClients.add(newClient); //добавляем в список зарегистрированных пользователей clientOutputStreams.add(outStream); //добавляем текущий outStream в clientOutputStream index = listClients.size()-1; //запоминаем индекс текущего потока для clientOutputStream System.out.println("'" + newClient.getLogin() + "' " + "был зарегистрирован"); //отправляем клиенту сообщение о том, что он может войти sendMarker("CANENTRANCE"); } //если имя не уникально else{ System.out.println("Отказано в регистрации для " + "'" + newClient.getLogin() + "'" ); //отпраляем клиенту сообщение о том, что он не может зарегистрироваться sendMarker("NOTREGISRATED"); } } } } catch(Exception ex){ //при отключении пользователя сохраняем listClients в файл saveData(); System.out.println("'" + listClients.get(index).getLogin() + "'" + " вышел"); } } //проверяем уникальность имени private boolean checkValidation(Client client){ if(!listClients.isEmpty()){ for(int i = 0; i < listClients.size(); i++){ if(listClients.get(i).getLogin().equals(client.getLogin())){ return false; } } return true; } else{ return true; } } //сериализуем listClient в файл temp.out private void saveData(){ try { FileOutputStream fos = new FileOutputStream("temp.out"); ObjectOutputStream oos = new ObjectOutputStream(fos); oos.writeObject(listClients); oos.flush(); oos.close(); } catch (Exception e) { System.out.println("Не удалось сохранить данные в файл temp.out"); } } //проверка наличия пользователя в списке зарегистрированных клиентов private void checkClient(Client client){ boolean isRegistered = false; if(listClients.isEmpty()){ sendMarker("NOTREGISRATED"); } else{ //идем по всему списку for(int i = 0; i < listClients.size(); i++){ //если нашли совпадение то if(listClients.get(i).getLogin().equals(client.getLogin()) && listClients.get(i).getPassword().equals(client.getPassword())) { isRegistered = true; clientOutputStreams.set(i, outStream); index = i; // запоминаем индекс System.out.println("'" + listClients.get(i).getLogin() + "'" + " вошел"); //говорим клиенту о том, что он может войти sendMarker("CANENTRANCE"); } } if(!isRegistered){ sendMarker("NOTREGISRATED"); } } } //отправляет системные сообщения клиентам private void sendMarker(String marker){ try { out.writeUnshared(new Wrapper(marker)); out.reset(); } catch (Exception e) { System.err.println("Не удалось отправить сообщение клиенту"); } } } 

} The server code is maximally commented out for clarity, everything should be clear there. For the exchange of any data between the server and the client, a wrapper class is used, which will later be improved, as well as transmitting various system messages. Here is his code:

package clientapp;

import java.io.Serializable; // wrapper for other classes public class Wrapper implements Serializable {String marker; // system message Client client; // client // overloaded constructor public Wrapper (Client client, String marker) {this.marker = marker; this.client = client; } public Wrapper (String marker) {this.marker = marker; } // get the system message public String getMarker () {return marker; } // get the client public Client getClient () {Return client; }}

And the client itself consists of the login window, the registration window and the chat itself. The login window: public class RegWindow extends javax.swing.JFrame {Socket sock; // socket ObjectInputStream in; ObjectOutputStream out; Client client; Thread readerThread; // stream that will receive data from the server

 public RegWindow() { initComponents(); SetUpNetworking(); //создаем поток на считывание данных от сервера readerThread = new Thread(new IncomingReader()); readerThread.start(); } @SuppressWarnings("unchecked") // <editor-fold defaultstate="collapsed" desc="Generated Code"> private void initComponents() { jLabel1 = new javax.swing.JLabel(); jTextField1 = new javax.swing.JTextField(); jLabel2 = new javax.swing.JLabel(); jPasswordField1 = new javax.swing.JPasswordField(); jButton1 = new javax.swing.JButton(); jButton2 = new javax.swing.JButton(); setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE); setBounds(new java.awt.Rectangle(960, 300, 0, 0)); jLabel1.setFont(new java.awt.Font("Tahoma", 0, 14)); // NOI18N jLabel1.setText("Логин"); jTextField1.setFont(new java.awt.Font("Tahoma", 0, 14)); // NOI18N jTextField1.setText("user"); jTextField1.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { jTextField1ActionPerformed(evt); } }); jLabel2.setFont(new java.awt.Font("Tahoma", 0, 14)); // NOI18N jLabel2.setText("Пароль"); jPasswordField1.setText("1234"); jButton1.setText("Войти"); jButton1.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { jButton1ActionPerformed(evt); } }); jButton2.setText("Регистрация"); jButton2.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { jButton2ActionPerformed(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() .addGap(30, 30, 30) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false) .addComponent(jLabel2) .addComponent(jLabel1) .addComponent(jButton1, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) .addComponent(jTextField1) .addComponent(jPasswordField1, javax.swing.GroupLayout.Alignment.TRAILING) .addComponent(jButton2, javax.swing.GroupLayout.DEFAULT_SIZE, 119, Short.MAX_VALUE)) .addContainerGap(29, Short.MAX_VALUE)) ); layout.setVerticalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createSequentialGroup() .addContainerGap() .addComponent(jLabel1) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) .addComponent(jTextField1, javax.swing.GroupLayout.PREFERRED_SIZE, 30, javax.swing.GroupLayout.PREFERRED_SIZE) .addGap(18, 18, 18) .addComponent(jLabel2) .addGap(18, 18, 18) .addComponent(jPasswordField1, javax.swing.GroupLayout.PREFERRED_SIZE, 29, javax.swing.GroupLayout.PREFERRED_SIZE) .addGap(18, 18, 18) .addComponent(jButton1, javax.swing.GroupLayout.PREFERRED_SIZE, 48, javax.swing.GroupLayout.PREFERRED_SIZE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) .addComponent(jButton2, javax.swing.GroupLayout.PREFERRED_SIZE, 37, javax.swing.GroupLayout.PREFERRED_SIZE) .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) ); pack(); }// </editor-fold> //устанавливаем соединение private void SetUpNetworking() { try{ sock = new Socket("192.168.1.37",5000); out = new ObjectOutputStream(sock.getOutputStream()); in = new ObjectInputStream(sock.getInputStream()); System.out.println("networking established"); } catch(IOException ex){ System.out.println("Не удалось подключиться к серверу"); } } public class IncomingReader implements Runnable{ public synchronized void run(){ Wrapper o; try{ while((o = (Wrapper)in.readObject()) != null){ //если сервер говорит клиенту, что таких данных как у него не найдено listClient, то показать всплывающее окно if(o.getMarker().equals("NOTREGISRATED")){ JOptionPane.showMessageDialog(null, "Вы неправильно ввели логин или пароль", "Ошибка входа", JOptionPane.PLAIN_MESSAGE); } //если сервер нашел пользователя с такими данными то создать окно чата else if(o.getMarker().equals("CANENTRANCE")){ new ClientWindow(sock,in,out).setVisible(true); setVisible(false); } } } catch(Exception ex){ //System.out.println("RegWindow"); ex.printStackTrace(); } } } private void jTextField1ActionPerformed(java.awt.event.ActionEvent evt) { // TODO add your handling code here: } //КНОПКА ВХОДА private void jButton1ActionPerformed(java.awt.event.ActionEvent evt) { //считываем логин и пароль из textboxОВ client = new Client(jTextField1.getText(), jPasswordField1.getText()); try { //Отправляем данные на сервер для проверки out.writeUnshared(new Wrapper(client, "ENTRANCE")); out.reset(); } catch (Exception e) { System.out.println("Не удалось отправить логин и пароль на сервер"); } } //КНОПКА РЕГИСТРАЦИИ private void jButton2ActionPerformed(java.awt.event.ActionEvent evt) { // TODO add your handling code here: //если пользователь хочет зарегистрироваться создаем окно регистрации //и !!! передаем в него sock, in и out из текущего класса new Registration(sock,in,out).setVisible(true); this.setVisible(false); } 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(RegWindow.class.getName()).log(java.util.logging.Level.SEVERE, null, ex); } catch (InstantiationException ex) { java.util.logging.Logger.getLogger(RegWindow.class.getName()).log(java.util.logging.Level.SEVERE, null, ex); } catch (IllegalAccessException ex) { java.util.logging.Logger.getLogger(RegWindow.class.getName()).log(java.util.logging.Level.SEVERE, null, ex); } catch (javax.swing.UnsupportedLookAndFeelException ex) { java.util.logging.Logger.getLogger(RegWindow.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 RegWindow().setVisible(true); } }); } // Variables declaration - do not modify private javax.swing.JButton jButton1; private javax.swing.JButton jButton2; private javax.swing.JLabel jLabel1; private javax.swing.JLabel jLabel2; private javax.swing.JPasswordField jPasswordField1; private javax.swing.JTextField jTextField1; // End of variables declaration 

} When you click the register button, a registration window opens, which receives the same socket, ObjectOutputStream and ObjectInputStream, as the login window. Here is the registration window code. package clientapp;

import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.net.Socket; import java.util.ArrayList; import javax.swing.JOptionPane;

public class Registration extends javax.swing.JFrame {

 Socket socket; ObjectInputStream in; ObjectOutputStream out; //конструктор public Registration(Socket socket, ObjectInputStream in, ObjectOutputStream out) { initComponents(); this.socket = socket; this.in = in; this.out = out; Thread readerThread = new Thread(new Registration.IncomingReader1()); readerThread.start(); } public class IncomingReader1 implements Runnable{ public void run(){ Wrapper o; try{ //считываем данные, которые отправляет сервер к окну регистрации while((o = (Wrapper) in.readObject()) != null){ if(o.getMarker().equals("NOTREGISRATED")){ JOptionPane.showMessageDialog(null, "Данный логин уже используется", "Ошибка регистрации", JOptionPane.PLAIN_MESSAGE); } //если сервер разрешил регистрацию то создаем окно чата else if(o.getMarker().equals("CANENTRANCE")){ createClientWindow(); } } } catch(Exception ex){ //System.out.println("Registration"); ex.printStackTrace(); } } } @SuppressWarnings("unchecked") // <editor-fold defaultstate="collapsed" desc="Generated Code"> private void initComponents() { jLabel1 = new javax.swing.JLabel(); jLabel2 = new javax.swing.JLabel(); jLabel3 = new javax.swing.JLabel(); jLabel4 = new javax.swing.JLabel(); jTextField1 = new javax.swing.JTextField(); jTextField2 = new javax.swing.JTextField(); jTextField3 = new javax.swing.JTextField(); jButton1 = new javax.swing.JButton(); jPasswordField1 = new javax.swing.JPasswordField(); setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE); setBounds(new java.awt.Rectangle(960, 300, 0, 0)); jLabel1.setFont(new java.awt.Font("Tahoma", 0, 14)); // NOI18N jLabel1.setText("Введите имя"); jLabel2.setFont(new java.awt.Font("Tahoma", 0, 14)); // NOI18N jLabel2.setText("Введите фамилию"); jLabel3.setFont(new java.awt.Font("Tahoma", 0, 14)); // NOI18N jLabel3.setText("Введите логин"); jLabel4.setFont(new java.awt.Font("Tahoma", 0, 14)); // NOI18N jLabel4.setText("Введите пароль"); jTextField1.setFont(new java.awt.Font("Tahoma", 0, 14)); // NOI18N jTextField1.setText("Евгений"); jTextField2.setFont(new java.awt.Font("Tahoma", 0, 14)); // NOI18N jTextField2.setText("Просветов"); jTextField3.setFont(new java.awt.Font("Tahoma", 0, 14)); // NOI18N jTextField3.setText("user"); jButton1.setText("Зарегистрироваться"); jButton1.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { jButton1ActionPerformed(evt); } }); jPasswordField1.setFont(new java.awt.Font("Tahoma", 0, 14)); // NOI18N jPasswordField1.setText("1234"); jPasswordField1.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(java.awt.event.ActionEvent evt) { jPasswordField1ActionPerformed(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() .addGap(28, 28, 28) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false) .addComponent(jLabel1) .addComponent(jLabel2, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) .addComponent(jLabel3) .addComponent(jLabel4) .addComponent(jTextField1) .addComponent(jTextField2) .addComponent(jTextField3) .addComponent(jButton1, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) .addComponent(jPasswordField1)) .addContainerGap(19, Short.MAX_VALUE)) ); layout.setVerticalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createSequentialGroup() .addContainerGap() .addComponent(jLabel1) .addGap(24, 24, 24) .addComponent(jTextField1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addGap(18, 18, 18) .addComponent(jLabel2) .addGap(18, 18, 18) .addComponent(jTextField2, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addGap(21, 21, 21) .addComponent(jLabel3) .addGap(18, 18, 18) .addComponent(jTextField3, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addGap(18, 18, 18) .addComponent(jLabel4) .addGap(18, 18, 18) .addComponent(jPasswordField1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 26, Short.MAX_VALUE) .addComponent(jButton1, javax.swing.GroupLayout.PREFERRED_SIZE, 37, javax.swing.GroupLayout.PREFERRED_SIZE) .addContainerGap()) ); pack(); }// </editor-fold> private void jPasswordField1ActionPerformed(java.awt.event.ActionEvent evt) { } //создает окно чата private void createClientWindow(){ new ClientWindow(socket,in,out).setVisible(true); this.setVisible(false); } //КНОПКА ЗАРЕГИСТРИРОВАТЬСЯ private void jButton1ActionPerformed(java.awt.event.ActionEvent evt) { //отравляем данные на сервер для проверки валидности try { Client client = new Client(jTextField3.getText(), jPasswordField1.getText()); out.writeUnshared(new Wrapper(client, "REGISTRATION")); out.reset(); } catch (Exception e) { System.out.println("Не удалось отправить данные на сервер"); } } 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(Registration.class.getName()).log(java.util.logging.Level.SEVERE, null, ex); } catch (InstantiationException ex) { java.util.logging.Logger.getLogger(Registration.class.getName()).log(java.util.logging.Level.SEVERE, null, ex); } catch (IllegalAccessException ex) { java.util.logging.Logger.getLogger(Registration.class.getName()).log(java.util.logging.Level.SEVERE, null, ex); } catch (javax.swing.UnsupportedLookAndFeelException ex) { java.util.logging.Logger.getLogger(Registration.class.getName()).log(java.util.logging.Level.SEVERE, null, ex); } //</editor-fold> } // Variables declaration - do not modify private javax.swing.JButton jButton1; private javax.swing.JLabel jLabel1; private javax.swing.JLabel jLabel2; private javax.swing.JLabel jLabel3; private javax.swing.JLabel jLabel4; private javax.swing.JPasswordField jPasswordField1; private javax.swing.JTextField jTextField1; private javax.swing.JTextField jTextField2; private javax.swing.JTextField jTextField3; // End of variables declaration 

}

The error occurs when I click the register button:

java.lang.ClassCastException: java.io.ObjectStreamClass cannot be cast to clientapp.Wrapper at clientapp.Registration $ IncomingReader1.run (Registration.java:31) at java.lang.Thread.run (Thread.java:745) java. io.StreamCorruptedException: invalid type code: 00 at java.io.ObjectInputStream.readObject0 (ObjectInputStream.java:1381) at java.io.ObjectInputStream. java: 123) at java.lang.Thread.run (Thread.java:745)

I understand that it most likely arises from the fact that I use the same ObjectInputStream for all windows that I run. Accordingly, in each of them, a stream is started which waits for data through the same ObjectInputStream, but I do not know how to correct this error. Could you tell me what to do in this case?

    1 answer 1

    The problem can be solved if you change the client's architecture. Look at the MVC pattern.

    Work with the stream (stream) should be only one object, let's call it a model. If any data comes from the stream, the model notifies all those who are waiting for these changes about changes.