删除此线程时,List.Remove()无效

时间:2018-04-07 11:54:26

标签: java

所以我正在处理一个处理一些命令的服务器,一个小问题就是在用户决定注销时尝试删除活动客户端列表。每个客户端都在一个线程中处理,一旦命令完成,这个活动的客户端就会被删除,但不会被删除。

以下是删除活动客户端的示例,此线程为

public class serverHandlerThread implements Runnable
{
    private Socket socket;
    //private BufferedWriter clientOut;
    private ObjectOutputStream toClient;
    private MainServer server;
    private Users user;

    //Constructor
    serverHandlerThread(MainServer server, Socket socket)
    {
        this.server = server;
        this.socket = socket;
    }

    private ObjectOutputStream getWriter()
    {
        return toClient;
    }

    private void deleteClient(serverHandlerThread obj)
    {
        synchronized (server.clients)
        {
            server.clients.remove(obj);
        }
    }

    @Override
    public void run ()
    {
        try
        {
            //Setup I/O
            toClient = new ObjectOutputStream(socket.getOutputStream());
            ObjectInputStream fromClient = new ObjectInputStream(socket.getInputStream());

            while(!socket.isClosed())
            {
                //If server has received a message
                if(fromClient.available() > 0)
                {
                    //Reads message and objects from client
                    String input = fromClient.readUTF();
                    Object obj = fromClient.readObject();

                    //logger(input);

                    switch (input)
                    {
                        //Logout the user
                        case ".logout":
                            //Set the user to being logged out and print the log
                            user = (Users) obj;

                            deleteClient(this);

                            for (int i = 0; i < server.usersList.size(); i++)
                            {
                                if (user.getUserName().equals(server.usersList.get(i).getUserName()))
                                {
                                    server.usersList.get(i).setLoggedIn(false);
                                    logger(user.getUserName() + " has logged out");
                                }
                            }

                            break;

                        //Push message received to other clients
                        default:
                            logger("Sending message to clients");
                            user = (Users) obj;

                            deleteClient(this);


                            logger("clients size is: " + String.valueOf(server.clients.size()));

                            for (serverHandlerThread thatClient : server.getClients())
                            {
                                ObjectOutputStream thatClientOut = thatClient.getWriter();
                                if (thatClientOut != null)
                                {
                                    thatClientOut.writeUTF(user.getUserName() + ": " + input + "\r\n");
                                    thatClientOut.flush();
                                }
                            }
                            break;
                    }
                }

            }
        }
        catch (IOException | ClassNotFoundException e)
        {
            e.printStackTrace();
        }
    }
}

server属于MainServer类型,其中包含客户列表,并写为List<ServerHandlerThread> clients。当接受新客户时,MainServer会调用serverHandlerThread,即。使服务器成为多线程。

问题是当客户端请求注销时,它应该从活动客户端列表中删除用户。当服务器尝试将消息推送到所有客户端时,它也没有这样做,它也尝试向已关闭套接字的用户(注销用户)写入消息,因此服务器吐出broken pipe错误。有任何想法吗?

*修改 有关mainServer类的更多信息,省略了一些内容,但这应该是足够的信息

public class MainServer
{

    //Static variables
    private static final int portNumber = 4444;

    //Variables
    private int serverPort;
    private List<serverHandlerThread> clients;

    /**
     * Very basic logger that prints out
     * the current time and date
     * @param msg used when printing the log
     */
    private void logger(String msg)
    {
        System.out.println(LocalDate.now()+ " " +LocalTime.now() + " - " +msg);
    }

    private List<serverHandlerThread> getClients()
    {
        return clients;
    }

    //Starts the server and begins accepting clients
    private void startServer()
    {
        clients = new ArrayList<>();
        ServerSocket serverSocket;
        try
        {
            serverSocket = new ServerSocket(serverPort);
            acceptClients(serverSocket);
        }
        catch (IOException e)
        {
            logger("Could not listen on port: " + serverPort);
            System.exit(1);
        }
    }

    //Continuously accept clients
    private void acceptClients(ServerSocket serverSocket)
    {
        logger("Server starts port = " + serverSocket.getLocalSocketAddress());
        while (true)
        {
            try
            {
                Socket socket = serverSocket.accept();
                //logger("Accepts: " + socket.getRemoteSocketAddress());
                serverHandlerThread client = new serverHandlerThread(this, socket);
                Thread thread = new Thread(client);
                thread.setDaemon(true);
                thread.start();

                synchronized(clients)
                {
                    clients.add(client);
                }


            }
            catch (IOException e)
            {
                System.err.println("Accept failed on:" + serverPort);
            }
        }
    }

    public MainServer(int portNumber)
    {
        this.serverPort = portNumber;
    }

    public static void main(String[] args) 
    {
        MainServer server  = new MainServer(portNumber);
        server.startServer();

    }
}

*编辑2 所以我做了一个小方法来同步所有线程的客户端列表并编辑mainServer来做到这一点但问题仍然存在

private void deleteClient(serverHandlerThread obj)
{
    synchronized (server.clients)
    {
        server.clients.remove(obj);
    }
}

1 个答案:

答案 0 :(得分:0)

您应该使用客户端管理器模式重构代码,以避免您目前遇到的问题:

  • 您正在从N个线程管理您的客户端,其中一个是服务器。
  • 您可以访问一个具有各种同步形式的列表,这可能会导致同步问题,因为代码已全部结束。

鉴于这种模式,这里有一个例子(我使用了synchronized,但其他形式的同步可能有效):

class ClientManager {
  private final List<Client> clients;

  public ClientManager() {
    this.clients = new ArrayList<>();
  }

  public synchronized void add(Client client) {
    this.clients.add(client);
  }

  public synchronized void remove(Client client) {
    this.clients.remove(client);
  }

  public synchronized List<Client> list() {
    return new ArrayList<>(this.clients);
  }

}

ClientServerHandlerThread)和ServerMainServer)都将播放ClientManager:我的观点是此课程正在进行所有同步工作而不是服务器/客户端。

我使用列表的副本来最小化锁定时间(否则,客户端将等待其他线程调用list())。这意味着当您发送消息时,此处可能会发生Client注销:您需要使用标记(活动等)来指示客户端是否仍在那里。

您也可以在sendMessage中查看它,并返回一个状态,指明邮件是否已发送。

class Server {
  private final ClientManager manager = new ClientManager();

  // register new client
  Client newClient() {
    Client client = new Client(manager);
    manager.add(client);
    return client;
  }

  void sendMessageToAll(String msg) {
    for (Client client : manager.list()) {
      // isAlive returns true except if the client was logged out. 
      // It should probably be synchronized too.
      if (client.isAlive()) {
        client.sendMessage(msg);
      }
    }
  }
}

class Client {
  private final ClientManager manager;

  public Client(ClientManager manager) {
    this.manager = manager;
  }

  public void logoff() {
    manager.remove(this);
  }
}

编辑以回答您的评论,我添加了客户端示例以及服务器和客户端如何使用该管理器。

  

只是为了澄清你讨论客户在客户端玩游戏   经理让我困惑。我的服务器接受客户端(socket =   serversocket.accept())并将此套接字发送到新线程,这个   线程处理与客户端的所有通信(消息和   命令)。线程启动后,线程被添加到   客户名单。问题出在客户端发送的线程中   命令线程应该运行命令然后从中删除自己   客户列表(clientlist.remove(this))。您的解决方案是否仍然可行   这里?因为你讨论了客户困惑我

简而言之,是的。

简单:您使用的是List,默认情况下未同步(您可以使用VectorCollections::synchronizedList)。因为你在几个线程中这样做,所以存在同步问题(又名&#34;随机效应&#34; :)。)

您应该使用专用于此用法的类,而不是直接使用列表:ClientManager的用途是什么。将管理Client

列表的对象

这还有另一个好处:代码中没有多个synchronized块,所有代码都在一个地方。