0

Edited my question for clarification and code:

My goal is to pass my String data from my background thread, to my main application thread. Any help is appreciated.

Here is the code that creates the main background thread. This is located in my Server.java class

public class Server {

boolean isConnected = false;
Controller controller = new Controller();

public void startHost() {
    Thread host = new Thread(() -> {
        Controller controller = new Controller();
        ServerSocket server = null;

        try {
            server = new ServerSocket(GeneralConstants.applicationPort);

        } catch (BindException e2) {
            System.out.println("Port Already in Use!");

        } catch (IOException e) {
            //do nothing

        }

        while (true) {
            if (server == null) { break; }

            try {
                Socket client = server.accept();

                System.out.println("Client Connected: " + isConnected);

                if (!isConnected) {
                    controller.createClientHandler(client);
                    isConnected = true;
                    System.out.println("Client Connected: " + isConnected);
                }

            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();

            }
        }
    });

    host.setDaemon(true);
    host.start();

}

Here is the code that is then called when a client is connected, located in my Controller.java class.

    public synchronized void createClientHandler(Socket client) {
    boolean alreadyConnected = false;

    if (alreadyConnected) {
        //do NOT assign multiple threads for each client

    } else {
        ClientHandler handleClients = new ClientHandler("client", client);

    }

}

The program then creates two background threads for my client, one to manage receiving messages, and sending messages.

public ClientHandler(String name, Socket s) {
    clientSocket = s;
    clientName = name;

    receiveThread = new Thread(this::receive);
    sendThread = new Thread(this::send);

    connected = clientSocket.isConnected();

    receiveThread.start();
    sendThread.start();

}

The thread then successfully creates the inputstream and passes the object to my controller. Which then process and grabs a string assigning it to a variable

public synchronized void handleReceivedPacket(String name, BufferedReader in) {
    try {
        data = in.readLine();
        System.out.println("Successfully assigned data to: " + data);

    } catch (IOException e) {
        System.out.println("Unable to read result data");

    }

}

How do I access my String data from the main thread without getting null?

Aka I can call (or something similar)

controller.returnData();

from my main application. From which it'll either return null (no data yet), or actually return my data. Right now, it's always null.

Edit, this is what's actually calling controller.returnData() {

I don't want to paste a massive amount of code for fear of reaching StackOverflow's code limit, so here's my application structure.

My JavaFX creates the scene, and creates a root gridpane, it then calls a method that creates sub gridpanes based the specified input. Aka, a user can press "Main Menu" that calls my method setScene() which removes the current "sub-root" gridpane and creates a "new" scene. Right now, I have a GameBoard.java class which on button press, calls controller.returnData()

PassOption.setOnAction(event -> {
        System.out.println(controller.returnData());


    });

There is no functional purpose for this besides testing. If I can receive the data, then I can expand on this using the data.

  • Why do you pass the `BufferedReader` instance instead of the actual received `String` to the controller? What is `ClientHandler::receive` doing? I don't understand what you mean by "main application". Could you please post the relevant code of the "main application", especially the snippet which accesses the controller's data, i.e. the method calls to `controller.returnData()`. – trylimits Oct 30 '18 at 16:25
  • I don't have to pass `BufferedReader`, but could pass the string, I just kinda didn't. Also the `ClientHandler::` receive is the exact same as `() -> receive()`. The portion of code that is doing `controller.returnData` is in a portion of my JavaFX application, that all it should do is print the output of controller.returnData. I'll edit it to the main post. – Dalton Smith Oct 30 '18 at 16:43

1 Answers1

0

Start thinking about design. In network applications you typically have to manage the following responsibilites:

  1. Connected clients and their state (connection state, heartbeats, ...)
  2. Received messages from the clients
  3. Messages to transmit to the clients

It makes sense to separate those responsibilities in order to keep the code clean, readable and maintainable.

Separation can mean both, thread-wise and class-wise.

For example, you could implement it as follows:

The class ClientAcceptor is responsible for opening the socket and accepting clients. As soon as a client has connected, it delegates the further work to a controller and then waits for other clients:

public class ClientAcceptor implements Runnable {

    @Override
    public void run() {
        while (true) {
            ServerSocket server;
            try {
                server = new ServerSocket(1992);
                Socket client = server.accept();

                if (client.isConnected()) {
                    controller.createClientHandler(client);
                }
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }
}

The controller could then create a handler (if the controller decides to do so, e.g. it could also decline the client). The ClientHandler class could look as follows:

public class ClientHandler {
    private Thread receiveThread;
    private Thread sendThread;
    private boolean connected;
    private Socket clientSocket;
    private String clientName;
    private LinkedBlockingDeque<byte[]> sendQueue;

    public ClientHandler(String name, Socket s) {
        clientSocket = s;
        clientName = name;

        receiveThread = new Thread(() -> receive());
        sendThread = new Thread(() -> send());

        connected = clientSocket.isConnected();

        receiveThread.start();
        sendThread.start();
    }

    private void receive() {
        BufferedInputStream in = null;
        try {
            in = new BufferedInputStream(clientSocket.getInputStream());
        } catch (IOException e) {
            connected = false;
        }
        while (connected) {
            try {
                byte[] bytes = in.readAllBytes();
                if (bytes != null && bytes.length > 0) {
                    controller.handleReceivedPacket(clientName, bytes);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    private void send() {
        BufferedOutputStream out = null;
        try {
            out = new BufferedOutputStream(clientSocket.getOutputStream());
        } catch (IOException e) {
            connected = false;
        }
        while (connected) {
            byte[] toSend = sendQueue.getFirst();
            if (toSend != null && toSend.length > 0) {
                try {
                    out.write(toSend);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public void send(byte[] packet) {
        sendQueue.add(packet);
    }

    public void close() {
        connected = false;
    }
}

The ClientHandler is responsible for receiving and transmitting data. If a packet arrives it informes the controller, which parses the packet. The ClientHandler also provides a public API to send data (which is stored in a queue and handled by a thread) and close the connection.

The above code examples are neither tested, nor complete. Take it as a starting point.

trylimits
  • 2,477
  • 1
  • 21
  • 31
  • Looking at the code (not on my computer atm), it still doesn't seem like you'd be able to access the data outside the scope of the thread. Pardon me if I'm wrong. Could you clarify? Or is there a better way I should approach things? – Dalton Smith Oct 29 '18 at 23:06
  • What do you mean by "scope of the thread"? Threads share the same memory and it's absolutely fine to access objects and call methods from two different threads. If you are wondering about accessing the GUI components, JavaFX provides the method `Platform.runLater(...)`. You should also read about `wait()` and `notifyAll()` if you want a thread wait for another thread to finish its work: https://stackoverflow.com/questions/2536692/a-simple-scenario-using-wait-and-notify-in-java – trylimits Oct 29 '18 at 23:54
  • So to clarify, I can access the input stream from outside of the thread? – Dalton Smith Oct 30 '18 at 00:59
  • Yes, that's possible. – trylimits Oct 30 '18 at 06:28
  • I've successfully modified and extended the code to handle multiple clients(only receive the first, and then reset the state when that client is connected, ignore other clients). The thing is.. I still don't know how to access the data that is being set. I modified the receive function to set a global string to the value given via the stream, however, whenever I try to access the string outside the scope of the receive thread. I get null. – Dalton Smith Oct 30 '18 at 15:30
  • I have rewrote the question with my changes. – Dalton Smith Oct 30 '18 at 16:02
  • I'll go ahead and mark yours as the answer. I had to pass the constructor of the controller class between the different access points, once I did that, I was able to access the data in the queue. – Dalton Smith Oct 31 '18 at 18:15