diff --git a/examples/04-StreamingTimeServer/StreamingTimeServer/pom.xml b/examples/04-StreamingTimeServer/StreamingTimeServer/pom.xml new file mode 100644 index 0000000..660960d --- /dev/null +++ b/examples/04-StreamingTimeServer/StreamingTimeServer/pom.xml @@ -0,0 +1,40 @@ + + + 4.0.0 + ch.heigvd.res.examples + StreamingTimeServer + 1.0-SNAPSHOT + jar + + + + org.apache.maven.plugins + maven-shade-plugin + 2.3 + + + package + + shade + + + true + standalone + + + ch.heigvd.res.examples.StreamingTimeServer + + + + + + + + + + UTF-8 + 1.7 + 1.7 + + StreamingTimeServer + \ No newline at end of file diff --git a/examples/04-StreamingTimeServer/StreamingTimeServer/src/main/java/ch/heigvd/res/examples/StreamingTimeServer.java b/examples/04-StreamingTimeServer/StreamingTimeServer/src/main/java/ch/heigvd/res/examples/StreamingTimeServer.java new file mode 100644 index 0000000..46e5c39 --- /dev/null +++ b/examples/04-StreamingTimeServer/StreamingTimeServer/src/main/java/ch/heigvd/res/examples/StreamingTimeServer.java @@ -0,0 +1,134 @@ +package ch.heigvd.res.examples; + +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.Date; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * A very simple example of TCP server. When the server starts, it binds a + * server socket on any of the available network interfaces and on port 2205. It + * then waits until one (only one!) client makes a connection request. When the + * client arrives, the server does not even check if the client sends data. It + * simply writes the current time, every second, during 15 seconds. + * + * To test the server, simply open a terminal, do a "telnet localhost 2205" and + * see what you get back. Use Wireshark to have a look at the transmitted TCP + * segments. + * + * @author Olivier Liechti + */ +public class StreamingTimeServer { + + static final Logger LOG = Logger.getLogger(StreamingTimeServer.class.getName()); + + private final int TEST_DURATION = 15000; + private final int PAUSE_DURATION = 1000; + private final int NUMBER_OF_ITERATIONS = TEST_DURATION / PAUSE_DURATION; + private final int LISTEN_PORT = 2205; + + /** + * This method does the entire processing. + */ + public void start() { + LOG.info("Starting server..."); + + ServerSocket serverSocket = null; + Socket clientSocket = null; + BufferedReader reader = null; + PrintWriter writer = null; + + try { + LOG.log(Level.INFO, "Creating a server socket and binding it on any of the available network interfaces and on port {0}", new Object[]{Integer.toString(LISTEN_PORT)}); + serverSocket = new ServerSocket(LISTEN_PORT); + logServerSocketAddress(serverSocket); + + while (true) { + LOG.log(Level.INFO, "Waiting (blocking) for a connection request on {0} : {1}", new Object[]{serverSocket.getInetAddress(), Integer.toString(serverSocket.getLocalPort())}); + clientSocket = serverSocket.accept(); + + LOG.log(Level.INFO, "A client has arrived. We now have a client socket with following attributes:"); + logSocketAddress(clientSocket); + + LOG.log(Level.INFO, "Getting a Reader and a Writer connected to the client socket..."); + reader = new BufferedReader(new InputStreamReader(clientSocket.getInputStream())); + writer = new PrintWriter(clientSocket.getOutputStream()); + + LOG.log(Level.INFO, "Starting my job... sending current time to the client for {0} ms", TEST_DURATION); + for (int i = 0; i < NUMBER_OF_ITERATIONS; i++) { + writer.println(String.format("{'time' : '%s'}", new Date())); + writer.flush(); + LOG.log(Level.INFO, "Sent data to client, doing a pause..."); + Thread.sleep(PAUSE_DURATION); + } + + reader.close(); + writer.close(); + clientSocket.close(); + + + } + + } catch (IOException | InterruptedException ex) { + LOG.log(Level.SEVERE, ex.getMessage()); + } finally { + LOG.log(Level.INFO, "We are done. Cleaning up resources, closing streams and sockets..."); + try { + reader.close(); + } catch (IOException ex) { + Logger.getLogger(StreamingTimeServer.class.getName()).log(Level.SEVERE, null, ex); + } + writer.close(); + try { + clientSocket.close(); + } catch (IOException ex) { + Logger.getLogger(StreamingTimeServer.class.getName()).log(Level.SEVERE, null, ex); + } + try { + serverSocket.close(); + } catch (IOException ex) { + Logger.getLogger(StreamingTimeServer.class.getName()).log(Level.SEVERE, null, ex); + } + } + + } + + /** + * A utility method to print server socket information + * + * @param serverSocket the socket that we want to log + */ + private void logServerSocketAddress(ServerSocket serverSocket) { + LOG.log(Level.INFO, " Local IP address: {0}", new Object[]{serverSocket.getLocalSocketAddress()}); + LOG.log(Level.INFO, " Local port: {0}", new Object[]{Integer.toString(serverSocket.getLocalPort())}); + LOG.log(Level.INFO, " is bound: {0}", new Object[]{serverSocket.isBound()}); + } + + /** + * A utility method to print socket information + * + * @param clientSocket the socket that we want to log + */ + private void logSocketAddress(Socket clientSocket) { + LOG.log(Level.INFO, " Local IP address: {0}", new Object[]{clientSocket.getLocalAddress()}); + LOG.log(Level.INFO, " Local port: {0}", new Object[]{Integer.toString(clientSocket.getLocalPort())}); + LOG.log(Level.INFO, " Remote Socket address: {0}", new Object[]{clientSocket.getRemoteSocketAddress()}); + LOG.log(Level.INFO, " Remote port: {0}", new Object[]{Integer.toString(clientSocket.getPort())}); + } + + /** + * @param args the command line arguments + */ + public static void main(String[] args) { + System.setProperty("java.util.logging.SimpleFormatter.format", "%5$s %n"); + + StreamingTimeServer server = new StreamingTimeServer(); + server.start(); + } + +} diff --git a/examples/05-DumbHttpClient/DumbHttpClient/pom.xml b/examples/05-DumbHttpClient/DumbHttpClient/pom.xml new file mode 100644 index 0000000..c35b865 --- /dev/null +++ b/examples/05-DumbHttpClient/DumbHttpClient/pom.xml @@ -0,0 +1,39 @@ + + + 4.0.0 + ch.heigvd.res.examples + DumbHttpClient + 1.0-SNAPSHOT + jar + + + + org.apache.maven.plugins + maven-shade-plugin + 2.3 + + + package + + shade + + + true + standalone + + + ch.heigvd.res.examples.DumbHttpClient + + + + + + + + + + UTF-8 + 1.7 + 1.7 + + \ No newline at end of file diff --git a/examples/05-DumbHttpClient/DumbHttpClient/src/main/java/ch/heigvd/res/examples/DumbHttpClient.java b/examples/05-DumbHttpClient/DumbHttpClient/src/main/java/ch/heigvd/res/examples/DumbHttpClient.java new file mode 100644 index 0000000..0df8749 --- /dev/null +++ b/examples/05-DumbHttpClient/DumbHttpClient/src/main/java/ch/heigvd/res/examples/DumbHttpClient.java @@ -0,0 +1,80 @@ +package ch.heigvd.res.examples; + +import java.io.*; +import java.net.Socket; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * This is not really an HTTP client, but rather a very simple program that + * establishes a TCP connection with a real HTTP server. Once connected, the + * client sends "garbage" to the server (the client does not send a proper + * HTTP request that the server would understand). The client then reads the + * response sent back by the server and logs it onto the console. + * + * @author Olivier Liechti + */ +public class DumbHttpClient { + + static final Logger LOG = Logger.getLogger(DumbHttpClient.class.getName()); + + final static int BUFFER_SIZE = 1024; + + /** + * This method does the whole processing + */ + public void sendWrongHttpRequest() { + Socket clientSocket = null; + OutputStream os = null; + InputStream is = null; + + try { + clientSocket = new Socket("www.lematin.ch", 80); + os = clientSocket.getOutputStream(); + is = clientSocket.getInputStream(); + + String malformedHttpRequest = "Hello, sorry, but I don't speak HTTP...\r\n\r\n"; + os.write(malformedHttpRequest.getBytes()); + + ByteArrayOutputStream responseBuffer = new ByteArrayOutputStream(); + byte[] buffer = new byte[BUFFER_SIZE]; + int newBytes; + while ((newBytes = is.read(buffer)) != -1) { + responseBuffer.write(buffer, 0, newBytes); + } + + LOG.log(Level.INFO, "Response sent by the server: "); + LOG.log(Level.INFO, responseBuffer.toString()); + } catch (IOException ex) { + LOG.log(Level.SEVERE, null, ex); + } finally { + try { + is.close(); + } catch (IOException ex) { + Logger.getLogger(DumbHttpClient.class.getName()).log(Level.SEVERE, null, ex); + } + try { + os.close(); + } catch (IOException ex) { + Logger.getLogger(DumbHttpClient.class.getName()).log(Level.SEVERE, null, ex); + } + try { + clientSocket.close(); + } catch (IOException ex) { + Logger.getLogger(DumbHttpClient.class.getName()).log(Level.SEVERE, null, ex); + } + } + } + + /** + * @param args the command line arguments + */ + public static void main(String[] args) { + System.setProperty("java.util.logging.SimpleFormatter.format", "%5$s %n"); + + DumbHttpClient client = new DumbHttpClient(); + client.sendWrongHttpRequest(); + + } + +} diff --git a/examples/06-PresenceApplication/PresenceApplication/pom.xml b/examples/06-PresenceApplication/PresenceApplication/pom.xml new file mode 100644 index 0000000..1b87ea4 --- /dev/null +++ b/examples/06-PresenceApplication/PresenceApplication/pom.xml @@ -0,0 +1,40 @@ + + + 4.0.0 + ch.heigvd.res.examples + PresenceApplication + 1.0-SNAPSHOT + jar + + + + org.apache.maven.plugins + maven-shade-plugin + 2.3 + + + package + + shade + + + true + standalone + + + ch.heigvd.res.examples.PresenceApplication + + + + + + + + + + UTF-8 + 1.7 + 1.7 + + PresenceApplication + \ No newline at end of file diff --git a/examples/06-PresenceApplication/PresenceApplication/src/main/java/ch/heigvd/res/examples/PresenceApplication.java b/examples/06-PresenceApplication/PresenceApplication/src/main/java/ch/heigvd/res/examples/PresenceApplication.java new file mode 100644 index 0000000..ff36e57 --- /dev/null +++ b/examples/06-PresenceApplication/PresenceApplication/src/main/java/ch/heigvd/res/examples/PresenceApplication.java @@ -0,0 +1,38 @@ +package ch.heigvd.res.examples; + +/** + * The server reacts to the following commands, defined in the protocol: + * - HELLO name: the user "behind" the client is not anonymous anymore + * - SAY message: the message is broadcasted to connected clients + * - WHO: the server returns the list of connected users + * - BYE: the client is disconnected and the others are notified + * + * @author Olivier Liechti + */ +public class PresenceApplication { + + /** + * @param args the command line arguments + */ + public static void main(String[] args) { + System.setProperty("java.util.logging.SimpleFormatter.format", "%5$s %n"); + + Thread listenThread = new Thread(new PresenceServer()); + listenThread.start(); + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + + PresenceClient c1 = new PresenceClient(); + c1.connect("localhost", Protocol.PRESENCE_DEFAULT_PORT, "Sacha"); + new PresenceClient().connect("localhost", Protocol.PRESENCE_DEFAULT_PORT, "Fabienne"); + new PresenceClient().connect("localhost", Protocol.PRESENCE_DEFAULT_PORT, "Olivier"); + c1.disconnect(); + new PresenceClient().connect("localhost", Protocol.PRESENCE_DEFAULT_PORT, "Jean"); + new PresenceClient().connect("localhost", Protocol.PRESENCE_DEFAULT_PORT, "Nicole"); + + } + +} diff --git a/examples/06-PresenceApplication/PresenceApplication/src/main/java/ch/heigvd/res/examples/PresenceClient.java b/examples/06-PresenceApplication/PresenceApplication/src/main/java/ch/heigvd/res/examples/PresenceClient.java new file mode 100644 index 0000000..4670146 --- /dev/null +++ b/examples/06-PresenceApplication/PresenceApplication/src/main/java/ch/heigvd/res/examples/PresenceClient.java @@ -0,0 +1,112 @@ +package ch.heigvd.res.examples; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.PrintWriter; +import java.net.Socket; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * This class implements a simple client for our custom presence protocol. + * When the client connects to a server, a thread is started to listen for + * notifications sent by the server. + * + * @author Olivier Liechti + */ +public class PresenceClient { + + final static Logger LOG = Logger.getLogger(PresenceClient.class.getName()); + + Socket clientSocket; + BufferedReader in; + PrintWriter out; + boolean connected = false; + String userName; + + /** + * This inner class implements the Runnable interface, so that the run() + * method can execute on its own thread. This method reads data sent from the + * server, line by line, until the connection is closed or lost. + */ + class NotificationListener implements Runnable { + + @Override + public void run() { + String notification; + try { + while ((connected && (notification = in.readLine()) != null)) { + LOG.log(Level.INFO, "Server notification for {1}: {0}", new Object[]{notification,userName}); + } + } catch (IOException e) { + LOG.log(Level.SEVERE, "Connection problem in client used by {1}: {0}", new Object[]{e.getMessage(),userName}); + connected = false; + } finally { + cleanup(); + } + } + } + + /** + * This method is used to connect to the server and to inform the server that + * the user "behind" the client has a name (in other words, the HELLO command + * is issued after successful connection). + * + * @param serverAddress the IP address used by the Presence Server + * @param serverPort the port used by the Presence Server + * @param userName the name of the user, used as a parameter for the HELLO command + */ + public void connect(String serverAddress, int serverPort, String userName) { + try { + clientSocket = new Socket(serverAddress, serverPort); + in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream())); + out = new PrintWriter(clientSocket.getOutputStream()); + connected = true; + this.userName = userName; + } catch (IOException e) { + LOG.log(Level.SEVERE, "Unable to connect to server: {0}", e.getMessage()); + cleanup(); + return; + } + // Let us start a thread, so that we can listen for server notifications + new Thread(new NotificationListener()).start(); + + // Let us send the HELLO command to inform the server about who the user + // is. Other clients will be notified. + out.println("HELLO " + userName); + out.flush(); + } + + public void disconnect() { + LOG.log(Level.INFO, "{0} has requested to be disconnected.", userName); + connected = false; + out.println("BYE"); + cleanup(); + } + + private 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); + } + } + + +} diff --git a/examples/06-PresenceApplication/PresenceApplication/src/main/java/ch/heigvd/res/examples/PresenceServer.java b/examples/06-PresenceApplication/PresenceApplication/src/main/java/ch/heigvd/res/examples/PresenceServer.java new file mode 100644 index 0000000..517a248 --- /dev/null +++ b/examples/06-PresenceApplication/PresenceApplication/src/main/java/ch/heigvd/res/examples/PresenceServer.java @@ -0,0 +1,213 @@ +package ch.heigvd.res.examples; + +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; + +/** + * This class is a multi-threaded server of the custom presence protocol. The + * server binds a socket on the specified port and waits for incoming connection + * requests. It keeps track of connected clients in a list. When new clients + * arrive, leave or send messages, the server notifies all connected clients. + * + * @author Olivier Liechti + */ +public class PresenceServer implements Runnable { + + final static Logger LOG = Logger.getLogger(PresenceServer.class.getName()); + + boolean shouldRun; + ServerSocket serverSocket; + final List connectedWorkers; + + public PresenceServer() { + this.shouldRun = true; + this.connectedWorkers = Collections.synchronizedList(new LinkedList()); + } + + private void registerWorker(Worker worker) { + LOG.log(Level.INFO, ">> Waiting for lock before registring worker {0}", worker.userName); + connectedWorkers.add(worker); + LOG.log(Level.INFO, "<< Worker {0} registered.", worker.userName); + } + + private void unregisterWorker(Worker worker) { + LOG.log(Level.INFO, ">> Waiting for lock before unregistring worker {0}", worker.userName); + connectedWorkers.remove(worker); + LOG.log(Level.INFO, "<< Worker {0} unregistered.", worker.userName); + } + + private void notifyConnectedWorkers(String message) { + LOG.info(">> Waiting for lock before notifying workers"); + synchronized (connectedWorkers) { + LOG.info("Notifying workers"); + for (Worker worker : connectedWorkers) { + worker.sendNotification(message); + } + } + LOG.info("<< Workers notified"); + } + + private void disconnectConnectedWorkers() { + LOG.info(">> Waiting for lock before disconnecting workers"); + synchronized (connectedWorkers) { + LOG.info("Disconnecting workers"); + for (Worker worker : connectedWorkers) { + worker.disconnect(); + } + } + LOG.info("<< Workers disconnected"); + } + + @Override + public void run() { + try { + LOG.log(Level.INFO, "Starting Presence Server on port {0}", Protocol.PRESENCE_DEFAULT_PORT); + serverSocket = new ServerSocket(Protocol.PRESENCE_DEFAULT_PORT); + while (shouldRun) { + Socket clientSocket = serverSocket.accept(); + PresenceServer.this.notifyConnectedWorkers("Someone has arrived..."); + Worker newWorker = new Worker(clientSocket); + registerWorker(newWorker); + new Thread(newWorker).start(); + } + serverSocket.close(); + LOG.info("shouldRun is false... server going down"); + } catch (IOException ex) { + LOG.log(Level.SEVERE, ex.getMessage(), ex); + System.exit(-1); + } + } + + private void shutdown() { + LOG.info("Shutting down server..."); + shouldRun = false; + try { + serverSocket.close(); + } catch (IOException ex) { + LOG.log(Level.SEVERE, ex.getMessage(), ex); + } + disconnectConnectedWorkers(); + } + + class Worker implements Runnable { + + Socket clientSocket; + BufferedReader in; + PrintWriter out; + boolean connected; + String userName = "An anonymous user"; + + 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 commandLine; + PresenceServer.this.notifyConnectedWorkers("Welcome to the Presence Server"); + PresenceServer.this.notifyConnectedWorkers(" Tell me who you are with 'HELLO name'"); + PresenceServer.this.notifyConnectedWorkers(" Say something to other users with 'SAY message'"); + PresenceServer.this.notifyConnectedWorkers(" Ask me who is connected with 'WHO'"); + PresenceServer.this.notifyConnectedWorkers(" Leave with 'BYE'"); + PresenceServer.this.notifyConnectedWorkers(" Shutdown server with 'KILL'"); + try { + while (connected && ((commandLine = in.readLine()) != null)) { + String[] tokens = commandLine.split(" "); + switch (tokens[0].toUpperCase()) { + case (Protocol.CMD_HELLO): + userName = tokens.length >= 2 ? tokens[1] : "An anonymous user"; + PresenceServer.this.notifyConnectedWorkers(userName + " is in the room."); + break; + case (Protocol.CMD_SAY): + String message = tokens.length >= 2 ? commandLine.substring(4) : "nothing..."; + PresenceServer.this.notifyConnectedWorkers(userName + " says: " + message); + break; + case (Protocol.CMD_WHO): + StringBuilder sb = new StringBuilder("Currently connected users:\r\n"); + for (Worker w : connectedWorkers) { + sb.append(" - "); + sb.append(w.userName); + sb.append("\n"); + } + sendNotification(sb.toString()); + break; + case (Protocol.CMD_BYE): + PresenceServer.this.notifyConnectedWorkers(userName + " is about to leave the room."); + connected = false; + break; + case (Protocol.CMD_KILL): + sendNotification("KILL command received. Bringing server down..."); + shutdown(); + break; + default: + sendNotification("What? I only understand HELLO, SAY, WHO, BYE and KILL commands"); + } + } + } catch (IOException ex) { + LOG.log(Level.SEVERE, ex.getMessage(), ex); + } finally { + unregisterWorker(this); + PresenceServer.this.notifyConnectedWorkers(userName + " has left the room."); + cleanup(); + } + } + + private void cleanup() { + LOG.log(Level.INFO, "Cleaning up worker used by {0}", userName); + + LOG.log(Level.INFO, "Closing clientSocket used by {0}", userName); + try { + if (clientSocket != null) { + clientSocket.close(); + } + } catch (IOException ex) { + LOG.log(Level.SEVERE, ex.getMessage(), ex); + } + + LOG.log(Level.INFO, "Closing in used by {0}", userName); + try { + if (in != null) { + in.close(); + } + } catch (IOException ex) { + LOG.log(Level.SEVERE, ex.getMessage(), ex); + } + + LOG.log(Level.INFO, "Closing out used by {0}", userName); + if (out != null) { + out.close(); + } + + LOG.log(Level.INFO, "Clean up done for worker used by {0}", userName); + } + + public void sendNotification(String message) { + out.println(message); + out.flush(); + } + + private void disconnect() { + LOG.log(Level.INFO, "Disconnecting worker used by {0}", userName); + connected = false; + cleanup(); + } + + } + +} diff --git a/examples/06-PresenceApplication/PresenceApplication/src/main/java/ch/heigvd/res/examples/Protocol.java b/examples/06-PresenceApplication/PresenceApplication/src/main/java/ch/heigvd/res/examples/Protocol.java new file mode 100644 index 0000000..3a807b5 --- /dev/null +++ b/examples/06-PresenceApplication/PresenceApplication/src/main/java/ch/heigvd/res/examples/Protocol.java @@ -0,0 +1,17 @@ +package ch.heigvd.res.examples; + +/** + * + * @author Olivier Liechti + */ +public class Protocol { + + public static final int PRESENCE_DEFAULT_PORT = 9907; + + public static final String CMD_HELLO = "HELLO"; + public static final String CMD_WHO = "WHO"; + public static final String CMD_BYE = "BYE"; + public static final String CMD_SAY = "SAY"; + public static final String CMD_KILL = "KILL"; + +} diff --git a/examples/07-TcpServers/TcpServers/pom.xml b/examples/07-TcpServers/TcpServers/pom.xml new file mode 100644 index 0000000..7399bec --- /dev/null +++ b/examples/07-TcpServers/TcpServers/pom.xml @@ -0,0 +1,39 @@ + + + 4.0.0 + ch.heigvd.res.examples + TcpServers + 1.0-SNAPSHOT + jar + + + + org.apache.maven.plugins + maven-shade-plugin + 2.3 + + + package + + shade + + + true + standalone + + + ch.heigvd.res.examples.DumbHttpClient + + + + + + + + + + UTF-8 + 1.8 + 1.8 + + \ No newline at end of file diff --git a/examples/07-TcpServers/TcpServers/src/main/java/ch/heigvd/res/examples/MultiThreadedServer.java b/examples/07-TcpServers/TcpServers/src/main/java/ch/heigvd/res/examples/MultiThreadedServer.java new file mode 100644 index 0000000..8ce2bc9 --- /dev/null +++ b/examples/07-TcpServers/TcpServers/src/main/java/ch/heigvd/res/examples/MultiThreadedServer.java @@ -0,0 +1,145 @@ +package ch.heigvd.res.examples; + +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.logging.Level; +import java.util.logging.Logger; + +/** + * This class implements a multi-threaded TCP server. It is able to interact + * with several clients at the time, as well as to continue listening for + * connection requests. + * + * @author Olivier Liechti + */ +public class MultiThreadedServer { + + final static Logger LOG = Logger.getLogger(MultiThreadedServer.class.getName()); + + int port; + + /** + * Constructor + * + * @param port the port to listen on + */ + public MultiThreadedServer(int port) { + this.port = port; + } + + /** + * This method initiates the process. The server creates a socket and binds it + * to the previously specified port. It then waits for clients in a infinite + * loop. When a client arrives, the server will read its input line by line + * and send back the data converted to uppercase. This will continue until the + * client sends the "BYE" command. + */ + public void serveClients() { + LOG.info("Starting the Receptionist Worker on a new thread..."); + new Thread(new ReceptionistWorker()).start(); + } + + /** + * This inner class implements the behavior of the "receptionist", whose + * responsibility is to listen for incoming connection requests. As soon as a + * new client has arrived, the receptionist delegates the processing to a + * "servant" who will execute on its own thread. + */ + private class ReceptionistWorker implements Runnable { + + @Override + public void run() { + ServerSocket serverSocket; + + try { + serverSocket = new ServerSocket(port); + } catch (IOException ex) { + LOG.log(Level.SEVERE, null, ex); + return; + } + + while (true) { + LOG.log(Level.INFO, "Waiting (blocking) for a new client on port {0}", port); + try { + Socket clientSocket = serverSocket.accept(); + LOG.info("A new client has arrived. Starting a new thread and delegating work to a new servant..."); + new Thread(new ServantWorker(clientSocket)).start(); + } catch (IOException ex) { + Logger.getLogger(MultiThreadedServer.class.getName()).log(Level.SEVERE, null, ex); + } + } + + } + + /** + * This inner class implements the behavior of the "servants", whose + * responsibility is to take care of clients once they have connected. This + * is where we implement the application protocol logic, i.e. where we read + * data sent by the client and where we generate the responses. + */ + private class ServantWorker implements Runnable { + + Socket clientSocket; + BufferedReader in = null; + PrintWriter out = null; + + public ServantWorker(Socket clientSocket) { + try { + this.clientSocket = clientSocket; + in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream())); + out = new PrintWriter(clientSocket.getOutputStream()); + } catch (IOException ex) { + Logger.getLogger(MultiThreadedServer.class.getName()).log(Level.SEVERE, null, ex); + } + } + + @Override + public void run() { + String line; + boolean shouldRun = true; + + out.println("Welcome to the Multi-Threaded Server.\nSend me text lines and conclude with the BYE command."); + out.flush(); + try { + LOG.info("Reading until client sends BYE or closes the connection..."); + while ((shouldRun) && (line = in.readLine()) != null) { + if (line.equalsIgnoreCase("bye")) { + shouldRun = false; + } + out.println("> " + line.toUpperCase()); + out.flush(); + } + + LOG.info("Cleaning up resources..."); + clientSocket.close(); + in.close(); + out.close(); + + } catch (IOException ex) { + if (in != null) { + try { + in.close(); + } catch (IOException ex1) { + LOG.log(Level.SEVERE, ex1.getMessage(), ex1); + } + } + if (out != null) { + out.close(); + } + if (clientSocket != null) { + try { + clientSocket.close(); + } catch (IOException ex1) { + LOG.log(Level.SEVERE, ex1.getMessage(), ex1); + } + } + LOG.log(Level.SEVERE, ex.getMessage(), ex); + } + } + } + } +} diff --git a/examples/07-TcpServers/TcpServers/src/main/java/ch/heigvd/res/examples/SingleThreadedServer.java b/examples/07-TcpServers/TcpServers/src/main/java/ch/heigvd/res/examples/SingleThreadedServer.java new file mode 100644 index 0000000..ef48f90 --- /dev/null +++ b/examples/07-TcpServers/TcpServers/src/main/java/ch/heigvd/res/examples/SingleThreadedServer.java @@ -0,0 +1,101 @@ +package ch.heigvd.res.examples; + +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.logging.Level; +import java.util.logging.Logger; + +/** + * This class implements a single-threaded TCP server. It is able to interact + * with only one client at the time. If a client tries to connect when + * the server is busy with another one, it will have to wait. + * + * @author Olivier Liechti + */ +public class SingleThreadedServer { + + final static Logger LOG = Logger.getLogger(SingleThreadedServer.class.getName()); + + int port; + + /** + * Constructor + * @param port the port to listen on + */ + public SingleThreadedServer(int port) { + this.port = port; + } + + /** + * This method initiates the process. The server creates a socket and binds + * it to the previously specified port. It then waits for clients in a infinite + * loop. When a client arrives, the server will read its input line by line + * and send back the data converted to uppercase. This will continue until + * the client sends the "BYE" command. + */ + public void serveClients() { + ServerSocket serverSocket; + Socket clientSocket = null; + BufferedReader in = null; + PrintWriter out = null; + + try { + serverSocket = new ServerSocket(port); + } catch (IOException ex) { + LOG.log(Level.SEVERE, null, ex); + return; + } + + while (true) { + try { + + LOG.log(Level.INFO, "Waiting (blocking) for a new client on port {0}", port); + clientSocket = serverSocket.accept(); + in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream())); + out = new PrintWriter(clientSocket.getOutputStream()); + String line; + boolean shouldRun = true; + + out.println("Welcome to the Single-Threaded Server.\nSend me text lines and conclude with the BYE command."); + out.flush(); + LOG.info("Reading until client sends BYE or closes the connection..."); + while ( (shouldRun) && (line = in.readLine()) != null ) { + if (line.equalsIgnoreCase("bye")) { + shouldRun = false; + } + out.println("> " + line.toUpperCase()); + out.flush(); + } + + LOG.info("Cleaning up resources..."); + clientSocket.close(); + in.close(); + out.close(); + + } catch (IOException ex) { + if (in != null) { + try { + in.close(); + } catch (IOException ex1) { + LOG.log(Level.SEVERE, ex1.getMessage(), ex1); + } + } + if (out != null) { + out.close(); + } + if (clientSocket != null) { + try { + clientSocket.close(); + } catch (IOException ex1) { + LOG.log(Level.SEVERE, ex1.getMessage(), ex1); + } + } + LOG.log(Level.SEVERE, ex.getMessage(), ex); + } + } + } +} diff --git a/examples/07-TcpServers/TcpServers/src/main/java/ch/heigvd/res/examples/TcpServers.java b/examples/07-TcpServers/TcpServers/src/main/java/ch/heigvd/res/examples/TcpServers.java new file mode 100644 index 0000000..0327cd1 --- /dev/null +++ b/examples/07-TcpServers/TcpServers/src/main/java/ch/heigvd/res/examples/TcpServers.java @@ -0,0 +1,37 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package ch.heigvd.res.examples; + +/** + * This application shows the difference between a single threaded TCP server + * and a multi threaded TCP server. It shows that the first one is only able to + * process one client at the time, which is obviously not really an option for + * most applications. The second one uses n+1 threads, where n is the number of + * clients currently connected. The extra thread is used to wait for new clients + * to arrive, in a loop. + * + * The application starts the multi-threaded server on port 2323 and the + * single-threaded server on port 2424. Use several terminals and the telnet + * command to (try to) connect to the servers and compare the behavior. + * + * @author Olivier Liechti + */ +public class TcpServers { + + /** + * @param args the command line arguments + */ + public static void main(String[] args) { + System.setProperty("java.util.logging.SimpleFormatter.format", "%5$s %n"); + + MultiThreadedServer multi = new MultiThreadedServer(2323); + multi.serveClients(); + + SingleThreadedServer single = new SingleThreadedServer(2424); + single.serveClients(); + } + +}