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

时间:2015-10-23 13:20:13

标签: java multithreading concurrentmodification

这是一个使用swing组件的多线程服务器/客户端应用程序,有点像聊天应用程序。除非我在同一台计算机上打开2个或更多客户端,否则一切正常。然后,聊天和消息仍然可以正常工作,但如果我关闭其中一个客户端,那么另外两个也关闭。我知道它必须与多线程有关,但我无法确切地知道问题是什么。

Server.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

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

编辑:以下是我遇到的错误,但这可能无法说明问题所在。

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)

这只是断开连接的客户端的正常行为,问题是,在我关闭第三个客户端后(如果我们假设我打开了3),我去写第二个东西然后按Enter键,它会广播消息所有其余的,然后该客户端立即断开连接,也有相同的错误。这不是我的迭代。

3 个答案:

答案 0 :(得分:1)

我认为它本身与多线程无关。

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

这不是迭代列表并删除项目的正确方法。您在增强的for循环中隐式创建了一个迭代器;像这样调用remove是对迭代器的并发修改,因为迭代器无法知道你在列表上调用了remove

目前还不清楚你在这里做什么意思。条件只是将局部变量设置为null。您也可以只调用clientThreads.remove(this),不需要循环。

迭代列表并删除项目的正确方法是使用显式迭代器,您可以在其上调用remove方法。

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

当然,在应用这些方法时,您需要确保对clientThreads的独占访问权。

答案 1 :(得分:1)

更新:问题出现在Server类的这一行:

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

private BufferedReader in是Server类的私有属性,因此,它在所有ClientThreads之间共享。每次调用此行时,它都会将所有流切换到当前的socketInputStream。 您应该为每个线程创建BufferedReader,而不是为每个人使用私有成员:

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

答案 2 :(得分:0)

在迭代Server.java类中的列表时,您正在使用以下行。这也是原因。

clientThreads.remove(this);

而不是直接删除线程使用线程安全迭代器。 或者使用CopyOnWriteArrayList这可以解决这个问题。