有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

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

这是一个多线程服务器/客户端应用程序,我正在使用swing组件,有点像聊天应用程序。一切正常,除非我在同一台计算机上打开两个或多个客户端。然后,聊天和消息仍然可以正常工作,但是如果我关闭一个客户端,那么其他两个客户端也会关闭。我知道这一定与多线程有关,但我不太清楚到底是什么问题

服务器。爪哇

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

客户。爪哇

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)

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


共 (3) 个答案

  1. # 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的独占访问

  2. # 2 楼答案

    在服务器中迭代列表时,您正在使用下面的行。java类。这也是同样的原因

    clientThreads.remove(this);
    

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

  3. # 3 楼答案

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

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

    private BufferedReader in是服务器类的私有属性,因此,它在所有ClientThreads之间共享。每次呼叫这条线路时,它都会将所有流切换到当前的SocketInput流。 您应该为每个线程创建BufferedReader,而不是为每个线程使用私有成员:

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