Мне нужно реализовать два объекта игрока в отдельных процессах 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();
}
}
Вышеуказанный класс;
Ниже я прилагаю служебные методы, которые использую для взаимодействия с файлом.
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 Я пробовал. Но точки останова не останавливают программу. :/ поэтому я не могу проверить значение.
Затем попробуйте добавить журналирование для каждого процесса, чтобы увидеть, что происходит.
Вы неправильно настроили 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) {
Вы пробовали отлаживать программу?