简体   繁体   English

客户端在多线程客户端/服务器应用程序上断开连接

[英]Clients disconnect on multithreaded Client/Server application

This is a multithreading Server/Client application I am doing using swing components, kind of like a chat application. 这是我正在使用swing组件执行的多线程Server / Client应用程序,有点像聊天应用程序。 Everything works fine, unless I open 2 or more Clients on the same computer. 除非我在同一台计算机上打开2个或更多客户端,否则一切正常。 Then, still everything works fine with the chat and the messages, but if I close one of the Clients, then the other 2 close as well. 然后,聊天和消息仍然可以正常工作,但是如果我关闭其中一个客户端,则其他两个也都关闭。 I know it must have something to do with multithreading but I can't quite figure out exactly what the problem is. 我知道它一定与多线程有关,但是我不能完全弄清楚问题出在哪里。

Server.java 服务器.java

import javax.swing.*;
import java.awt.*;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.A

rrayList;

public class Server extends JFrame {

    private int port;
String host;
private ArrayList<ClientThread> clientThreads;

private JTextArea textArea;

private ServerSocket serverSocket = null;
Socket connection;
private PrintWriter out;
private BufferedReader in;


Server(int port) {
    this.port = port;
    clientThreads = new ArrayList<>();

    // Tar hand om JFrame.
    setLayout(new BorderLayout());
    setVisible(true);
    setLocation(750, 0);
    setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
    textArea = new JTextArea(15, 60);
    JPanel centerPanel = new JPanel();
    centerPanel.add(new JScrollPane(textArea));
    add(centerPanel, BorderLayout.CENTER);
    pack();

    try {
        serverSocket = new ServerSocket(port);
        host = serverSocket.getInetAddress().getLocalHost().getHostAddress();
    } catch (IOException e) {
        System.err.println(e.getMessage());
    }

    // Titeln skrivs ut.
    updateTitle();

    while (true) {
        try {
            connection = serverSocket.accept();
            // Byter titel
            updateTitle();

            // Startar en Thread för den nya socket:en.
            ClientThread task = new ClientThread(connection);
            clientThreads.add(task);
            task.start();
        } catch (IOException ex) {
            // Ignorera.
        }
    }
}

public static void main(String[] args) {
    if (args.length == 0) {
        // Skickar default värde.
        new Server(2000);
    } else if (args.length == 1) {
        // Skickar argumentet
        new Server(Integer.parseInt(args[0]));
    }
}

public class ClientThread extends Thread {

    // Threadklassen till klientsocket:en.
    private Socket clientSocket;
    private String clientHost;

    public ClientThread(Socket connection) {
        this.clientSocket = connection;
        clientHost = clientSocket.getInetAddress().getHostName();
    }

    @Override
    public void run() {
        // Skriver ut att någon har "loggat in" i JTextArea.
        textArea.append("CLIENT: " + clientHost + "CONNECTED" + "\n");
        // Metoden som kollar om klienten skriver en chatmeddelande.

        try {
            synchronized (this) {
                out = new PrintWriter(clientSocket.getOutputStream(), true);
                in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
                while (true) {
                    synchronized (this) {
                        if (in == null) System.out.println("NULL");
                        String line = in.readLine();
                        textArea.append("CLIENT: " + clientHost + " BROADCAST: " + line + "\n");
                        broadcast(line);
                    }
                }


          }
                // Byter titel och stänger allt.
//                if (out != null) out.close();
//                if (in != null) in.close();
//                if (clientSocket != null) clientSocket.close();
            } catch (IOException e){
                    synchronized (this) {
                        e.printStackTrace();
                    // Vad händer när en klient blir disconnected.
                    textArea.append("CLIENT: " + clientHost + " DISCONNECTED" + "\n");
                    for (Thread t : clientThreads) {
                        if (t == this) {
                            t = null;
                        }
                        clientThreads.remove(this);
                    }
                    System.out.println(clientThreads.size());
                    updateTitle();
                }
            }

    }

}

public void broadcast(String message) {
    // Synchronized för att slippa krocka.

        try {
            synchronized (this) {
            // Skriver ut meddelandet i JTextArea och sänder det till alla klienter.
            for (int i = clientThreads.size() - 1; i >= 0; i--) {
                out = new PrintWriter(clientThreads.get(i).clientSocket.getOutputStream(), true);
                out.write(message + "\n");
                out.flush();
            }
            }
        } catch (IOException e) {
            System.err.println(e.getMessage());
        }

}

public void updateTitle() {
    setTitle("HOST: " + host + " | PORT: " + port + " | NUMBER OF CLIENTS: " + 

clientThreads.size());
    }
}

Client.java 客户端.java

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.*;
import java.net.Socket;
import java.net.SocketTimeoutException;

public class Client extends JFrame {

    private Socket socket;
    private BufferedReader in;
    private PrintWriter out;

    private JTextField inputTextField = new JTextField(60);
    private JTextArea textArea = new JTextArea(15, 60);

    // Tråd som kallas för att läsa rader från servern.
    private Thread backgroundThread = new Thread(new Runnable() {
        @Override
        public void run() {
            String line;
            // Eviga loopen
            try {
                in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                line = in.readLine();
                while (line != null) {
                    textArea.append(line + "\n");
                    line = in.readLine();
                }
            } catch (SocketTimeoutException e2) {
                // Ignorera.
            } catch (IOException e) {
                // Ignorera.
            }

//            try {
//                if (out != null) out.close();
//                if (in != null) in.close();
//                if (socket != null) socket.close();
//            } catch (IOException e) {
                // Ignorera.
//            }
        }
    });

    // Vad som händer när man trycker på enter i JTextField
    private AbstractAction onEnterPressAction = new AbstractAction() {
        @Override
        public void actionPerformed(ActionEvent e) {
            String textToSend = inputTextField.getText();
            inputTextField.setText("");
            try {
                // Skickar ut strängen till servern.
                out = new PrintWriter(new OutputStreamWriter(socket.getOutputStream(), "ISO-8859-1"), true);
                out.write(textToSend + "\n");
                out.flush();
            } catch (SocketTimeoutException e2) {
                // Ignorera
            } catch (IOException e1) {
                e1.printStackTrace();
            }
        }
    };

    public Client(String host, int port) {
        // Konstruktorn.
        // Skapar en Socket.
        try {
            socket = new Socket(host, port);
            socket.setSoTimeout(15000);
        } catch (IOException e) {
            // Kunde inte kopplas till servern.
            JOptionPane.showMessageDialog(null, "Could not connect to server", "Connection Error", JOptionPane.ERROR_MESSAGE);
            System.exit(0);
        }

        // Sätter titelrad
        setTitle("CONNECTED TO SERVER: " + host + " IN PORT: " + port);

        // Startar eviga läsloopen
        backgroundThread.start();

        // Tar hand om JFrame.
        setLayout(new BorderLayout());
        setVisible(true);
        // Sätter en annan operation när JFrame stängs för att stoppa loopen.
        addWindowListener(new WindowAdapter() {
            @Override
            public void windowClosing(WindowEvent e) {
                System.exit(0);
            }
        });

        // Söder JPanel (JTextField som man skriver meddelande i)
        JPanel southPanel = new JPanel();
        southPanel.add(inputTextField);
        inputTextField.addActionListener(onEnterPressAction);

        // Centrala JPanel (JTextArea som visar alla meddelanden).
        JPanel centerPanel = new JPanel();
        JScrollPane scrollPane = new JScrollPane(textArea);
        centerPanel.add(scrollPane);

        // Adderar och packar.
        add(centerPanel, BorderLayout.CENTER);
        add(southPanel, BorderLayout.SOUTH);
        pack();
        setLocationRelativeTo(null);
    }

    public static void main(String[] args) {
//        if (args.length == 0) {
//            // Skickar default värden.
//            new Client("127.0.0.1", 2000);
//        } else if (args.length == 1) {
//            // Skickar argument och default port värde.
//            new Client(args[0], 2000);
//        } else if (args.length == 2) {
//            // Skickar argumenter.
//            new Client(args[0], Integer.parseInt(args[1]));
//        }
        new Client("192.168.1.66", 2000);
    }
}

EDIT: Here is the error I'm getting, but that might not indicate where the problem is. 编辑:这是我得到的错误,但这可能并不表示问题出在哪里。

2
java.net.SocketException: Connection reset
    at java.net.SocketInputStream.read(SocketInputStream.java:189)
    at java.net.SocketInputStream.read(SocketInputStream.java:121)
    at sun.nio.cs.StreamDecoder.readBytes(StreamDecoder.java:284)
    at sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:326)
    at sun.nio.cs.StreamDecoder.read(StreamDecoder.java:178)
    at java.io.InputStreamReader.read(InputStreamReader.java:184)
    at java.io.BufferedReader.fill(BufferedReader.java:161)
    at java.io.BufferedReader.readLine(BufferedReader.java:324)
    at java.io.BufferedReader.readLine(BufferedReader.java:389)
    at com.company.Server$ClientThread.run(Server.java:98)
1
java.net.SocketException: Connection reset
    at java.net.SocketInputStream.read(SocketInputStream.java:134)
    at java.net.SocketInputStream.read(SocketInputStream.java:121)
    at sun.nio.cs.StreamDecoder.readBytes(StreamDecoder.java:284)
    at sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:326)
    at sun.nio.cs.StreamDecoder.read(StreamDecoder.java:178)
    at java.io.InputStreamReader.read(InputStreamReader.java:184)
    at java.io.BufferedReader.fill(BufferedReader.java:161)
    at java.io.BufferedReader.readLine(BufferedReader.java:324)
    at java.io.BufferedReader.readLine(BufferedReader.java:389)
    at com.company.Server$ClientThread.run(Server.java:98)

This is just the normal behavior of a client that disconnects, the problem is, after I close the 3rd client (if we assume I opened 3), I go to write something on the 2nd and press Enter, it broadcasts the message to all the rest and then that client disconnects immediately, also with the same error. 这只是客户端断开连接的正常行为,问题是,当我关闭第三个客户端(假设我们打开了3个客户端)后,我去写了第二个,然后按Enter,它将消息广播到所有休息,然后该客户端立即断开连接,也出现相同的错误。 It wasn't my iteration. 这不是我的迭代。

I don't think it has much to do with multithreading, per se. 从本质上讲,我认为这与多线程无关。

for (Thread t : clientThreads) {
  if (t == this) {
    t = null;
  }
  clientThreads.remove(this);
}

This isn't the right way to iterate through a list and remove items. 这不是遍历列表并删除项目的正确方法。 You are implicitly creating an iterator in the enhanced for loop; 您将在增强的for循环中隐式创建一个迭代器; calling remove like this is a concurrent modification with respect to that iterator, because there is no way for the iterator to know that you've called remove on the list. 像这样调用remove是对该迭代器的并发修改,因为迭代器无法知道您已经在列表中调用了remove

It's unclear what you're really meaning to do here. 目前尚不清楚您在这里真正要做什么。 The conditional is just setting a local variable to null. 条件只是将局部变量设置为null。 You may as well just call clientThreads.remove(this) , no loop required. 您也可以只调用clientThreads.remove(this) ,而无需循环。

The correct way to iterate through a list and remove items is to use an explicit iterator, on which you can call the remove method. 遍历列表和删除项目的正确方法是使用显式迭代器,您可以在其上调用remove方法。

Iterator<Thread> it = clientThreads.iterator();
while (it.hasNext()) {
  Thread t = it.next();
  // Do something with t.
  it.remove();
}

And, of course, you would need to ensure exclusive access to clientThreads while you apply either of these methods. 而且,当然,在应用这两种方法中的任何一种时,您都需要确保对clientThreads独占访问。

UPDATE: Turned out the problem was in this line in Server class: 更新:原来问题出在Server类的这一行:

in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));

the private BufferedReader in is a private attribute of the Server class, therefore, it was being shared among all ClientThreads. private BufferedReader in是Server类的私有属性,因此,它已在所有ClientThreads中共享。 Every time this line got called, it switched all the streams to the current socketInputStream. 每次调用此行时,它将所有流切换到当前的socketInputStream。 Instead of using a private member for everyone, you should create BufferedReader for each thread: 而不是为所有人使用私有成员,您应该为每个线程创建BufferedReader:

BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));

You are using the below line while iterating over the list in Server.java class. 您在遍历Server.java类中的列表时使用下面的行。 This is the cause for the same. 这是相同的原因。

clientThreads.remove(this);

Instead of directly removing the thread use thread safe iterators. 与其直接删除线程,不如使用线程安全迭代器。 or use CopyOnWriteArrayList this would fix you this issue. 或使用CopyOnWriteArrayList可以解决此问题。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM