javasocketscastingjavafx-8enum-map

Java Multi threaded socket client/server: sending and receiving Enummap objects


To begin with, it is a JavaFX application with classical MVC architecture. This application roll dices and display them. The goal is to display them to several clients. I am using a multi threaded "echo" server with sockets to deal with clients. Since in JavaFX we can't directly send nodes through socket, i decided to send arguments wich are generated by the client, then the client sends them to the server so it can echo the arguments to all connected clients.

Here is how it works :

First, the main server thread is created. It creates a ServerSocket and a while loop creates a new thread that will handle connected client. This main server thread has 3 methods : 2 that will keep track of connected client, and 1 that sends incoming arguments to all connected clients.

public class DiceRollServerThread implements Runnable
{
    private Server _server;
    private Server_C controller;
    private Vector<ObjectOutputStream> tabClients = new Vector<ObjectOutputStream>(); // Contain all outpput streams to connected clients
    private Thread t;
    private ServerSocket diceSS;
    
    public DiceRollServerThread(Server server) throws IOException
    {
        _server = server;
        
        controller = _server.getController();

        String port = "2000";
        String ip = "127.0.0.1";
        
        controller.setConsole("IP : "+ip+"\n"+"Port : "+port);
        
        diceSS = new ServerSocket(Integer.parseInt(port), 0, InetAddress.getByName(null));

        t = new Thread(this);
        t.start();
    }
    
    @Override
    public void run()
    {
        while (true) // bloquing on ss.accept
        {
            try
            {
                new DiceRollThread(diceSS.accept(), this);
            }
            catch (IOException e)
            {
                e.printStackTrace();
            }
        }
    }
    
    synchronized public void sendAll(EnumMap<ARGS, String> arguments) throws IOException
    {
        ObjectOutputStream out;
        
        for (int i = 0; i < tabClients.size(); i++) // browsing connected clients
        {
            out = (ObjectOutputStream) tabClients.elementAt(i);
            if (out != null)
            {
                out.writeObject(arguments);
                out.flush();
            }
        }
    }
    
    synchronized public void delClient(int i)
    {
        if (tabClients.elementAt(i) != null) // If element exist ...
        {
            tabClients.removeElementAt(i); // ... delete it
            System.out.println("delClient");
        }
    }
    
    synchronized public int addClient(ObjectOutputStream out)
    {
        tabClients.addElement(out); // Adding new output stream to vector
        System.out.println("addClient");    
        return tabClients.size()-1; // return client number (size-1)
    }
    
    public Server get_server()
    {
        return _server;
    }
}

Here is the thread handling clients :

public class DiceRollThread implements Runnable
{
    private Thread t;
    private Socket _s;
    private ObjectOutputStream out;
    private ObjectInputStream in;
    private DiceRollServerThread _serverThread; // to use main thread methods
    private int numClient=0;
    private Server _server;
    private EnumMap<ARGS,String> _arguments;
    
    DiceRollThread(Socket s, DiceRollServerThread serverThread)
    {
        _s = s;
        _serverThread = serverThread;
        _server = _serverThread.get_server();
        
        try
        {
            out = new ObjectOutputStream(_s.getOutputStream());
            in = new ObjectInputStream(_s.getInputStream());
            numClient = _serverThread.addClient(out);
            _server.getController().setConsole("Client n°"+numClient+" connected.");
        }
        catch (IOException e)
        {
            
        }
        
        t = new Thread(this);
        t.start();
    }
    
    @Override
    public void run()
    {
        try
        {
            while(_s.getInputStream().read() != -1) // verifying if connection is still up
            {
                _arguments = (EnumMap<ARGS, String>) in.readObject(); // Problem is here
                
                if(_arguments != null)
                {
                    System.out.println(_arguments);
                    _serverThread.sendAll(_arguments);
                }
            }
        }
        catch (ClassNotFoundException e)
        {
            e.printStackTrace();
        }
        catch (IOException e)
        {
            e.printStackTrace();
        }
        finally // Usually happens when client disconnect
        {
            try
            {
                _server.getController().setConsole("Client n°"+numClient+" disconnected.");
                _serverThread.delClient(numClient); // deleting from the vector
                _s.close(); // Closing socket if not done by upper exception
            }
            catch (IOException e)
            {
                
            }
        }
    }

Now on client side. The main app has a controller that will get the value of two fields and store them in the following Enummap

public class Arguments
{
    public enum ARGS {Couleur,Valeur};
}

And then sends it to the following thread handling connection to the server.

private void roll()
{
    arguments.put(ARGS.Couleur, box_couleur.getValue().toString());
    arguments.put(ARGS.Valeur, randInt(1,Integer.parseInt(box_de.getValue())));

    diceRollThread.send(arguments); // the thread gives his reference to the controller when created
}

The client thread (wich is connected to the server)

public class DiceRollThread implements Runnable
{
    private DiceRoll_C _controller;
    private Socket s;
    private ObjectOutputStream out;
    private ObjectInputStream in;
    private Thread t;
    private EnumMap<ARGS,String> _arguments;
            
    DiceRollThread(DiceRoll diceroll) throws IOException
    {   
        _controller = diceroll.getController();
        _controller.setDiceRollThread(this);

        String port = "2000";
        String ip = "127.0.0.1";
        
        try
        {
            s = new Socket(InetAddress.getByName(ip),Integer.parseInt(port));
            out = new ObjectOutputStream(s.getOutputStream());
            in = new ObjectInputStream(s.getInputStream());
        }
        catch (IOException e)
        {
            e.printStackTrace();
        }
        
        t = new Thread();
        t.start();
    }
    
    @Override
    public void run()
    {
        try
        {
            _arguments = (EnumMap<ARGS, String>) in.readObject();
            
            if(_arguments != null)
            {
                _controller.addDice(_arguments); // Creating dice from received arguments
            }
        }
        catch (ClassNotFoundException e)
        {
            e.printStackTrace();
        }
        catch (IOException e)
        {
            e.printStackTrace();
        }
        finally
        {
            try
            {
                s.close();
            }
            catch (IOException e)
            {
                e.printStackTrace();
            }
        }
    }
    
    public void send(EnumMap<ARGS,String> arguments) // arguments received from the controller, sends them to the server
    {
        try
        {
            out.writeObject(arguments);
            out.flush();
        }
        catch (IOException e)
        {
            e.printStackTrace();
        }
    }

A typical scenario looks like this : Launching server > launching client (connection made successfuly) > user press roll. When the user press roll, 2 values are stored in an enummap created by the controller, the controller forwards this enummap to the client thread via send() method; and this method writes the enummap to the objectoutput stream.

The problem happens at next step. The thread handling client connection on server side receives the enummap from the stream

_arguments = (EnumMap<ARGS, String>) in.readObject();

But it appears it is unable to cast it to Enummap and throws an exception

java.lang.ClassCastException: java.io.ObjectStreamClass cannot be cast to java.util.EnumMap

at java.lang.Thread.run(Unknown Source)

What am i doing wrong ?


Solution

  • while(_s.getInputStream().read() != -1) // verifying if connection is still up
    

    The problem is here. It consumes a byte from the input stream and so puts the serialization out of synchronization with the sender.

    It also doesn't accomplish the objective stated in the comment. The correct way to do that is just to catch the IOException: connection reset that will result from sending to a broken connection, or the EOFException that results from reading past end of stream.