簡體   English   中英

客戶端在多線程客戶端/服務器應用程序上斷開連接

[英]Clients disconnect on multithreaded Client/Server application

這是我正在使用swing組件執行的多線程Server / Client應用程序,有點像聊天應用程序。 除非我在同一台計算機上打開2個或更多客戶端,否則一切正常。 然后,聊天和消息仍然可以正常工作,但是如果我關閉其中一個客戶端,則其他兩個也都關閉。 我知道它一定與多線程有關,但是我不能完全弄清楚問題出在哪里。

服務器.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());
    }
}

客戶端.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,它將消息廣播到所有休息,然后該客戶端立即斷開連接,也出現相同的錯誤。 這不是我的迭代。

從本質上講,我認為這與多線程無關。

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獨占訪問。

更新:原來問題出在Server類的這一行:

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

private BufferedReader in是Server類的私有屬性,因此,它已在所有ClientThreads中共享。 每次調用此行時,它將所有流切換到當前的socketInputStream。 而不是為所有人使用私有成員,您應該為每個線程創建BufferedReader:

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

您在遍歷Server.java類中的列表時使用下面的行。 這是相同的原因。

clientThreads.remove(this);

與其直接刪除線程,不如使用線程安全迭代器。 或使用CopyOnWriteArrayList可以解決此問題。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM