Java套接字:如果有多个客户端,则客户端 - 服务器通信会遇到多线程问题

时间:2015-02-04 20:31:46

标签: java multithreading sockets client-server

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

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

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

他们都在不使用多线程的情况下提供上述输出。但是使用多线程时,只有当有1个服务器和1个客户端时才会提供上述输出

以下是服务器代码。只显示了重要部分; TODO发表评论以表明发送/接收数据。 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!");
    }
}

以下是客户端代码(仅限重要部分)。第一位客户有clientID 1 。第二位客户有clientID 2 等等。首先应该运行第一个客户端,然后运行第二个客户端。 TODO发表评论以表明发送/接收数据。

        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。请注意,如果连接了多个客户端,则服务器和客户端都会停留在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类,通过这些类我成功实现了P2P通信。如果您有疑虑,请随时询问我在此处使用的更详细的代码。

<击> 修改 为方便起见,我添加了完整的runnable项目(它只包含5个小的.java文件:2个线程类;服务器,客户端类和公共类)。使用其他线程时当前有问题。但它没有额外的线程就可以正常工作。要在没有其他线程的情况下进行测试,请执行以下操作:1。注释\\ TODO行,2。在\\ TODO行之后取消注释单行。 3.注释其他螺纹结构线(4条线)。这是链接:(我删除了链接,因为它不需要解决问题!)

2 个答案:

答案 0 :(得分:1)

您的服务器“多线程错误”本身。虽然你有2 * totalClients的线程,但你仍然只在服务器上运行一个线程(主线程)。我的意思是你的主线程有一个for循环遍历每个客户端;如果其中一个客户端被卡住,您的主线程将被阻止,您将无法接收或发送其他客户端。

如何解决这个问题:将接收和发送代码放在每个相应的客户端线程中,而不是放在主线程上。你的主要应该看起来更像(伪代码)

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

答案 1 :(得分:0)

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

所以ServerMain类应该是(只有应修改的部分):

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

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