所以我正在处理一个处理一些命令的服务器,一个小问题就是在用户决定注销时尝试删除活动客户端列表。每个客户端都在一个线程中处理,一旦命令完成,这个活动的客户端就会被删除,但不会被删除。
以下是删除活动客户端的示例,此线程为
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);
}
}
答案 0 :(得分:0)
您应该使用客户端管理器模式重构代码,以避免您目前遇到的问题:
鉴于这种模式,这里有一个例子(我使用了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);
}
}
Client
(ServerHandlerThread
)和Server
(MainServer
)都将播放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
,默认情况下未同步(您可以使用Vector
或Collections::synchronizedList
)。因为你在几个线程中这样做,所以存在同步问题(又名&#34;随机效应&#34; :)。)
您应该使用专用于此用法的类,而不是直接使用列表:ClientManager
的用途是什么。将管理Client
。
这还有另一个好处:代码中没有多个synchronized
块,所有代码都在一个地方。