这是一个使用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键,它会广播消息所有其余的,然后该客户端立即断开连接,也有相同的错误。这不是我的迭代。
答案 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这可以解决这个问题。