Java 套接字:如果超过 1 个客户端,客户端-服务器通信将卡在多线程中

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

首先,我的代码只是一个演示我的多人游戏(2个或更多玩家可以同时玩)来演示我的问题,没有任何额外的东西。我已经在我的游戏中成功实现了点对点 (P2P) 通信。后来,我决定在我的游戏中添加对 client/server 通信的支持(即中央服务器也是玩家)。它应该比P2P容易得多。但是很奇怪!不幸的是,我正面临着我不知道如何解决的问题!现在问题来了:

假设,我有1台服务器和一些客户端(可能是1个或多个客户端)。他们都应该给出以下输出:

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

他们都在没有使用多线程的情况下给出了上述输出。但是使用多线程,它给出上面的输出只有当有 1 个服务器和 1 个客户端时。

这是服务器代码。只显示重要部分; TODO 注释表示 send/receive 数据。 Common.totalClients是要连接的客户端数量。

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!");
    }
}

这里是客户端代码(只是重要的部分)。第一个客户有 clientID1。第二个客户有 clientID2 等等。第一个客户应该首先是 运行,然后是第二个等等。 TODO 注释以指示 send/receive 数据。

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

不知道是哪个原因导致使用多线程没有得到预期的输出。同样,使用多线程,它仅适用于 1 个客户端(和服务器)!

这里是ReceiveThread。请注意,如果连接的客户端超过 1 个,服务器和客户端都会卡在 try { ch = queue.take(); }

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;
    }
}

这里是SendThread代码:

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;
    }
}

现在,如果 Common.totalClient 是 2(即 运行 有 2 个客户端),那么我得到以下输出:

服务器:(首先运行)

Starting...
A

客户端 1(clientID 为 1):(在服务器之后运行)

Starting...
A
B
B

客户端 2(clientID 为 2):(在客户端 1 之后运行)

Starting...
A

他们都卡在那儿,也不例外。为什么会出现这种行为?如何解决?请注意,我使用了相同的 SendThreadReceiveThread classes 来成功实现 P2P 通信。如果您有疑虑,请随时询问我在这里使用的更详细的代码。

编辑: 为方便起见,我添加了完整的 运行nable 项目(仅包含 5 个小的 .java 文件:2 个线程 classes;服务器、客户端 classes 和通用class)。目前在使用额外线程时出现故障。但它在没有额外线程的情况下按预期工作。要在没有额外线程的情况下对其进行测试,请执行以下操作:1. 注释 \ TODO 行,2. 取消注释 \ TODO 行之后的单行。 3. 注释额外的线程构造行(4 行)。这是link:(我已经删除了link,因为它不需要解决问题!)

您的服务器 "multithreaded wrong" 本身 。虽然您确实有 2* totalClients 个线程,但服务器上仍然只有 运行 个线程(主线程)。我的意思是你的主线程有一个 for 循环遍历每个客户端;如果其中一个客户端卡住了,您的主线程将被阻塞,您将无法接收或发送来自其他客户端的信息。

如何解决这个问题:将接收和发送代码放在各自的客户端线程中,而不是放在主线程中。你的 main 应该看起来更像 (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();   
    }
}

这里有一个明显的问题:将数据发送到 sendThreads[i] 而不是 sendThreads[j]j 是循环变量,其实我想用它但是打错了。但是\ TODO后面的评论是正确的!这就是它在不使用额外线程的情况下工作的原因。正如问题中所述,这里没有什么奇怪的!

所以ServerMainclass应该是(只修改应该修改的部分):

// 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);
}

其实是一些愚蠢的错误!但这是我问题的实际答案。