Ошибка при чтении файла, используемого двумя отдельными процессами Java

Мне нужно реализовать два объекта игрока в отдельных процессах JAVA. Объекты игрока должны отправлять друг другу сообщения туда и обратно, и между ними должна быть общая переменная-счетчик, к которой должно быть добавлено сообщение, передаваемое между этими объектами.

С каждым сообщением переменная счетчика должна увеличиваться.

Что я реализовал на данный момент

У меня 2 класса. ProcessPlayer класс и Main класс.

Main класс создаст два процесса. Каждый игрок принадлежит одному процессу.

Чтобы иметь общую переменную счетчика, я решил использовать простой текстовый файл, который будет иметь переменную счетчика, и оба процесса будут читать значение, добавлять к сообщению, обновлять значение в файле для следующего процесса. Ниже приведен код класса Main.java.

public class Main {
public static void main(String[] args) throws FileNotFoundException {
    System.out.println("Implementing players on different processes.\n");

    Utils.createFile();     // both players on different processes will use this common file.

    String classpath = Paths.get(".").toAbsolutePath().normalize().toString();
    classpath = classpath + "\\src";

    ProcessBuilder secondPlayerProcessBuilder = new ProcessBuilder(
            "java", "-cp", classpath, "ProcessPlayer", "Player2", "5003", "5002", "false"
    ).inheritIO();

    ProcessBuilder firstPlayerProcessBuilder = new ProcessBuilder(
            "java", "-cp", classpath, "ProcessPlayer", "Player1","5002", "5003", "true"
    ).inheritIO();

    try {
        Process secondPlayerProcess = secondPlayerProcessBuilder.start();
        Process firstPlayerProcess = firstPlayerProcessBuilder.start();

        secondPlayerProcess.waitFor();
        firstPlayerProcess.waitFor();

        secondPlayerProcess.destroy();
        firstPlayerProcess.destroy();

        System.out.println("First player process exited with code: " + firstPlayerProcess.exitValue());
        System.out.println("Second player process exited with code: " + secondPlayerProcess.exitValue());

        Utils.deleteFile();
    } catch (IOException | InterruptedException exception) {
        exception.printStackTrace();
    }
}

Вышеуказанный класс;

  1. Изначально создает файл со значением счетчика 0.
  2. Создает два процесса, в которых работают объекты «ProcessPlayer».

Ниже я прилагаю служебные методы, которые использую для взаимодействия с файлом.

public class Utils {
public static void createFile() {
    try {
        if (new File("messageCounter.txt").createNewFile()) {
            System.out.println("File Created");
            PrintWriter writer = new PrintWriter(new FileWriter("messageCounter.txt"));
            writer.print(0);
            writer.close();
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
}

public static void deleteFile() {
    boolean isDeleted = new File("messageCounter.txt").delete();
    if (isDeleted) {
        System.out.println("File removed!");
    }
}

public static void updateCounter(int newCounter) throws IOException {
    PrintWriter printWriter = new PrintWriter(new FileWriter("messageCounter.txt"));
    printWriter.print(newCounter);
    printWriter.close();
}

}

Точнее, «updateCounter» — это метод, который будет использоваться обоими процессами во время обратной и обратной связи.

И, наконец, ниже я прикрепляю класс «ProcessPlayer.java».

public class ProcessPlayer {
int numberOfMessagesSent;
int maxMessages;
static int messageCounter = 0;

boolean isInitiator;
private String playerName;
int port;
int partnerPort;

private ProcessPlayer(ProcessPlayer.PlayerBuilder builder) {
    this.numberOfMessagesSent = builder.numberOfMessagesSent;
    this.maxMessages = builder.maxMessages;
    this.isInitiator = builder.isInitiator;
    this.playerName = builder.playerName;
    this.port = builder.port;
    this.partnerPort = builder.partnerPort;
}

public void start() {
    new Thread(this::receiveMessage).start();
    if (this.isInitiator) {
        sendMessage("HelloWorld!!", true);
    }
}

// read counter from file, append it to message and increment it. Update the file with incremented counter.
public void sendMessage(String message, boolean initialMessage) {
    try {
        Socket socket = new Socket("localhost", this.partnerPort);
        PrintWriter stream = new PrintWriter(socket.getOutputStream(), true);
        String senderInfo = "This message was sent from " + this.getPlayerName() + ":";

        FileReader fileReader = new FileReader("messageCounter.txt");
        int character;
        StringBuilder counterFromFile = new StringBuilder();

        while (counterFromFile.toString().isEmpty()) {
            while ((character = fileReader.read()) != -1) {
                counterFromFile.append((char) character);
            }
        }

        fileReader.close();

        if (numberOfMessagesSent < maxMessages) {
            numberOfMessagesSent++;
            if (initialMessage) {
                stream.println(senderInfo + message);
            }
            else if (Integer.parseInt(String.valueOf(counterFromFile)) == 0) {
                stream.println(senderInfo + message + counterFromFile);
                Utils.updateCounter(Integer.parseInt(counterFromFile.toString()) + 1);
            }
            else {
                stream.println(senderInfo + message + counterFromFile);
                Utils.updateCounter(Integer.parseInt(counterFromFile.toString()) + 1);
            }
        }
    } catch(Exception e) {
        e.printStackTrace();
    }
}

public void receiveMessage() {
    try {
        ServerSocket serverSocket = new ServerSocket(this.port);
        while(numberOfMessagesSent < maxMessages) {
            Socket s = serverSocket.accept();
            BufferedReader reader = new BufferedReader(new InputStreamReader(s.getInputStream()));
            String message = reader.readLine();
            System.out.println(message);
            message = message.substring(message.lastIndexOf(":") + 1);
            if (messageCounter < maxMessages) {
                sendMessage(message, false);
            }
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
}

public String getPlayerName() {
    return playerName;
}

public static class PlayerBuilder {
    int numberOfMessagesSent;
    int maxMessages;
    boolean isInitiator;
    String playerName;
    int port;
    int partnerPort;

    public PlayerBuilder(int port, int partnerPort) {
        this.numberOfMessagesSent = 0;
        this.maxMessages = 10;
        this.port = port;
        this.partnerPort = partnerPort;
    }

    public ProcessPlayer.PlayerBuilder setInitiator(boolean isInitiator) {
        this.isInitiator = isInitiator;
        return this;
    }

    public ProcessPlayer.PlayerBuilder setName(String name) {
        this.playerName = name;
        return this;
    }

    public ProcessPlayer build() {
        return new ProcessPlayer(this);
    }
}

/**
 * This method initializes a player instance with the required parameters
 * (name, port, and partner's port) and starts the communication process.
 * This will be one of the two processes.
 * @param args - The value in args array will be passed from Main.java via ProcessBuilderObject
 */
public static void main(String[] args) {
    if (args.length != 4) {
        System.out.println("Usage: java Player <name> <port> <partnerPort> <isInitiator>");
        return;
    }
    String name = args[0];
    int port = Integer.parseInt(args[1]);
    int partnerPort = Integer.parseInt(args[2]);
    boolean isInitiator = Boolean.parseBoolean(args[3]);

    try {
        ProcessPlayer player = new ProcessPlayer.PlayerBuilder(port, partnerPort)
                .setInitiator(isInitiator)
                .setName(name).build();
        player.start();
    } catch (Exception e) {
        System.out.println("Exception caught");
        throw e;
    }
}

}

Проблема

Я наблюдаю непоследовательное поведение. Если я полностью перекомпилирую классы «Main.java» и «ProcessPlayer.java» и запущу приложение, я получу следующий результат.

This message was sent from Player1:HelloWorld!!
This message was sent from Player2:HelloWorld!!0
This message was sent from Player1:HelloWorld!!01
This message was sent from Player2:HelloWorld!!011
This message was sent from Player1:HelloWorld!!0112
This message was sent from Player2:HelloWorld!!01123
This message was sent from Player1:HelloWorld!!011234
This message was sent from Player2:HelloWorld!!0112345
This message was sent from Player1:HelloWorld!!01123456
This message was sent from Player2:HelloWorld!!011234567

и так далее. Как вы можете видеть в приведенном выше выводе, 1 добавляется дважды, этого также не должно происходить. Однако во второй раз, когда я запускаю программу, я получаю следующую ошибку.

This message was sent from Player1:HelloWorld!!
This message was sent from Player2:HelloWorld!!0
java.lang.NumberFormatException: For input string: ""
    at 
    java.base/java.lang.NumberFormatException.forInputString(NumberFormatException.java:67)
    at java.base/java.lang.Integer.parseInt(Integer.java:565)
    at java.base/java.lang.Integer.parseInt(Integer.java:685)
    at ProcessPlayer.sendMessage(ProcessPlayer.java:61)
    at ProcessPlayer.receiveMessage(ProcessPlayer.java:85)
    at java.base/java.lang.Thread.run(Thread.java:1570)

И тогда мне придется вручную убить дочерние процессы из CMD. Если вы видите метод sendMessage класса ProcessBuilder.java, я читаю файл посимвольно и создаю строку из asciis, вот откуда эта ошибка. Если я использую объект класса Scanner, он выдает исключение java.util.NoSuchElementException при попытке прочитать файл, указывая, что файл пуст, когда это НЕ так. Спасибо за ваше время и любая помощь очень ценится.

Вы пробовали отлаживать программу?

aled 30.06.2024 04:29

@aled Я пробовал. Но точки останова не останавливают программу. :/ поэтому я не могу проверить значение.

Usman_Codes._. 30.06.2024 11:56

Затем попробуйте добавить журналирование для каждого процесса, чтобы увидеть, что происходит.

aled 30.06.2024 17:50
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
0
3
85
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Вы неправильно настроили sendMessage в этих строках:

stream.println(senderInfo + message + counterFromFile);
Utils.updateCounter(Integer.parseInt(counterFromFile.toString()) + 1);

Это означает, что вы отправляете сообщение другому процессу перед обновлением счетчика файлов. Однако другой обработчик процесса receiveMessage может получить сообщение и открыть файл до или во время updateCounter вызова отправителя — отсюда ваше наблюдение неправильного значения счетчика или усеченного значения.

Исправление должно быть простым: поменяйте местами строки, чтобы обновить счетчики перед отправкой сообщения парному процессу:

Utils.updateCounter(Integer.parseInt(counterFromFile.toString()) + 1);
stream.println(senderInfo + message + counterFromFile);

Также следует привести в порядок код, так как он дублирует приведенные выше вызовы, ветка для проверки не нужна counter == 0:

else if (Integer.parseInt(String.valueOf(counterFromFile)) == 0) {
  

Другие вопросы по теме

Elispprocess-send-string выдает неожиданную ошибку «процесс не запущен: прослушайте»
Мой код работает только с одним файлом изображения, остальные файлы изображений считываются и сохраняются, но мой код не затрагивает их. Мне нужно, чтобы мой код работал со всеми файлами изображений
Процесс FFmpeg завершает запись файла только после закрытия программы
ProcessBuilder не может запустить программу, найденную в PATH
Как получить полное имя пользователя владельца процесса с помощью ps
Попытка остановить процесс msedge по заголовку окна
Как мне заставить обработчик сигналов перехватывать сигнал, отправленный дочерним процессом?
Golang-копия родительского процесса не может выполнять вызовы https/tls и получает сообщение «tls: не удалось проверить сертификат»
Взаимодействие процессов Java и Python через некоторое время зависает при использовании readline(), но не input()
Почему существует разница между используемой оперативной памятью в диспетчере задач и измерением с помощью psutil из Python?

Похожие вопросы

Bean-компонент типа «org.springframework.security.crypto.password.PasswordEncoder», который не удалось найти
Общий метод для преобразования List<CustomObject> в Chunk<CustomObject>
Проблема Spring Security с JWT: невозможно создать подкласс финального класса JwtAuthenticationProvider
Java АОrollPane не будет изменять размер ниже минимального размера JButton с текстом
Где скачать eclipse IDE (есть две основные ссылки, и все они совершенно разные установщики)
Издевающийся объект, вызываемый в лямбда-выражении, вызванный из издевающегося объекта
Почему BFS работает намного быстрее, чем DFS, когда я реализую их оба?
Шаблон проектирования интерфейса для классов.... Кто-нибудь использует шаблон проектирования интерфейса для классов?
Запуск4j | «Для этого приложения требуется среда выполнения Java». при открытии .exe это файл .jre, преобразованный в .exe
Джексон десериализует общий класс с полем T как массив POJO или String