0

Firstly, my code is just a demo of my multiplayer game (2 or more players can play simultaneously) to demonstrate my problem without any extra things. I have successfully implemented peer-to-peer (P2P) communication in my game. Later, I decided to add support for client/server communication (ie a central server which is also a player) in my game. It should be much easier than P2P. But strange! Unfortunately I'm facing the problem which I have no clue to solve it! Now here is the problem:

Suppose, I have 1 server and some clients (may be 1 or more clients). They all should give the following output:

Starting...
A
B
C
D
E
F
...
...
Done!

They all give the above output without using multi-thread. But using multi-threading, it gives the above output only when there're 1 server and 1 client.

Here is the server code. Only the important part is shown; TODO comment to indicate send/receive data. Common.totalClients is the number of clients to be connected with.

class ServerMain {
    public static void main(String[] args) {

        ServerSocket serverSocket = null;
        Socket[] sockets = new Socket[Common.totalClients];
        ObjectOutputStream[] sendStreams = new ObjectOutputStream[Common.totalClients];
        ObjectInputStream[] receiveStreams = new ObjectInputStream[Common.totalClients];
        SendThread[] sendThreads = new SendThread[Common.totalClients];
        ReceiveThread[] receiveThreads = new ReceiveThread[Common.totalClients];

        // ... (here, all assignment of the above variables and closing serverSocket)

        System.out.println("Starting...");
        final char lastSendChar = 'Z' - (26 % (Common.totalClients + 1)) - Common.totalClients;
        for (char sendChar = 'A'; sendChar <= lastSendChar; sendChar += (Common.totalClients + 1)) {

            // sending server data to all clients
            for (int i = 0; i < Common.totalClients; i++) {
                sendThreads[i].send(sendChar);  // TODO
                //Common.send(sendStreams[i], sendChar);
            }
            System.out.println(sendChar);

            for (int i = 0; i < Common.totalClients; i++) {
                char receivedChar = receiveThreads[i].receive();    // TODO
                //char receivedChar = Common.receive(receiveStreams[i]);

                // sending received data to other clients except the one from which data has been received
                // (so that all clients can receive other clients' data indirectly via this server)
                for (int j = 0; j < i; j++) {
                    sendThreads[i].send(receivedChar);  // TODO
                    //Common.send(sendStreams[j], receivedChar);
                }
                for (int j = i + 1; j < Common.totalClients; j++) {
                    sendThreads[i].send(receivedChar);  // TODO
                    //Common.send(sendStreams[j], receivedChar);
                }

                System.out.println(receivedChar);
            }

            try { Thread.sleep(Common.loopSleep); }
            catch (InterruptedException e) { e.printStackTrace(); }
        }

        // ... (here, closing all sockets and interrupt all threads)
        System.out.println("Done!");
    }
}

Here is the client code (only important part). First client has clientID of 1. Second client has clientID of 2 and such that. And first client should be run first, then second and such that. TODO comment to indicate send/receive data.

        System.out.println("Starting...");
        final char lastSendChar = 'Z' - (26 % (Common.totalClients + 1)) - Common.totalClients + clientID;
        for (char sendChar = 'A' + clientID; sendChar <= lastSendChar; sendChar += (Common.totalClients + 1)) {

            // receiving data from server and other clients whose "clientID" is less than this client's "clientID" (via server)
            for (int j = 0; j < clientID; j++) {
                System.out.println(receiveThread.receive());    // TODO
                //System.out.println(Common.receive(receiveStream));
            }

            // sending this client's data
            sendThread.send(sendChar);  // TODO
            //Common.send(sendStream, sendChar);
            System.out.println(sendChar);

            // receiving data from other clients whose "clientID" is greater than this client's "clientID" (via server)
            for (int j = clientID; j < Common.totalClients; j++) {
                System.out.println(receiveThread.receive());    // TODO
                //System.out.println(Common.receive(receiveStream));
            }

            try { Thread.sleep(Common.loopSleep); }
            catch (InterruptedException e) { e.printStackTrace(); }
        }

I don't know which is the culprit of not getting the expected output when using multi-thread. Again, using multi-thread, it works only for 1 client (and the server)!

Here is ReceiveThread. Note, both the server and the clients are stuck at try { ch = queue.take(); } if more than 1 client are connected.

class ReceiveThread extends Thread {
    private ObjectInputStream receiveStream;
    private BlockingQueue<Character> queue = new ArrayBlockingQueue<Character>(Common.totalClients);

    public ReceiveThread(ObjectInputStream receiveStream) {
        this.receiveStream = receiveStream; start();
    }

    public void run() {
        while (!Thread.interrupted()) {
            try { queue.put(receiveStream.readChar()); }
            catch (InterruptedException e) { return; }
            catch (IOException e) { return; }
        }
    }

    public char receive() {
        char ch = '#';
        try { ch = queue.take(); }
        catch (InterruptedException e) { e.printStackTrace(); }
        return ch;
    }
}

Here is SendThread code:

class SendThread extends Thread {
    private ObjectOutputStream sendStream;
    private volatile boolean pending = false;
    private volatile char sendChar;

    public SendThread(ObjectOutputStream sendStream) {
        this.sendStream = sendStream; start();
    }

    public void run() {
        while (!Thread.interrupted()) {
            if (pending) {
                pending = false;
                try {
                    sendStream.writeChar(sendChar);
                    sendStream.flush();
                } catch (IOException e) { return; }
            }

            try { Thread.sleep(10); }
            catch (InterruptedException e) { return; }
        }
    }

    public void send(char ch) {
        sendChar = ch; pending = true;
    }
}

Now, if Common.totalClient is 2 (ie 2 clients to run), then I get the following output:

Server: (Runs first)

Starting...
A

Client 1 (clientID is 1): (Runs after the server)

Starting...
A
B
B

Client 2 (clientID is 2): (Runs after Client 1)

Starting...
A

They are stuck at there, even no exception. Why is this behaviour? How to solve it? Note that I have used the same SendThread and ReceiveThread classes by which I have succcessfully implemented P2P communication. Feel free to ask more detailed code which I have used here if you have concern.

Edit: For convenience, I've added the full runnable project (which only contains 5 small .java files: 2 thread classes; server, client classes and common class). It is currently faulty when using additional threads. But it works as expected without additional threads. To test it without the additional threads, please just do: 1. Comment \\ TODO lines, 2. Uncomment the single lines just after \\ TODO lines. 3. Comment the additional thread construction lines (4 lines). Here is the link: (I've deleted the link because it is not needed to solve the problem!)

arnobpl
  • 1,126
  • 4
  • 16
  • 32
  • 1
    Can you explain your multithread structure a bit more? For example, do you have a send thread and a receive thread per client, or does the client run on just one thread? Perhaps your server has ONE receive thread and ONE send thread? Or does it have one R and one S thread PER CLIENT? – Aify Feb 04 '15 at 20:43
  • @Aify: your 2nd one. Every `Socket` has 2 threads (1 for sending, other for receiving); That means, each client runs only 2 threads. But the server runs 2 * `totalClients` threads. For example, if the server has 3 clients, then the server runs 6 threads (3 for sending and the other 3 for receiving). – arnobpl Feb 04 '15 at 20:48

2 Answers2

1

Your server is "multithreaded wrong" per se. While you do have 2* totalClients of threads, you're still only running one thread on the server (main thread). What I mean by that is that your main thread has a for loop that loops through each client; if one of the clients gets stuck, your main thread will be blocked and you won't receive or send from other clients.

How to fix this: put the receiving and sending code inside each respective client thread instead of on the main thread. Your main should look more like (pseudocode)

main func {
    while true {
        accept a socketconnection 
        make a sender thread for the new socket connection {
            thread code (always ready to send)
        }.start();
        make a listener thread for the new socket connection {
            thread code (listens continously)
        }.start();   
    }
}
user207421
  • 305,947
  • 44
  • 307
  • 483
Aify
  • 3,543
  • 3
  • 24
  • 43
  • `if one of the clients gets stuck` - why that would happen? It can only happen if one or more clients (if any) are deliberately disconnected, isn't it? But this is not case here because the server and all the clients are still running and no `Socket` is closed before finishing. I have edited the question to add `SendThread` class code for more clarification. – arnobpl Feb 04 '15 at 21:16
  • 1
    You're using a blocking queue's .take method (which is a blocking method) in the receive thread, which automatically makes your .receive() a blocking method. So what happens when there's nothing to take? You're **stuck** in that for loop until there is something to "take". – Aify Feb 04 '15 at 21:22
  • But `receive()` ( in fact, blocking queue's `take()` ) is only called here if there must be something to be received. If I only replace multi-threading, the server and all clients work as expected. If `receive()` were called in wrong place/time, then they should *not* work as expected even without multi-threading, should they? The server and the clients all run as expected without using the additional threads. – arnobpl Feb 04 '15 at 21:30
  • Actually `receive()` is called only when something to be received. For example, suppose I have 1 server and 2 clients. Firstly, the server will send "A" to the other 2 clients. Then the 2 clients will receive it simultaneously. Notice that the server code has sending first and the client code has receiving first. After receiving "A", client-1 will send "B" to the server. Receiving "B", server will send it to client-2. Client-2 will receive it and send "C" to server. Server will send it to client-1. Then server will send "D" to all the clients. I have commented my code there for clarification. – arnobpl Feb 04 '15 at 21:49
  • 1
    I read that code, but because it's in a for loop, you'll get livelock with the clients. Server sends to clients, clients send something back, server sends again but client's are all listening for another client, not the server. On a side note, did you even try my solution? You really don't need to argue here if you just don't want to restructure your code. – Aify Feb 04 '15 at 21:56
  • 1
    And again, your server is **NOT** multi-threaded. Everything is running off the main thread. – Aify Feb 04 '15 at 21:59
  • Firstly server will send its data to all clients. All clients will receive it. Then its other clients turn to send data - that is client-1. Client-2 will send its data only if it gets data twice from server - first data is actually the server's data and second is client-1's data (of course via server). When server receives client-1's data, it will send it to the other clients (except client-1). Now, it is client-2's turn! That client will send its data to server. And that goes on until everyone sends `lastSendChar`. It is not the case in my code that everyone is to receive at the same time. – arnobpl Feb 04 '15 at 22:08
  • Yes, my sever is not "multi-threaded" in the sense that it does **not** serve all the clients simultaneously, I understand. But that is I want. I want additional dedicated threads only for sending/receiving purpose but not the purpose to give service to all the clients simultaneously. – arnobpl Feb 04 '15 at 22:13
  • 1
    Here's the side-side breakdown of what's happening in your code: 1) S Sending to A, A receiving, B waiting for S; 2) S Sending to B, A Waiting for S, B receiving; 3) S Sending to A, A receiving, B waiting for **A** <-- This is blocking. – Aify Feb 04 '15 at 22:19
  • Although I was confident, after reading your last comment, I tested the code **without the dedicated threads** once more! In fact, the lines just after commented `\\ TODO` is for sending/receiving without dedicated threads. And the server and all the clients work as expected (no stuck at somewhere)! Don't know if I should upload my full test project as a ZIP file and give its link here! It might be helpful to me and may be others. And I gratefully thank you even if it is unsolved! :) – arnobpl Feb 04 '15 at 22:31
0

There is an obvious problem right here: sending data to sendThreads[i] instead of sendThreads[j]. j is the loop variable, and actually I wanted to use it but mistyped it. But the comments after \\ TODO is correct! That's why it was working without using additional threads. And nothing strange here as stated in the question!

So ServerMain class should be (only the part that should be modified):

// sending received data to other clients except the one from which data has been received
// (so that all clients can receive other clients' data indirectly via this server)
for (int j = 0; j < i; j++) {
    sendThreads[j].send(receivedChar);  // instead of sendThreads[i]
    //Common.send(sendStreams[j], receivedChar);
}
for (int j = i + 1; j < Common.totalClients; j++) {
    sendThreads[j].send(receivedChar);  // instead of sendThreads[i]
    //Common.send(sendStreams[j], receivedChar);
}

Actually it is some kind of silly mistakes! But this is the actual answer of my question.

arnobpl
  • 1,126
  • 4
  • 16
  • 32