关闭一个套接字会关闭所有这些,Java

时间:2014-12-14 08:26:51

标签: java sockets

我有一个连接到一台服务器的多线程聊天室。它们彼此独立地连接,登录和消息都很好,但是当我与其中一个客户端注销(并且服务器为该客户端实例执行了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,因为我还没有达到这个目标,因为我遇到了并且无法解决日志记录/套接字问题。要重新创建该错误,请执行以下步骤:

  1. 运行Server类
  2. 运行客户端类,输入&#34; localhost&#34;作为服务器地址,然后输入名称并单击登录
  3. 运行另一个客户端类并按照步骤2
  4. 运行
  5. 点击其中一个客户端类的注销,您将看到另一个也已注销。
  6. 在此先感谢您的帮助,我很感激!

1 个答案:

答案 0 :(得分:2)

在您的ChatClient类中,您需要更改行

} else if (message.getNumber() == 2) {

} else if (message.getNumber() == 2 && message.getName().equals(name)) {

由于当前的方式,每个聊天客户端都会收到客户端已注销的消息,并且每个客户端都通过注销来响应该消息。之后你需要处理附加的case.getNumber()== 2,因为现在你会认为它意味着这个名字已被采用。