From e533262c3812b4d43d3e236d9e9cb066823a8112 Mon Sep 17 00:00:00 2001 From: ginkelmath Date: Wed, 11 Mar 2026 21:37:35 +0100 Subject: [PATCH 1/5] Add: First version of a Client-side Networking Service --- .../cs108/casono/client/chat/Message.java | 57 +++++++++ .../casono/client/network/ClientService.java | 110 ++++++++++++++++++ .../casono/client/network/sendRequest.java | 49 ++++++++ .../dmi/dbis/cs108/casono/server/Server.java | 59 ++++++++++ .../client/network/ClientServiceTest.java | 35 ++++++ 5 files changed, 310 insertions(+) create mode 100644 src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/chat/Message.java create mode 100644 src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/network/ClientService.java create mode 100644 src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/network/sendRequest.java create mode 100644 src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/Server.java create mode 100644 src/test/java/ch/unibas/dmi/dbis/cs108/casono/client/network/ClientServiceTest.java diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/chat/Message.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/chat/Message.java new file mode 100644 index 0000000..44e2c45 --- /dev/null +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/chat/Message.java @@ -0,0 +1,57 @@ +package ch.unibas.dmi.dbis.cs108.casono.client.chat; + +import java.time.LocalTime; + +public class Message { + private String message; + public String name; + public int hourTime; + public int minuteTime; + public int game_id = 0; + public String target = null; + + // For Global Chat + + public Message(String message, String name) { + this.message = message; + this.name = name; + LocalTime now = LocalTime.now(); + this.hourTime = now.getHour(); + this.minuteTime = now.getMinute(); + } + + // For Lobby Chat + + public Message(String message, String name, int game_id) { + this.message = message; + this.name = name; + LocalTime now = LocalTime.now(); + this.hourTime = now.getHour(); + this.minuteTime = now.getMinute(); + this.game_id = game_id; + } + + // For Whisper Chat + + public Message(String message, String name, int game_id, String target) { + this.message = message; + this.name = name; + LocalTime now = LocalTime.now(); + this.hourTime = now.getHour(); + this.minuteTime = now.getMinute(); + this.game_id = game_id; + this.target = target; + } + + public String getMessage() { + return message; + } + + public String toString() { + return String.format("%s: %s", this.name, this.message); + } + + public void print() { + System.out.println(this.toString()); + } +} diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/network/ClientService.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/network/ClientService.java new file mode 100644 index 0000000..e8f9f28 --- /dev/null +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/network/ClientService.java @@ -0,0 +1,110 @@ +package ch.unibas.dmi.dbis.cs108.casono.client.network; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.InputStreamReader; +import java.io.OutputStreamWriter; +import java.net.Socket; +import java.util.ArrayList; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; + +import java.io.IOException; +import java.net.UnknownHostException; + +import ch.unibas.dmi.dbis.cs108.casono.client.chat.Message; + +/** + * Responsible for the transferring of the data from the Client to the Server and the other way around + */ + +public class ClientService { + + private Socket socket; + private BufferedReader input; + private BufferedWriter output; + + private String ip; + private int port; + + private ExecutorService executor; + + /** + * Creates a new ClientSession with a Socket, a Reader and Writer of the Input- and the Outputstream and a pool of threads to send requests and receive responses. + * @param ip : ip-adress of the server + * @param port : port of the server + */ + + public ClientService(String ip, int port) { + + this.ip = ip; + this.port = port; + + try { + socket = new Socket(this.ip, this.port); + System.out.println("Connected"); + input = new BufferedReader(new InputStreamReader(socket.getInputStream())); + output = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())); + } + catch (UnknownHostException u) { + System.out.println(u); + return; + } + catch (IOException i) { + System.out.println(i); + return; + } + + executor = Executors.newSingleThreadExecutor(); + + + } + + /** + * Sends a Chat Message to the Server and awaits a response, which will be passed along + * @param message : Protocol Message of type "Message" to be sent to the server + * @return ArrayList : The Response of the Server to be interpreted later + */ + + public Future> sendMessage(Message message) { + + String time = message.hourTime + ":" + message.minuteTime; + + String request = ""; + + if (message.target == null && message.game_id == 0) { + request = String.format("SEND_MESSAGE TYPE=GLOBAL GAME=null USER=%s TARGET=null TEXT=%s TIME=%s", message.name, message.getMessage(), time); + } + else if (message.target == null) { + request = String.format("SEND_MESSAGE TYPE=LOBBY GAME=%d USER=%s TARGET=null TEXT=%s TIME=%s", message.game_id, message.name, message.getMessage(), time); + } else { + request = String.format("SEND_MESSAGE TYPE=WHISPER GAME=%d USER=%s TARGET=%s TEXT=%s TIME=%s", message.game_id, message.name, message.target, message.getMessage(), time); + } + + System.out.println("Writing following request: " + request); + + sendRequest task = new sendRequest(request, input, output); + + Future> response = executor.submit(task); + + return response; + } + + /** + * Closes the Socket and shuts down the Threadpool associated with that Socket-Connection. + */ + + public void closeSocket() { + + try { + executor.shutdown(); + input.close(); + output.close(); + socket.close(); + } catch (IOException j) { + System.out.println(j); + } + + } +} diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/network/sendRequest.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/network/sendRequest.java new file mode 100644 index 0000000..cb0a4fb --- /dev/null +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/network/sendRequest.java @@ -0,0 +1,49 @@ +package ch.unibas.dmi.dbis.cs108.casono.client.network; + +import java.io.BufferedReader; +import java.util.concurrent.Callable; + +import java.util.ArrayList; + +import java.io.*; + +public class sendRequest implements Callable> { + + final private String request; + final private BufferedReader input; + final private BufferedWriter output; + + public sendRequest(String request, BufferedReader input, BufferedWriter output) { + this.request = request; + this.input = input; + this.output = output; + } + + @Override + public ArrayList call() throws Exception { + try { + output.write(request+"\n"); + output.flush(); + System.out.println("Writing following request: " + request); + } catch (Exception e) { + System.out.println(e); + } + + ArrayList response = new ArrayList<>(); + + try { + String line; + while ((line = input.readLine()) != null) { + if (line.toLowerCase().startsWith("ok")) { + break; + } + response.add(line); + } + + System.out.println("Response from server: " + response.get(0)); + } + catch (Exception e) {System.out.println(e);} + + return response; + } +} diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/Server.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/Server.java new file mode 100644 index 0000000..98bbbcc --- /dev/null +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/Server.java @@ -0,0 +1,59 @@ +package ch.unibas.dmi.dbis.cs108.casono.server; + +import java.io.*; +import java.net.ServerSocket; +import java.net.Socket; + +public class Server { + private ServerSocket ss = null; + private BufferedReader input = null; + private BufferedWriter output = null; + + public Server(int port) { + try { + ss = new ServerSocket(port); + System.out.println("Server started"); + } + catch (IOException e) { + System.out.println(e.getMessage()); + } + } + + public void simpleListenLoop() { + System.out.println("Waiting for connection..."); + Socket s = null; + try { + while (true) { + s = ss.accept(); + System.out.println("Accepted connection"); + + input = new BufferedReader(new InputStreamReader(s.getInputStream())); + + output = new BufferedWriter(new OutputStreamWriter(s.getOutputStream())); + String line = null; + while((line = input.readLine())!=null) { + System.out.println("Server got "+ line); + // do something with this line + output.write("OK"+"\n"); + output.flush(); + if(Thread.currentThread().isInterrupted()) { + return; + } + } + if(Thread.currentThread().isInterrupted()) { + return; + } + } + } catch (IOException e) { + throw new RuntimeException(e); + } finally { + try { + s.close(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + } + +} \ No newline at end of file diff --git a/src/test/java/ch/unibas/dmi/dbis/cs108/casono/client/network/ClientServiceTest.java b/src/test/java/ch/unibas/dmi/dbis/cs108/casono/client/network/ClientServiceTest.java new file mode 100644 index 0000000..56cc2d5 --- /dev/null +++ b/src/test/java/ch/unibas/dmi/dbis/cs108/casono/client/network/ClientServiceTest.java @@ -0,0 +1,35 @@ +package ch.unibas.dmi.dbis.cs108.casono.client.network; + +import ch.unibas.dmi.dbis.cs108.casono.client.chat.Message; +import ch.unibas.dmi.dbis.cs108.casono.server.Server; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; + +public class ClientServiceTest { + + Server server = null; + private Thread serverThread; + + @BeforeEach + public void setUp() { + server = new Server(5000); + serverThread = new Thread(()-> server.simpleListenLoop()); + serverThread.start(); + } + @AfterEach + public void tearDown() { + serverThread.interrupt(); + } + @Test + public void testClientService() throws ExecutionException, InterruptedException { + ClientService client = new ClientService("localhost", 5000); + Future> future = client.sendMessage(new Message("bla", "name")); + future.get(); + client.closeSocket(); + } +} From 892f0553f6daaa4ce675309237fd83df2ca36694 Mon Sep 17 00:00:00 2001 From: ginkelmath Date: Sat, 14 Mar 2026 11:46:05 +0100 Subject: [PATCH 2/5] Change protocol implementation --- .../casono/client/network/ClientParser.java | 26 +++++ .../casono/client/network/ClientService.java | 98 ++++++++++++++++--- .../client/network/ResponseException.java | 9 ++ .../casono/client/network/sendRequest.java | 30 +++--- .../client/network/ClientServiceTest.java | 1 + 5 files changed, 137 insertions(+), 27 deletions(-) create mode 100644 src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/network/ClientParser.java create mode 100644 src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/network/ResponseException.java diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/network/ClientParser.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/network/ClientParser.java new file mode 100644 index 0000000..3334a6b --- /dev/null +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/network/ClientParser.java @@ -0,0 +1,26 @@ +package ch.unibas.dmi.dbis.cs108.casono.client.network; + +public class ClientParser { + public static final String Seperator = " "; + + enum Protocol {} + + public void parseMessage(String msg) { + var parts = msg.split(Seperator); + + switch (parts[0]) { + case : + + case : + + case : + + case : + + default : + System.out.println("Response " + msg + " is not recognized"); + } + + + } +} diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/network/ClientService.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/network/ClientService.java index e8f9f28..5b9d2b4 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/network/ClientService.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/network/ClientService.java @@ -6,6 +6,8 @@ import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.net.Socket; import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; @@ -30,6 +32,8 @@ public class ClientService { private ExecutorService executor; + public static ArrayList response; + /** * Creates a new ClientSession with a Socket, a Reader and Writer of the Input- and the Outputstream and a pool of threads to send requests and receive responses. * @param ip : ip-adress of the server @@ -57,8 +61,57 @@ public class ClientService { } executor = Executors.newSingleThreadExecutor(); + } + /** + * Sends a Request to get all the messages, the other clients sent, and that the client is not currently aware of. + */ + public List getMessage() { + return processMessage("GET_MESSAGE"); + } + + private List processMessage(String message) { + List response = new ArrayList<>(); + sendRequest(() -> { + try { + output.write(message+"\n"); + output.flush(); + String line; + while ((line = input.readLine()) != null) { + if (line.toLowerCase().startsWith(".")) { + break; + } + response.add(line); + } + } catch (Exception e) { + throw getRuntimeException(e); + } + }); + return response; + } + + private void sendRequest(Runnable request) { + Future future = executor.submit(request); + try { + future.get(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } catch (ExecutionException e) { + throw getRuntimeException(e); + } + } + + private static RuntimeException getRuntimeException(Exception e) { + Throwable reason = e.getCause(); + RuntimeException re; + if (reason == null) { + reason = e; + } else if (reason instanceof RuntimeException rte) { + re = rte; + } + re = new RuntimeException(reason); + return re; } /** @@ -67,27 +120,51 @@ public class ClientService { * @return ArrayList : The Response of the Server to be interpreted later */ - public Future> sendMessage(Message message) { - + public ArrayList sendMessage(Message message) { String time = message.hourTime + ":" + message.minuteTime; - String request = ""; - if (message.target == null && message.game_id == 0) { - request = String.format("SEND_MESSAGE TYPE=GLOBAL GAME=null USER=%s TARGET=null TEXT=%s TIME=%s", message.name, message.getMessage(), time); + request = String.format("SEND_MESSAGE TYPE=GLOBAL GAME=null USER=%s TARGET=null TIME=%s TEXT=%s", message.name, time, message.getMessage()); } else if (message.target == null) { - request = String.format("SEND_MESSAGE TYPE=LOBBY GAME=%d USER=%s TARGET=null TEXT=%s TIME=%s", message.game_id, message.name, message.getMessage(), time); + request = String.format("SEND_MESSAGE TYPE=LOBBY GAME=%d USER=%s TARGET=null TIME=%s TEXT=%s", message.game_id, message.name, time, message.getMessage()); } else { - request = String.format("SEND_MESSAGE TYPE=WHISPER GAME=%d USER=%s TARGET=%s TEXT=%s TIME=%s", message.game_id, message.name, message.target, message.getMessage(), time); + request = String.format("SEND_MESSAGE TYPE=WHISPER GAME=%d USER=%s TARGET=%s TIME=%s TEXT=%s", message.game_id, message.name, message.target, time, message.getMessage()); } - System.out.println("Writing following request: " + request); - sendRequest task = new sendRequest(request, input, output); + executor.submit(task); + return this.response; + } - Future> response = executor.submit(task); + public ArrayList getState(int game_id) { + String request = String.format("GET_STATE GAME=%d", game_id); + sendRequest task = new sendRequest(request, input, output); + executor.submit(task); + return this.response; + } + + public ArrayList joinGame(int game_id, String name) { + String request = String.format("JOIN GAME=%d NAME=%s", game_id, name); + sendRequest task = new sendRequest(request, input, output); + executor.submit(task); + return response; + } + + /** + * + * @param type : Keyword, as one-word description of the action the player did. + * @param game_id : ID of the Lobby that the client is currently in + * @param value : Dependent on the action of the User (money) + * @param name : Username of the player of that Client + * @return The Response of the Server -> Action successfull only if response is valid + */ + + public ArrayListsendAction(String type, int game_id, String action, int value, String name) { + String request = String.format("SEND_ACTION TYPE=%s GAME=%s ACTION=%s VALUE=%d NAME=%s", type, game_id, action, value, name); + sendRequest task = new sendRequest(request, input, output); + executor.submit(task); return response; } @@ -96,7 +173,6 @@ public class ClientService { */ public void closeSocket() { - try { executor.shutdown(); input.close(); diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/network/ResponseException.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/network/ResponseException.java new file mode 100644 index 0000000..5ab8a3a --- /dev/null +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/network/ResponseException.java @@ -0,0 +1,9 @@ +package ch.unibas.dmi.dbis.cs108.casono.client.network; + +public class ResponseException extends Exception { + public ResponseException() { + super(); + } + +} + diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/network/sendRequest.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/network/sendRequest.java index cb0a4fb..1c94f95 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/network/sendRequest.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/network/sendRequest.java @@ -3,11 +3,13 @@ package ch.unibas.dmi.dbis.cs108.casono.client.network; import java.io.BufferedReader; import java.util.concurrent.Callable; +import ch.unibas.dmi.dbis.cs108.casono.client.network.ClientService; + import java.util.ArrayList; import java.io.*; -public class sendRequest implements Callable> { +public class sendRequest implements Runnable { final private String request; final private BufferedReader input; @@ -20,30 +22,26 @@ public class sendRequest implements Callable> { } @Override - public ArrayList call() throws Exception { + public void run() { try { - output.write(request+"\n"); + output.write(request + "\r\n"); output.flush(); - System.out.println("Writing following request: " + request); + //System.out.println("Writing following request: " + request); } catch (Exception e) { System.out.println(e); } - ArrayList response = new ArrayList<>(); - try { - String line; - while ((line = input.readLine()) != null) { - if (line.toLowerCase().startsWith("ok")) { + while (true) { + String line; + line = input.readLine(); + if (line.toLowerCase().startsWith("+ok")) { break; } - response.add(line); + ClientService.response.add(line); } - - System.out.println("Response from server: " + response.get(0)); + } catch (Exception e) { + System.out.println(e); } - catch (Exception e) {System.out.println(e);} - - return response; } -} +} \ No newline at end of file diff --git a/src/test/java/ch/unibas/dmi/dbis/cs108/casono/client/network/ClientServiceTest.java b/src/test/java/ch/unibas/dmi/dbis/cs108/casono/client/network/ClientServiceTest.java index 56cc2d5..967636a 100644 --- a/src/test/java/ch/unibas/dmi/dbis/cs108/casono/client/network/ClientServiceTest.java +++ b/src/test/java/ch/unibas/dmi/dbis/cs108/casono/client/network/ClientServiceTest.java @@ -25,6 +25,7 @@ public class ClientServiceTest { public void tearDown() { serverThread.interrupt(); } + @Test public void testClientService() throws ExecutionException, InterruptedException { ClientService client = new ClientService("localhost", 5000); From 3809b7e0e2bead01bf11a0a74c71d519710acd8f Mon Sep 17 00:00:00 2001 From: ginkelmath Date: Sat, 14 Mar 2026 14:33:17 +0100 Subject: [PATCH 3/5] Fix: update clientservice --- .../cs108/casono/client/chat/Message.java | 148 ++++++++++++++---- .../casono/client/network/ClientParser.java | 26 --- .../casono/client/network/ClientService.java | 57 +------ .../client/network/ResponseException.java | 9 -- .../client/network/ClientServiceTest.java | 4 +- 5 files changed, 118 insertions(+), 126 deletions(-) delete mode 100644 src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/network/ClientParser.java delete mode 100644 src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/network/ResponseException.java diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/chat/Message.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/chat/Message.java index 44e2c45..d5b5a1c 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/chat/Message.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/client/chat/Message.java @@ -1,57 +1,137 @@ package ch.unibas.dmi.dbis.cs108.casono.client.chat; import java.time.LocalTime; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Message Object for internal handling of Chat-Messages + * TODO: Should be used on both sides of the network + */ public class Message { + private final MessageType type; private String message; - public String name; - public int hourTime; - public int minuteTime; + public String user; + public String timestamp; public int game_id = 0; public String target = null; + public enum MessageType { + GLOBAL, LOBBY, WHISPER + }; - // For Global Chat + public static final String SEPERATOR = " "; - public Message(String message, String name) { - this.message = message; - this.name = name; - LocalTime now = LocalTime.now(); - this.hourTime = now.getHour(); - this.minuteTime = now.getMinute(); - } + /** + * Constructor for creating Messages with all information given + * @param type - Either global, local or whisper + * @param game_id - lobby id, or null, if the type is global + * @param user - username + * @param target - username of the target user, for whisper chat + * @param message + */ - // For Lobby Chat - - public Message(String message, String name, int game_id) { - this.message = message; - this.name = name; - LocalTime now = LocalTime.now(); - this.hourTime = now.getHour(); - this.minuteTime = now.getMinute(); - this.game_id = game_id; - } - - // For Whisper Chat - - public Message(String message, String name, int game_id, String target) { - this.message = message; - this.name = name; - LocalTime now = LocalTime.now(); - this.hourTime = now.getHour(); - this.minuteTime = now.getMinute(); + public Message(MessageType type, int game_id, String user, String target, String timestamp, String message) { + this.type = type; this.game_id = game_id; + this.user = user; this.target = target; + this.timestamp = timestamp; + this.message = message; } + /** + * Constructor for creating the Messages of the current user, using this client -> time of writing is being recorded + * @param type - Either global, local or whisper + * @param game_id - lobby id, or null, if the type is global + * @param user - username + * @param target - username of the target user, for whisper chat + * @param message + */ + + public Message(MessageType type, int game_id, String user, String target, String message) { + this.type = type; + this.game_id = game_id; + this.user = user; + this.target = target; + this.message = message; + LocalTime now = LocalTime.now(); + this.timestamp = now.toString(); + } + + public String getMessage() { return message; } - public String toString() { - return String.format("%s: %s", this.name, this.message); + public String getMessageType() { + return type.toString(); } - public void print() { - System.out.println(this.toString()); + /** + * Method to test the system + * @return - String representation of the Message instance, as for example "player1: Hello World" + */ + public String toString() { + return String.format("%s: %s", this.user, this.message); + } + + /** + * Method to create the request representation of the message object, to be sent to the server + * @return - request as specified in the network protocol, as String + */ + public String toRequest() { + String request = String.format("SEND_MESSAGE TYPE=%s GAME=%d USER=%s TARGET=%s TIME=%s TEXT=%s", + this.type.toString(), this.game_id, this.user, this.target, this.timestamp, this.message); + return request; + } + + + /** + * Pattern, to analyze the response String with the given parameters + */ + public static Pattern msgRex = Pattern.compile("TYPE=(?\\w+) GAME=(?\\w+) USER=(?\\w+) TARGET=(?\\w+) TIME=(?