简体   繁体   English

Java多线程套接字客户端/服务器:发送和接收Enummap对象

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

To begin with, it is a JavaFX application with classical MVC architecture. 首先,它是具有经典MVC架构的JavaFX应用程序。 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. 我正在使用带有套接字的多线程“ echo”服务器来处理客户端。 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. 由于在JavaFX中我们无法直接通过套接字发送节点,因此我决定发送由客户端生成的参数 ,然后客户端将其发送到服务器,以便可以将参数回显到所有连接的客户端。

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. 它创建一个ServerSocket ,而while循环创建一个新线程,该线程将处理连接的客户端。 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. 这个主服务器线程有3种方法:2种跟踪连接的客户端,1种向所有连接的客户端发送传入的参数。

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 主应用程序具有一个控制器,该控制器将获取两个字段的值并将其存储在以下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; 当用户按下roll时,在控制器创建的枚举图中存储了2个值,控制器通过send()方法将此枚举映射转发到客户端线程; and this method writes the enummap to the objectoutput stream. 然后此方法将enummap写入objectoutput流。

The problem happens at next step. 问题发生在下一步。 The thread handling client connection on server side receives the enummap from the stream 服务器端的线程处理客户端连接从流中接收到enummap

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

But it appears it is unable to cast it to Enummap and throws an exception 但似乎无法其强制转换为Enummap并引发异常

java.lang.ClassCastException: java.io.ObjectStreamClass cannot be cast to java.util.EnumMap java.lang.ClassCastException:无法将java.io.ObjectStreamClass强制转换为java.util.EnumMap

at java.lang.Thread.run(Unknown Source) 在java.lang.Thread.run(未知来源)

What am i doing wrong ? 我究竟做错了什么 ?

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. 正确的方法是捕获IOException: connection reset发送到断开的连接将导致IOException: connection reset ,或者读取流的末尾而导致EOFException

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

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