Я работаю над простой Java-клиент-серверной игрой. И сервер, и клиент запускают несколько потоков одновременно. На сервере есть две параллельные задачи (вызываемые на нескольких потоках), которые произвольно сражаются друг с другом. Рано или поздно, после того, как клиент подключается к серверу, они блокируют друг друга (не знаю, может быть, тупик, может быть, гонка) и все соединения зависают. Я проверил много связанных тем здесь, перепробовал много модификаций, много изменений дизайна и логики, как на клиенте, так и на сервере. К сожалению, пока не удалось ее решить. Если вам нужны какие-либо другие занятия, не стесняйтесь спрашивать. Любая помощь очень ценится.

Ниже приведены два класса задач блокировки (на стороне сервера).

public class ClientUpdateTask extends AbstractClientTask {

private final String location = "update task execute";

public ClientUpdateTask(String clientToken, ServerManager serverManager, 
        ReadWriteLock clientTaskLock) {
    super(clientToken, serverManager, GameConditions.UPDATE_TASK, clientTaskLock);
}

@Override
public Boolean call() throws /*Exception*/ IOException {
    if(clientTaskLock.readLock().tryLock()) {
    try {
        execute();
        return true;
    } catch(IOException e) {
        String exLocation = "update task execute ioex";
        ClientDisconnectedException cde = new ClientDisconnectedException(
                clientToken, exLocation, e.getMessage(), e);
        error = cde;
        throw cde;
        //throw new RuntimeException("ex updating clients", e);
        //throw e;
    } finally {clientTaskLock.readLock().unlock();}
    }
    return false;
}

@Override
protected void execute() throws IOException {
    if(!serverManager.areAtleastTwoConnected())
        return;
    Client client = serverManager.getClient(clientToken, location);
    String clientAddress = client.getSocket().getRemoteSocketAddress().toString();
    client.printLine(GameConditions.SERVER_MODE_SEND);
    String inSerEntsLenTxt = client.readLine();
    printClientPacket(clientAddress, "after 1st readl, ents len: "+inSerEntsLenTxt);
    if(inSerEntsLenTxt != null && !(inSerEntsLenTxt.isEmpty())) {
        int inSerEntsLen = Integer.parseInt(inSerEntsLenTxt);
        inStreamBuff = new char[inSerEntsLen];
        inStreamBuff = client.readCharsToArray(inStreamBuff, 0, inSerEntsLen);
        String inSerEnts = new String(inStreamBuff);
        printClientPacket(clientAddress, "in ser ents: "+inSerEnts);
        //List<Client> clients = serverManager.getClients();
        if(clientTaskLock.writeLock().tryLock()) {
        try {
        for(Client clientOther : serverManager.getClients()) {
            String clientOtherToken = clientOther.getToken();
            if(!clientOtherToken.equals(clientToken)) {
                clientOther.printLine(GameConditions.SERVER_MODE_RECEIVE);
                clientOther.printLine(inSerEntsLen);
                clientOther.writeChars(inSerEnts);
            }
        }
        } finally {clientTaskLock.writeLock().unlock();}
        }
    }
}

}

public class ClientHeartbeatTask extends AbstractClientTask {

private final String location = "heartbeat task execute";

public ClientHeartbeatTask(String clientToken, ServerManager serverManager,
        ReadWriteLock clientTaskLock) {
    super(clientToken, serverManager, GameConditions.HEARTBEAT_TASK, clientTaskLock);
}

@Override
public Boolean call() throws /*Exception*/ IOException {
    if(clientTaskLock.readLock().tryLock()) {
    try {
        execute();
        return true;
    } catch(IOException e) {
        String exLocation = "heartbeat task execute ioex";
        ClientDisconnectedException cde = new ClientDisconnectedException(
                clientToken, exLocation, e.getMessage(), e);
        error = cde;
        throw cde;
        //throw new RuntimeException("ex listening client heartbeat", e);
        //throw e;
    } finally {clientTaskLock.readLock().unlock();}
    }
    return false;
}

@Override
protected void execute() throws IOException {
    Client client = serverManager.getClient(clientToken, location);
    String clientAddress = client.getSocket().getRemoteSocketAddress().toString();
    String inToken = "";
    //System.out.println("listening...");
    client.printLine(GameConditions.SERVER_MODE_HEARTBEAT); //1
    client.printLine(clientToken);
    inToken = client.readLine(); //2
    if(inToken != null) {
        Matcher tokenMatcher = GameConditions.TOKEN_PATTERN.matcher(inToken);
        //System.out.println("in token not null");
        if(tokenMatcher.matches()) {
            printClientPacket(clientAddress, "after 1st readl, token matched: " + inToken);
            serverManager.updateClient(clientToken, location);
        }
    } else {
        long idle = (long) (System.currentTimeMillis() - client.getLastHeartBeat());
        //System.out.println("idle: " + idle);
        if(idle > GameConditions.CLIENT_MAX_IDLE_TIME) {
            serverManager.disconnectClient(clientToken);
            throw new IOException("client disconnected, throw to "
                + "close resources and stop thread");
        }
    }
}

}

< Сильный > ИЗМЕНИТЬ Не важно, что это важно сказать, но лучшее, что мне удалось сделать, - это то, что задачи на стороне клиента (которые общаются с этими двумя) получают сообщения от сервера (до тех пор, пока не начнется этот конфликт). Но сервер молчит, сообщений не получено.

0
10101101 13 Дек 2019 в 15:51

1 ответ

Я забыл опубликовать решение, так что вот оно. Удален прямой доступ к потоку ввода / вывода из задач обновления и пульса. Они оба работают в нескольких потоках как на клиенте, так и на сервере, и считают, что это было причиной блокировки потоков и всей связи. Вместо этого перемещен прямой потоковый доступ к отдельному классу (клиенту), который имеет два ArrayBlockingQueues и предоставляет потокобезопасный интерфейс для потокового чтения и записи сокетов. Все входящие сообщения добавляются во входную очередь, а все исходящие сообщения добавляются в выходную очередь. Теперь создано еще две задачи как на клиенте, так и на сервере: одна для чтения из входной очереди и другая для записи из выходной очереди. Кроме того, во время процесса большая часть моих синхронизаций и блокировок оказалась бесполезной (и, вероятно, также могла блокировать потоки и, безусловно, замедлять производительность).

Изменено обновление задачи:

public class ClientUpdateTask extends AbstractClientTask {

    private final String location = "update task execute";

    public ClientUpdateTask(String clientToken, ServerManager serverManager, 
            ReadWriteLock clientTaskLock) {
        super(clientToken, serverManager, GameConditions.UPDATE_TASK, clientTaskLock);
    }

    @Override
    public Boolean call() throws /*Exception*/ IOException {
        try {
            execute();
            return true;
        } catch(IOException e) {
            String exLocation = "update task execute ioex";
            ClientDisconnectedException cde = new ClientDisconnectedException(
                    clientToken, exLocation, e.getMessage(), e);
            error = cde;
            throw cde;
        }
    }

    @Override
    protected void execute() throws IOException {
        if(!serverManager.areAtleastTwoConnected())
            return;
        Client client = serverManager.getClient(clientToken, location);
        String clientAddress = client.getSocket().getRemoteSocketAddress().toString();
        String inMessage = client.peekInMessage();
        if(inMessage != null && !inMessage.isEmpty() && 
                inMessage.startsWith(GameConditions.SERVER_MODE_SEND)) {
            client.pollInMessage();
            String[] inMessageSplitArray = inMessage.split(
                    GameConditions.MESSAGE_FRAGMENT_SEPARATOR);
            String inSerEnts = inMessageSplitArray[1];
            for(Client clientOther : serverManager.getClients()) {
                String clientOtherToken = clientOther.getToken();
                if(!clientOtherToken.equals(clientToken)) {
                    String outMessage = GameConditions.SERVER_MODE_RECEIVE
                        + GameConditions.MESSAGE_FRAGMENT_SEPARATOR + inSerEnts;
                    clientOther.pushOutMessage(outMessage);
                }
            }
        }
        String outMessage = GameConditions.SERVER_MODE_SEND;
        client.pushOutMessage(outMessage);
    }
}

Изменено задание сердцебиения:

public class ClientHeartbeatTask extends AbstractClientTask {

    private final String location = "heartbeat task execute";

    public ClientHeartbeatTask(String clientToken, ServerManager serverManager,
            ReadWriteLock clientTaskLock) {
        super(clientToken, serverManager, GameConditions.HEARTBEAT_TASK, clientTaskLock);
    }

    @Override
    public Boolean call() throws /*Exception*/ IOException {
        try {
            execute();
            return true;
        } catch(IOException e) {
            String exLocation = "heartbeat task execute ioex";
            ClientDisconnectedException cde = new ClientDisconnectedException(
                    clientToken, exLocation, e.getMessage(), e);
            error = cde;
            throw cde;
        }
    }

    @Override
    protected void execute() throws IOException {
        Client client = serverManager.getClient(clientToken, location);
        String clientAddress = client.getSocket().getRemoteSocketAddress().toString();
        String inMessage = client.peekInMessage(); //2
        if(inMessage != null && !inMessage.isEmpty() && 
                inMessage.startsWith(GameConditions.SERVER_MODE_HEARTBEAT)) {
            client.pollInMessage();
            String[] inMessageSplitArray = inMessage.split(
                    GameConditions.MESSAGE_FRAGMENT_SEPARATOR);
            String inToken = inMessageSplitArray[1];
            Matcher tokenMatcher = GameConditions.TOKEN_PATTERN.matcher(inToken);
        }
        String outMessage = GameConditions.SERVER_MODE_HEARTBEAT + 
            GameConditions.MESSAGE_FRAGMENT_SEPARATOR + clientToken;
        client.pushOutMessage(outMessage);
        long idle = (long) (System.currentTimeMillis() - client.getLastHeartBeat());
        if(idle > GameConditions.CLIENT_MAX_IDLE_TIME) {
            System.out.println("client idle time expired");
            serverManager.disconnectClient(clientToken);
            throw new IOException("client disconnected, throw to "
                + "close resources and stop thread");
        }
    }

}

Новая задача чтения ввода:

public class ClientInputListenerTask extends AbstractClientTask {

    private final String location = "input listener task execute";

    public ClientInputListenerTask(String clientToken, ServerManager serverManager, 
            ReadWriteLock clientTaskLock) {
        super(clientToken, serverManager, GameConditions.UPDATE_TASK, clientTaskLock);
    }

    @Override
    protected void execute() throws IOException {
        Client client = serverManager.getClient(clientToken, location);
        String inputLine = client.readLine();
        if(inputLine != null && !inputLine.isEmpty()) {
            client.pushInMessage(inputLine);
            System.out.println(client.getToken()+", in message: "+inputLine
                +", last heartbeat: "+client.getLastHeartBeat());
            serverManager.updateClient(client.getToken(), location);
        }
    }

    @Override
    public Boolean call() throws Exception {
        try {
            execute();
            return true;
        } catch(IOException e) {
            String exLocation = "input listener task execute ioex";
            ClientDisconnectedException cde = new ClientDisconnectedException(
                    clientToken, exLocation, e.getMessage(), e);
            error = cde;
            throw cde;
        }
    }
}

Новая задача записи вывода:

public class ClientOutputPrinterTask extends AbstractClientTask {

    private final String location = "input listener task execute";

    public ClientOutputPrinterTask(String clientToken, ServerManager serverManager, 
            ReadWriteLock clientTaskLock) {
        super(clientToken, serverManager, GameConditions.UPDATE_TASK, clientTaskLock);
    }

    @Override
    protected void execute() throws IOException {
        Client client = serverManager.getClient(clientToken, location);
        String outputLine = client.pollOutMessage();
        if(outputLine != null)
            client.printLine(outputLine);
    }

    @Override
    public Boolean call() throws Exception {
        try {
            execute();
            return true;
        } catch(IOException e) {
            String exLocation = "input listener task execute ioex";
            ClientDisconnectedException cde = new ClientDisconnectedException(
                    clientToken, exLocation, e.getMessage(), e);
            error = cde;
            throw cde;
        }
    }
}
0
10101101 23 Дек 2019 в 22:18