From 8e3758032a17e3fd0375f06b57d26e7caf8ab0a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20B=C3=A9guin?= Date: Fri, 20 Mar 2020 13:32:19 +0100 Subject: [PATCH 1/4] add specs --- specs/jul0105/PROTOCOL.md | 119 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 119 insertions(+) create mode 100644 specs/jul0105/PROTOCOL.md diff --git a/specs/jul0105/PROTOCOL.md b/specs/jul0105/PROTOCOL.md new file mode 100644 index 0000000..e464c9a --- /dev/null +++ b/specs/jul0105/PROTOCOL.md @@ -0,0 +1,119 @@ +# Specification + +## 1. What transport protocol do we use? + +We use TCP. + +## 2. How does the client find the server (addresses and ports)? + +The port used is 25566. + +The IP address is dynamic. + +## 3. Who speaks first? + +The client initialize the communication + +## 4. What is the sequence of messages exchanged by the client and the server? (flow) + +client open connection + +**client -> server** : handshake + +**server -> client** : handshake response + +**client -> server** : send operation + +**server -> client** : send result or error + +client close connection + +## 5. What happens when a message is received from the other party? (semantics) + +**Server :** + +1. **Receive handshake** : + 1. Send handshake response with supported operations +2. **Receive operation** : + 1. Check if the operation is supported + 2. Execute calculation + 3. Send result or error if any + +**Client :** + +1. **Receive handshake response** : + 1. Check if desired operation is supported by the server + 2. If yes, send operation + 3. If no, close connection +2. **Receive operation result or error** : + 1. Print result or error if any + +## 6. What is the syntax of the messages? How we generate and parse them? (syntax) + +**handshake :** + +``` +CALC CLIENT +``` + + + +**handshake response :** + +``` +CALC SERVER; +``` + +Example : + +``` +CALC SERVER;ADD SUB MUL DIV +``` + + + +**send operation :** + +``` + +``` + +Example : + +``` +ADD 6 4 +SUB 5 1 +MUL 2 4 +DIV 10 0 +``` + + + +**send result or error :** + +If the operation is correct : + +``` +RES +``` + +If the operation is incorrect or there is an error : + +``` +ERR +``` + +Example : + +``` +RES 10 +RES 4 +RES 8 +ERR Cannot divide by 0 +``` + + + +## 6. Who closes the connection and when? + +The client close the connection when he receive the response of the operation from the server. \ No newline at end of file From 27f56cf631e08401f94a8cf8f1dad7d17a5069a9 Mon Sep 17 00:00:00 2001 From: thomas rieder Date: Fri, 20 Mar 2020 16:27:34 +0100 Subject: [PATCH 2/4] WIP server --- .idea/.gitignore | 2 + .idea/misc.xml | 6 + .idea/modules.xml | 8 + .idea/uiDesigner.xml | 124 +++++++++++++ .idea/vcs.xml | 6 + ...IGVD-RES-2020-Exercise-Protocol-Design.iml | 11 ++ src/com/company/Main.java | 8 + src/com/company/Protocol.java | 9 + src/com/company/Server.java | 168 ++++++++++++++++++ 9 files changed, 342 insertions(+) create mode 100644 .idea/.gitignore create mode 100644 .idea/misc.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/uiDesigner.xml create mode 100644 .idea/vcs.xml create mode 100644 Teaching-HEIGVD-RES-2020-Exercise-Protocol-Design.iml create mode 100644 src/com/company/Main.java create mode 100644 src/com/company/Protocol.java create mode 100644 src/com/company/Server.java diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..e7e9d11 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,2 @@ +# Default ignored files +/workspace.xml diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..1763e15 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..1eb6dac --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/uiDesigner.xml b/.idea/uiDesigner.xml new file mode 100644 index 0000000..e96534f --- /dev/null +++ b/.idea/uiDesigner.xml @@ -0,0 +1,124 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/Teaching-HEIGVD-RES-2020-Exercise-Protocol-Design.iml b/Teaching-HEIGVD-RES-2020-Exercise-Protocol-Design.iml new file mode 100644 index 0000000..c90834f --- /dev/null +++ b/Teaching-HEIGVD-RES-2020-Exercise-Protocol-Design.iml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/src/com/company/Main.java b/src/com/company/Main.java new file mode 100644 index 0000000..d9d1a8b --- /dev/null +++ b/src/com/company/Main.java @@ -0,0 +1,8 @@ +package com.company; + +public class Main { + + public static void main(String[] args) { + // write your code here + } +} diff --git a/src/com/company/Protocol.java b/src/com/company/Protocol.java new file mode 100644 index 0000000..5daf046 --- /dev/null +++ b/src/com/company/Protocol.java @@ -0,0 +1,9 @@ +package com.company; + +public class Protocol { + public static final int DEFAULT_PORT = 9907; + public static final char OP_ADD = '+'; + public static final char OP_SUB = '-'; + public static final char OP_MUL = '*'; + public static final char OP_DIV = '/'; +} diff --git a/src/com/company/Server.java b/src/com/company/Server.java new file mode 100644 index 0000000..c0f3531 --- /dev/null +++ b/src/com/company/Server.java @@ -0,0 +1,168 @@ +package com.company; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.PrintWriter; +import java.net.ServerSocket; +import java.net.Socket; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; + +public class Server implements Runnable { + + final static Logger LOG = Logger.getLogger(Server.class.getName()); + + boolean shouldRun; + ServerSocket serverSocket; + final List connectedWorkers; + + public Server() { + this.shouldRun = true; + this.connectedWorkers = Collections.synchronizedList(new LinkedList()); + } + + private void registerWorker(Worker worker) { + + connectedWorkers.add(worker); + } + + private void unregisterWorker(Worker worker) { + + connectedWorkers.remove(worker); + } + + private void notifyConnectedWorkers(String message) { + + synchronized (connectedWorkers) { + LOG.info("Notifying workers"); + for(Worker worker : connectedWorkers){ + worker.sendNotification(message); + } + } + LOG.info("Workers notified"); + + } + + private void disconnectConnectedWorkers() { + + synchronized (connectedWorkers) { + LOG.info("Disconnecting workers"); + for(Worker worker : connectedWorkers) { + worker.disconnect(); + } + } + LOG.info("Workers disconnected"); + } + + @Override + public void run() { + + try { + serverSocket = new ServerSocket(Protocol.DEFAULT_PORT); + + while (this.shouldRun) { + + Socket clientSocket = serverSocket.accept(); + //NOTIFY + Worker newWorker = new Worker(clientSocket); + registerWorker(newWorker); + new Thread(newWorker).start(); + } + + serverSocket.close(); + + } catch (IOException ex) { + LOG.log(Level.SEVERE, ex.getMessage(), ex); + System.exit(-1); + } + } + + private void shutdown() { + this.shouldRun = false; + + try { + + serverSocket.close(); + + } catch (IOException ex) { + + LOG.log(Level.SEVERE, ex.getMessage(), ex); + } + } + + class Worker implements Runnable { + + Socket clientSocket; + BufferedReader in; + PrintWriter out; + boolean connected; + + + public Worker(Socket clientSocket) { + + this.clientSocket = clientSocket; + + try { + + in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream())); + out = new PrintWriter(clientSocket.getOutputStream()); + connected = true; + + } catch (IOException ex) { + LOG.log(Level.SEVERE, ex.getMessage(), ex); + } + + } + + @Override + public void run() { + + String calcul; + double result = 0; + Server.this.notifyConnectedWorkers("Welcome !"); + Server.this.notifyConnectedWorkers("Give me your calc !"); + + try { + + while (connected && ((calcul = in.readLine()) != null)) { + + String strOp[] = calcul.split(" "); + + double op1 = Double.parseDouble(strOp[0]); + double op2 = Double.parseDouble(strOp[2]); + + switch (strOp[1].toCharArray()[0]) { + + case (Protocol.OP_ADD): + result = op1 + op2; + break; + case (Protocol.OP_SUB): + result = op1 - op2; + break; + case (Protocol.OP_MUL): + result = op1 * op2; + break; + case (Protocol.OP_DIV): + result = op1 / op2; + break; + default: + sendNotification("I don't new this operation ! I only understand +, -, *, /"); + } + + sendNotification("Your resultats is " + result); + } + + } catch (IOException ex) { + LOG.log(Level.SEVERE, ex.getMessage(), ex); + } + } + + private void cleanup() { + + } + } +} From d83dfa94a3ee22ee201af529993a88bc68586397 Mon Sep 17 00:00:00 2001 From: thomas rieder Date: Fri, 20 Mar 2020 17:01:03 +0100 Subject: [PATCH 3/4] server done --- specs/jul0105/PROTOCOL.md | 10 ++-- src/com/company/Protocol.java | 2 +- src/com/company/Server.java | 91 ++++++++++++++++++++++------------- 3 files changed, 64 insertions(+), 39 deletions(-) diff --git a/specs/jul0105/PROTOCOL.md b/specs/jul0105/PROTOCOL.md index e464c9a..5f6c087 100644 --- a/specs/jul0105/PROTOCOL.md +++ b/specs/jul0105/PROTOCOL.md @@ -75,16 +75,16 @@ CALC SERVER;ADD SUB MUL DIV **send operation :** ``` - + >OPERAND 2> ``` Example : ``` -ADD 6 4 -SUB 5 1 -MUL 2 4 -DIV 10 0 +6 + 4 +5 - 1 +2 * 4 +10 / 0 ``` diff --git a/src/com/company/Protocol.java b/src/com/company/Protocol.java index 5daf046..c05593e 100644 --- a/src/com/company/Protocol.java +++ b/src/com/company/Protocol.java @@ -1,7 +1,7 @@ package com.company; public class Protocol { - public static final int DEFAULT_PORT = 9907; + public static final int DEFAULT_PORT = 25566; public static final char OP_ADD = '+'; public static final char OP_SUB = '-'; public static final char OP_MUL = '*'; diff --git a/src/com/company/Server.java b/src/com/company/Server.java index c0f3531..030eae5 100644 --- a/src/com/company/Server.java +++ b/src/com/company/Server.java @@ -47,17 +47,6 @@ private void notifyConnectedWorkers(String message) { } - private void disconnectConnectedWorkers() { - - synchronized (connectedWorkers) { - LOG.info("Disconnecting workers"); - for(Worker worker : connectedWorkers) { - worker.disconnect(); - } - } - LOG.info("Workers disconnected"); - } - @Override public void run() { @@ -67,9 +56,12 @@ public void run() { while (this.shouldRun) { Socket clientSocket = serverSocket.accept(); - //NOTIFY + + Server.this.notifyConnectedWorkers("Someone has arrived..."); + Worker newWorker = new Worker(clientSocket); registerWorker(newWorker); + new Thread(newWorker).start(); } @@ -81,19 +73,6 @@ public void run() { } } - private void shutdown() { - this.shouldRun = false; - - try { - - serverSocket.close(); - - } catch (IOException ex) { - - LOG.log(Level.SEVERE, ex.getMessage(), ex); - } - } - class Worker implements Runnable { Socket clientSocket; @@ -124,13 +103,14 @@ public void run() { String calcul; double result = 0; Server.this.notifyConnectedWorkers("Welcome !"); - Server.this.notifyConnectedWorkers("Give me your calc !"); + sendNotification("Send an operation: [op1] [+,-,*,/] [op2]"); + try { while (connected && ((calcul = in.readLine()) != null)) { - String strOp[] = calcul.split(" "); + String[] strOp = calcul.split(" "); double op1 = Double.parseDouble(strOp[0]); double op2 = Double.parseDouble(strOp[2]); @@ -138,31 +118,76 @@ public void run() { switch (strOp[1].toCharArray()[0]) { case (Protocol.OP_ADD): - result = op1 + op2; + sendNotification("RES " + (op1 + op2)); break; case (Protocol.OP_SUB): - result = op1 - op2; + sendNotification("RES " + (op1 - op2)); break; case (Protocol.OP_MUL): - result = op1 * op2; + sendNotification("RES " + (op1 * op2)); break; case (Protocol.OP_DIV): - result = op1 / op2; + + if(op2 != 0){ + + sendNotification("RES " + (op1 / op2)); + } else { + + sendNotification("ERR cannot divide by 0"); + } + break; default: - sendNotification("I don't new this operation ! I only understand +, -, *, /"); + sendNotification("ERR unknown : " + strOp[1] + ", use only : [+, -, *, /]"); } - sendNotification("Your resultats is " + result); + } } catch (IOException ex) { LOG.log(Level.SEVERE, ex.getMessage(), ex); + } finally { + unregisterWorker(this); + Server.this.notifyConnectedWorkers("Someone left..."); + cleanup(); } } private void cleanup() { + LOG.log(Level.INFO, "Cleaning up worker"); + + LOG.log(Level.INFO, "Closing clientSocked"); + try { + clientSocket.close(); + } catch (IOException ex) { + LOG.log(Level.SEVERE, ex.getMessage(), ex); + } + + LOG.log(Level.INFO, "Closing in"); + try { + in.close(); + } catch (IOException ex) { + LOG.log(Level.SEVERE, ex.getMessage(), ex); + } + + LOG.log(Level.INFO, "Closing out"); + if(out != null){ + out.close(); + } + + LOG.log(Level.INFO, "Clean up done"); + } + + public void sendNotification(String message) { + out.println(message); + out.flush(); + } + + private void disconnect() { + LOG.log(Level.INFO , "Disconnecting worker"); + connected = false; + cleanup(); } } } From ee3e5d0e5727ef3ed5008afc8f0a996752b2f676 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julien=20B=C3=A9guin?= Date: Fri, 20 Mar 2020 18:02:07 +0100 Subject: [PATCH 4/4] Implement client --- .gitignore | 1 + specs/jul0105/PROTOCOL.md | 2 +- src/client/jul0105/Client.java | 141 +++++++++++++++++++++++++++++++++ 3 files changed, 143 insertions(+), 1 deletion(-) create mode 100644 src/client/jul0105/Client.java diff --git a/.gitignore b/.gitignore index 0e13eeb..00e1a7d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +.idea/ target/ pom.xml.tag pom.xml.releaseBackup diff --git a/specs/jul0105/PROTOCOL.md b/specs/jul0105/PROTOCOL.md index 5f6c087..1a170ac 100644 --- a/specs/jul0105/PROTOCOL.md +++ b/specs/jul0105/PROTOCOL.md @@ -75,7 +75,7 @@ CALC SERVER;ADD SUB MUL DIV **send operation :** ``` - >OPERAND 2> + ``` Example : diff --git a/src/client/jul0105/Client.java b/src/client/jul0105/Client.java new file mode 100644 index 0000000..66d4f78 --- /dev/null +++ b/src/client/jul0105/Client.java @@ -0,0 +1,141 @@ +package client.jul0105; + +import java.io.*; +import java.net.Socket; +import java.util.logging.Level; +import java.util.logging.Logger; + + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.PrintWriter; + +public class Client { + private static final String SERVER_IP = ""; + private static final int SERVER_PORT = 25566; + private static final String HANDSHAKE = "CALC CLIENT"; + private static final String HANDSHAKE_RESPONSE = "CALC SERVER"; + + private static final Logger LOG = Logger.getLogger(Client.class.getName()); + + private Socket clientSocket; + private PrintWriter out; + private BufferedReader in; + private boolean isConnected = false; + private String supportedOperations; + + public double calculate(double op1, char operator, double op2) { + String response = null; + + // Test if there is an active connection with the server + if (!isConnected) { + throw new RuntimeException("Cannot execute calculation. No active connection."); + } + + // Test if the server support the desired operation + if (supportedOperations.indexOf(operator) < 0) { + throw new RuntimeException("Operation unsupported by the server."); + } + + // Send operation + out.println(String.format("%s %s %s", op2, operator, op2)); + + // Read response + try { + response = in.readLine(); + } catch (IOException ex) { + LOG.log(Level.SEVERE, ex.getMessage(), ex); + } + + // Return response + if (response != null && response.contains("RES")) { + return Double.parseDouble(response.split("\\s")[1]); + } else { + throw new RuntimeException(response); + } + } + + public void connect() { + String response; + LOG.log(Level.INFO, "Initialize new connection..."); + if (isConnected) { + LOG.log(Level.WARNING, "Already connected."); + } else { + try { + // Initialize connection + clientSocket = new Socket(SERVER_IP, SERVER_PORT); + out = new PrintWriter(clientSocket.getOutputStream()); + in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream())); + + // Handshake + out.println(HANDSHAKE); + response = in.readLine(); + if (response.contains(HANDSHAKE_RESPONSE)) { + isConnected = true; + + // Set supported operations + supportedOperations = response.split(";")[1]; + + LOG.log(Level.INFO, "Connected with success."); + } else { + LOG.log(Level.WARNING, "Handshake failed. Abort connection."); + cleanup(); + } + + } catch (IOException ex) { + LOG.log(Level.SEVERE, ex.getMessage(), ex); + cleanup(); + } + } + + } + public void disconnect() { + LOG.log(Level.INFO, "Disconnection request."); + if (isConnected) { + cleanup(); + isConnected = false; + LOG.log(Level.INFO, "Disconnected with success."); + } else { + LOG.log(Level.INFO, "Unable to disconnect. No active connection."); + } + } + public void cleanup() { + try { + if (in != null) { + in.close(); + } + } catch (IOException ex) { + LOG.log(Level.SEVERE, ex.getMessage(), ex); + } + + if (out != null) { + out.close(); + } + + try { + if (clientSocket != null) { + clientSocket.close(); + } + } catch (IOException ex) { + LOG.log(Level.SEVERE, ex.getMessage(), ex); + } + } + + public static void main(String[] args) { + Client client = new Client(); + client.connect(); + + try { + System.out.print("4 * 4 = "); + System.out.println(client.calculate(4, '*', 4)); + } catch (Exception ex) { + LOG.log(Level.SEVERE, ex.getMessage(), ex); + System.out.println(ex.getMessage()); + } + + client.disconnect(); + + } + +} \ No newline at end of file