Java Socket: Client-server communication is stuck with multi-threading if more than 1 client

时光怂恿深爱的人放手 提交于 2019-12-12 01:56:41

问题


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!)


回答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();   
    }
}



回答2:


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.



来源:https://stackoverflow.com/questions/28330870/java-socket-client-server-communication-is-stuck-with-multi-threading-if-more-t

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!