我有一个连接到一台服务器的多线程聊天室。它们彼此独立地连接,登录和消息都很好,但是当我与其中一个客户端注销(并且服务器为该客户端实例执行了socket.close())时,所有客户端都将被注销。在发布之前我查看了一堆关于stackoverflow的其他问题,但是没有一个问题与我的相同(我发现)。注意:所有客户端都在我的计算机上本地运行,其中2个是客户端,这就是我遇到错误的原因。他们可能在同一个IP上(虽然一切都在我的本地主机上完成..)会导致这种情况发生吗?任何有关导致此问题以及如何解决问题的帮助或见解都会很棒。出现问题时,它还会将此输出到控制台:
Socket: Socket[addr=localhost/127.0.0.1,port=4000,localport=55650]
以下是代码(如何重新创建错误位于底部):
服务器类:
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashSet;
public class ChatServer {
private static final int PORT = 4000;
private static HashSet<String> names = new HashSet<String>();
private static HashSet<ObjectOutputStream> outputs =
new HashSet<ObjectOutputStream>();
public static void main(String[] args) throws IOException{
System.out.println("The chat server is running...");
ServerSocket listener = new ServerSocket(PORT);
while(true){
new Handler(listener.accept()).start();
}
}
private static class Handler extends Thread{
private String name;
private Socket socket;
private ObjectInputStream in;
private ObjectOutputStream out;
private boolean loggedOut;
public Handler(Socket socket){
this.socket = socket;
}
public void run(){
try {
out = new ObjectOutputStream(socket.getOutputStream());
in = new ObjectInputStream(socket.getInputStream());
loggedOut = false;
while(true){
Message message = (Message) in.readObject();
System.out.println("Server recieved login message!");
if(message.getNumber() == 0){
name = message.getName();
synchronized(names){
if(!names.contains(name)){
names.add(name);
break;
}else{
Message nameTaken = new Message(null, 3);
sendMessage(out, nameTaken);
}
}
}
}
synchronized(outputs){
outputs.add(out);
}
for(ObjectOutputStream output: outputs){
Message response = new Message(name, name + " has logged on.", 0);
sendMessage(output, response);
}
while(true){
System.out.println("Waiting for message...");
Message message = (Message) in.readObject();
Message response = null;
if(message.getNumber() == 1 && message.getMessage() != null){
response = new Message(message.getName(),
message.getMessage(), 1);
}else if(message.getNumber() == 2){
response = new Message(message.getName(), message.getName() +
" has logged off.", 2);
loggedOut = true;
}
for(ObjectOutputStream output: outputs){
sendMessage(output, response);
}
if(loggedOut) break;
}
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}finally {
if(name != null) names.remove(name);
if(out != null) outputs.remove(out);
try{
socket.close();
}catch(IOException e){
e.printStackTrace();
}
}
}
private void sendMessage(ObjectOutputStream out, Message message){
ObjectOutputStream outPutMessage = out;
Message response = message;
try {
outPutMessage.writeObject(response);
outPutMessage.flush();
} catch (IOException e1) {
e1.printStackTrace();
}
}
}
}
客户类:
import java.awt.BorderLayout;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.ArrayList;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;
public class ChatClient extends JFrame {
private JTextField inputField;
private JTextField nameField;
private JTextArea textArea;
private JButton loginButton;
private JButton logoutButton;
private JButton connectButton;
private JScrollPane textScroll;
private JScrollPane listScroll;
private JPanel textPanel;
private JPanel connectPanel;
private JPanel listPanel;
private JPanel inputPanel;
private JPanel namePanel;
private JPanel inputAndDisplayPanel;
private JPanel listAndLogoutPanel;
private ArrayList<String> stringList;
private JList list;
private final int CELL_WIDTH = 100;
private ObjectOutputStream out;
private ObjectInputStream in;
private JFrame chatFrame;
private ChatClient client;
private String name;
private final int PORT = 4000;
private boolean needServerData;
private boolean loggedIn;
private boolean previouslyLoggedIn;
private String serverAddress = null;
private Socket socket = null;
private void run() throws IOException, ClassNotFoundException{
needServerData = true;
loggedIn = true;
while(true){
//Loops before a user is logged in, verifies that the host is usable
while (socket == null) {
if (needServerData) {
try {
connectButton.setEnabled(false);
serverAddress = JOptionPane.showInputDialog(client,
"Enter server address: ", "Server Address",
JOptionPane.QUESTION_MESSAGE);
if(serverAddress == null){
JOptionPane.showMessageDialog(client,
"Click 'Connect' to re-enter server address.",
"Info", JOptionPane.PLAIN_MESSAGE);
needServerData = false;
connectButton.setEnabled(true);
}else if(serverAddress.equals("")){
JOptionPane.showMessageDialog(client,
"Invalid input! Enter a valid server address.",
"Error", JOptionPane.ERROR_MESSAGE);
}else {
socket = new Socket(serverAddress, PORT);
out = new ObjectOutputStream(socket.getOutputStream());
in = new ObjectInputStream(socket.getInputStream());
loginButton.setEnabled(true);
logoutButton.setEnabled(false);
nameField.setEnabled(true);
connectButton.setEnabled(false);
}
} catch (UnknownHostException e1) {
JOptionPane.showMessageDialog(client,
"Error: Unknown host!", "Error",
JOptionPane.ERROR_MESSAGE);
}
}
}
/*Loops after the user is logged in (will output to the textarea that that user logged in),
* reads in messages from server to append to the textarea and also determines if a user is
* logging out. Changes editable/enabled of buttons and text fields accordingly.
*/
if(loggedIn) {
Message message = (Message) in.readObject();
if (message.getNumber() == 1) {
textArea.append(message.getName() + ": "
+ message.getMessage() + "\n");
} else if (message.getNumber() == 0) {
inputField.setEditable(true);
textArea.append(message.getMessage() + "\n");
loginButton.setEnabled(false);
logoutButton.setEnabled(true);
} else if (message.getNumber() == 2) {
inputField.setText(null);
inputField.setEditable(false);
nameField.setText(null);
nameField.setEnabled(false);
connectButton.setEnabled(true);
logoutButton.setEnabled(false);
loggedIn = false;
previouslyLoggedIn = true;
textArea.append(message.getMessage() + "\n");
System.out.println("Socket: " + socket);
} else {
JOptionPane.showMessageDialog(client,
"Error: That name is taken!", "Error",
JOptionPane.ERROR_MESSAGE);
}
}
}
}
private String getServerAddress(){
return JOptionPane.showInputDialog(client, "Enter server address: ",
"Server Address", JOptionPane.QUESTION_MESSAGE);
}
public static void main(String[] args) throws IOException, ClassNotFoundException{
ChatClient client = new ChatClient();
try {
client.run();
} catch (IOException e) {
e.printStackTrace();
}
}
public ChatClient(){
//GUI Formatting Stuff
inputField = new JTextField(30);
nameField = new JTextField(10);
textArea = new JTextArea(25, 30);
loginButton = new JButton("Login");
logoutButton = new JButton("Logout");
connectButton = new JButton("Connect");
textScroll = new JScrollPane(textArea,
JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
textPanel = new JPanel();
listPanel = new JPanel();
inputPanel = new JPanel();
namePanel = new JPanel();
connectPanel = new JPanel();
textArea.setEditable(false);
textPanel.add(textScroll);
stringList = new ArrayList<String>();
list = new JList(stringList.toArray());
list.setFixedCellWidth(CELL_WIDTH);
listScroll = new JScrollPane(list);
listPanel.add(listScroll);
listAndLogoutPanel = new JPanel(new BorderLayout());
listAndLogoutPanel.add(listPanel, BorderLayout.CENTER);
listAndLogoutPanel.add(logoutButton, BorderLayout.SOUTH);
listAndLogoutPanel.add(loginButton, BorderLayout.NORTH);
inputField.setEditable(false);
inputPanel.add(inputField);
inputAndDisplayPanel = new JPanel(new BorderLayout());
inputAndDisplayPanel.add(textPanel, BorderLayout.CENTER);
inputAndDisplayPanel.add(inputPanel, BorderLayout.SOUTH);
nameField.setEnabled(false);
loginButton.setEnabled(false);
logoutButton.setEnabled(false);
namePanel.setLayout(new FlowLayout(FlowLayout.LEFT));
namePanel.add(new JLabel("Name: "));
namePanel.add(nameField);
connectPanel.add(connectButton);
JPanel topPanel = new JPanel(new BorderLayout());
topPanel.add(namePanel, BorderLayout.WEST);
topPanel.add(connectPanel, BorderLayout.EAST);
JPanel centerPanel = new JPanel(new BorderLayout());
centerPanel.add(inputAndDisplayPanel, BorderLayout.CENTER);
centerPanel.add(topPanel, BorderLayout.NORTH);
JPanel eastPanel = new JPanel(new BorderLayout());
eastPanel.add(listAndLogoutPanel, BorderLayout.CENTER);
setLayout(new BorderLayout());
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setVisible(true);
add(centerPanel, BorderLayout.CENTER);
add(eastPanel, BorderLayout.EAST);
pack();
setLocationRelativeTo(null);
//Listeners
loginButton.addActionListener(new ActionListener(){
@Override
public void actionPerformed(ActionEvent e) {
name = nameField.getText();
if(name.trim().length() < 3 || name.length() > 15 || name == null){
JOptionPane.showMessageDialog(client,
"Error: Name must be between 3 and 15 characters long.", "Error",
JOptionPane.ERROR_MESSAGE);
}else{
Message message = new Message(name, 0);
try {
out.writeObject(message);
out.flush();
} catch (IOException e1) {
e1.printStackTrace();
}
loggedIn = true;
}
}
});
connectButton.addActionListener(new ActionListener(){
@Override
public void actionPerformed(ActionEvent arg0) {
needServerData = true;
if(previouslyLoggedIn) socket = null;
}
});
inputField.addActionListener(new ActionListener(){
@Override
public void actionPerformed(ActionEvent arg0) {
Message message = new Message(name, inputField.getText(), 1);
try {
out.writeObject(message);
out.flush();
} catch (IOException e1) {
e1.printStackTrace();
}
inputField.setText(null);
}
});
logoutButton.addActionListener(new ActionListener(){
@Override
public void actionPerformed(ActionEvent arg0) {
Message message = new Message(name, 2);
try {
out.writeObject(message);
out.flush();
} catch (IOException e1) {
e1.printStackTrace();
}
inputField.setText(null);
}
});
}
}
邮件类:
import java.io.Serializable;
public class Message implements Serializable{
private int number;
private String message;
private String name;
public Message(String name, int number){
this.name = name;
this.number = number;
}
public Message(String name, String message, int number){
this.message = message;
this.number = number;
this.name = name;
}
public boolean isLogin(){
if(number == 0) return true;
return false;
}
public boolean isMessage(){
if(number == 1) return true;
return false;
}
public boolean isLogout(){
if(number == 2) return true;
return false;
}
public int getNumber() {
return number;
}
public String getMessage() {
return message;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
我知道这是一个很大的代码块,而不是整齐的文档/格式化,所以我为此道歉。我只是发布了整个内容,因此您可以通过运行其中的代码来复制问题。另外,忽略JList,因为我还没有达到这个目标,因为我遇到了并且无法解决日志记录/套接字问题。要重新创建该错误,请执行以下步骤:
在此先感谢您的帮助,我很感激!
答案 0 :(得分:2)
在您的ChatClient类中,您需要更改行
} else if (message.getNumber() == 2) {
到
} else if (message.getNumber() == 2 && message.getName().equals(name)) {
由于当前的方式,每个聊天客户端都会收到客户端已注销的消息,并且每个客户端都通过注销来响应该消息。之后你需要处理附加的case.getNumber()== 2,因为现在你会认为它意味着这个名字已被采用。