diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/ServerApp.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/ServerApp.java index 520dc6b..a7814b0 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/ServerApp.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/ServerApp.java @@ -1,8 +1,22 @@ package ch.unibas.dmi.dbis.cs108.casono.server; +import ch.unibas.dmi.dbis.cs108.casono.server.network.NetworkManager; +import ch.unibas.dmi.dbis.cs108.casono.server.network.events.DisconnectEvent; +import ch.unibas.dmi.dbis.cs108.casono.server.network.events.EventBus; +import ch.unibas.dmi.dbis.cs108.casono.server.network.sessions.SessionManager; + +/** + * Application class for starting the server. + */ public class ServerApp { public static void start(String arg) { int port = Integer.parseInt(arg); System.out.println("You've selected the server. It will accept connections at port " + port); + + EventBus eventBus = new EventBus(); + SessionManager sessionManager = new SessionManager(); + eventBus.subscribe(DisconnectEvent.class, event -> sessionManager.removeSession(event.sessionId())); + NetworkManager networkManager = new NetworkManager(port, sessionManager, eventBus); + networkManager.start(); } } diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/NetworkManager.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/NetworkManager.java new file mode 100644 index 0000000..50b53d9 --- /dev/null +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/NetworkManager.java @@ -0,0 +1,81 @@ +package ch.unibas.dmi.dbis.cs108.casono.server.network; + +import java.io.IOException; +import java.net.ServerSocket; +import java.net.Socket; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import ch.unibas.dmi.dbis.cs108.casono.server.network.events.DisconnectEvent; +import ch.unibas.dmi.dbis.cs108.casono.server.network.events.EventBus; +import ch.unibas.dmi.dbis.cs108.casono.server.network.sessions.Session; +import ch.unibas.dmi.dbis.cs108.casono.server.network.sessions.SessionManager; +import ch.unibas.dmi.dbis.cs108.casono.server.network.transport.TcpTransport; + +/** + * Creates and manages the server socket. Accepts new incoming connections and creates sessions. + */ +public class NetworkManager implements Runnable { + private Integer port; + private Logger logger; + private Thread thread; + private Boolean running; + private SessionManager sessionManager; + private EventBus eventBus; + + /** + * Creates a new NetworkManager with the given port, session manager, and event bus. + * + * @param port the port to listen on + * @param sessionManager the session manager to use + * @param eventBus the event bus for events + */ + public NetworkManager(Integer port, SessionManager sessionManager, EventBus eventBus) { + this.port = port; + this.logger = LogManager.getLogger(NetworkManager.class); + this.thread = new Thread(this, "networkManager"); + this.running = true; + this.sessionManager = sessionManager; + this.eventBus = eventBus; + this.eventBus.subscribe(DisconnectEvent.class, event -> clientDisconnected(event)); + } + + /** + * Starts the internal thread to accept new connections. + */ + public void start() { + logger.debug("Starting server at port " + port); + thread.start(); + } + + /** + * Handles client disconnection events. + * + * @param event the disconnect event + */ + public void clientDisconnected(DisconnectEvent event) { + logger.info("Session " + event.sessionId().value() + " disconnected adhasghd"); + } + + /** + * Runs the network manager loop, accepting connections. + */ + @Override + public void run() { + try (ServerSocket serverSocket = new ServerSocket(port)) { + while (running) { + Socket clientSocket = serverSocket.accept(); + + System.out.println("Accepted connection from " + clientSocket.getRemoteSocketAddress()); + + Session session = new Session(new TcpTransport(clientSocket), eventBus); + sessionManager.addSession(session); + session.start(); + } + + } catch (IOException e) { + logger.fatal(e); + } + } +} diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/events/DisconnectEvent.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/events/DisconnectEvent.java new file mode 100644 index 0000000..b6b0d78 --- /dev/null +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/events/DisconnectEvent.java @@ -0,0 +1,8 @@ +package ch.unibas.dmi.dbis.cs108.casono.server.network.events; + +import ch.unibas.dmi.dbis.cs108.casono.server.network.sessions.SessionId; + +/** + * Represents a disconnect event for a session. + */ +public record DisconnectEvent(SessionId sessionId) implements Event {} diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/events/Event.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/events/Event.java new file mode 100644 index 0000000..75c0954 --- /dev/null +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/events/Event.java @@ -0,0 +1,6 @@ +package ch.unibas.dmi.dbis.cs108.casono.server.network.events; + +/** + * Marker interface for events in the event bus system. + */ +interface Event {} diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/events/EventBus.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/events/EventBus.java new file mode 100644 index 0000000..4141ab8 --- /dev/null +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/events/EventBus.java @@ -0,0 +1,40 @@ +package ch.unibas.dmi.dbis.cs108.casono.server.network.events; + +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.function.Consumer; + +/** + * A simple event bus for publishing and subscribing to events. + */ +public class EventBus { + private final Map, List>> handlers = new ConcurrentHashMap<>(); + + /** + * Subscribes a handler to a specific event type. + * + * @param eventType the class of the event to subscribe to + * @param handler the consumer to handle the event + */ + @SuppressWarnings("unchecked") // This cast is safe, because handlers only get passed the type they subscribed to + public void subscribe(Class eventType, Consumer handler) { + handlers.computeIfAbsent(eventType, k -> new CopyOnWriteArrayList<>()) + .add((Consumer) (Consumer) handler); + } + + /** + * Publishes an event to all subscribed handlers. + * + * @param event the event to publish + */ + public void publish(T event) { + Objects.requireNonNull(event, "event must not be null"); + List> subscribers = handlers.get(event.getClass()); + if (subscribers != null) { + subscribers.forEach(h -> h.accept(event)); + } + } +} diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/sessions/Session.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/sessions/Session.java new file mode 100644 index 0000000..d2464f1 --- /dev/null +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/sessions/Session.java @@ -0,0 +1,85 @@ +package ch.unibas.dmi.dbis.cs108.casono.server.network.sessions; + +import java.io.EOFException; +import java.io.IOException; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import ch.unibas.dmi.dbis.cs108.casono.server.network.events.DisconnectEvent; +import ch.unibas.dmi.dbis.cs108.casono.server.network.events.EventBus; +import ch.unibas.dmi.dbis.cs108.casono.server.network.transport.TransportLayer; + +/** + * Represents a client session in the network server. + */ +public class Session implements Runnable { + private SessionId id; + private Thread thread; + private TransportLayer transport; + private Logger logger; + private Boolean running; + private EventBus eventBus; + + /** + * Creates a new Session with the given transport and event bus. + * + * @param transport the transport layer for communication + * @param eventBus the event bus for publishing events + * @throws IOException if an I/O error occurs during initialization + */ + public Session(TransportLayer transport, EventBus eventBus) throws IOException { + this.id = new SessionId(); + this.thread = new Thread(this, "session-" + this.id.value()); + this.transport = transport; + this.running = true; + this.eventBus = eventBus; + + this.logger = LogManager.getLogger(Session.class.toString() + id.value()); + this.logger.info("Created new session"); + } + + /** + * Returns the ID of this session. + * + * @return the session ID + */ + public SessionId getId() { + return this.id; + } + + /** + * Starts the session thread. + */ + public void start() { + thread.start(); + } + + /** + * Closes the session and its transport. + * + * @throws IOException if an I/O error occurs + */ + public void close() throws IOException { + transport.close(); + this.running = false; + } + + /** + * Runs the session loop, reading from the transport. + */ + @Override + public void run() { + while (running) { + try { + System.out.println("Recieved: " + transport.read()); + } catch (EOFException e) { + logger.info("Client disconnected"); + eventBus.publish(new DisconnectEvent(id)); + break; + } catch (IOException e) { + e.printStackTrace(); + break; + } + } + } +} diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/sessions/SessionId.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/sessions/SessionId.java new file mode 100644 index 0000000..431ebdc --- /dev/null +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/sessions/SessionId.java @@ -0,0 +1,35 @@ +package ch.unibas.dmi.dbis.cs108.casono.server.network.sessions; + +import java.util.UUID; + +/** + * Represents a unique identifier for a session. + */ +public class SessionId { + private final UUID value; + + /** + * Creates a new SessionId with a randomly generated UUID. + */ + public SessionId() { + this.value = UUID.randomUUID(); + } + + /** + * Creates a new SessionId with the specified UUID. + * + * @param UUID to use for this SessionId + */ + public SessionId(UUID value) { + this.value = value; + } + + /** + * Returns the UUID value of this SessionId. + * + * @return the UUID value + */ + public UUID value() { + return value; + } +} diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/sessions/SessionManager.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/sessions/SessionManager.java new file mode 100644 index 0000000..42bc4ac --- /dev/null +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/sessions/SessionManager.java @@ -0,0 +1,59 @@ +package ch.unibas.dmi.dbis.cs108.casono.server.network.sessions; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Manages active sessions in the server. + */ +public class SessionManager { + private Map sessions; + + /** + * Constructs a new SessionManager. + */ + public SessionManager() { + this.sessions = new ConcurrentHashMap<>(); + } + + /** + * Adds a session to the manager. + * + * @param session the session to add + */ + public void addSession(Session session) { + sessions.put(session.getId(), session); + System.out.println("Added session " + session.getId().value() + " to session manager"); + } + + /** + * Removes a session by its ID. + * + * @param id the ID of the session to remove + * @return the removed session, or null if not found + */ + public Session removeSession(SessionId id) { + System.out.println("Removed session " + id.value() + " from session manager"); + return sessions.remove(id); + } + + /** + * Removes the specified session. + * + * @param session the session to remove + * @return the removed session, or null if not found + */ + public Session removeSession(Session session) { + return sessions.remove(session.getId()); + } + + /** + * Retrieves a session by its ID. + * + * @param id the ID of the session to retrieve + * @return the session with the specified ID, or null if not found + */ + public Session getSessionById(SessionId id) { + return sessions.get(id); + } +} diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/transport/TcpTransport.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/transport/TcpTransport.java new file mode 100644 index 0000000..3f80d6c --- /dev/null +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/transport/TcpTransport.java @@ -0,0 +1,63 @@ +package ch.unibas.dmi.dbis.cs108.casono.server.network.transport; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.net.Socket; +import java.nio.charset.StandardCharsets; + +/** + * Implements TCP-based transport layer for network communication. + */ +public class TcpTransport implements TransportLayer { + private Socket socket; + private DataInputStream in; + private DataOutputStream out; + + /** + * Creates a new TcpTransport with the given socket. + * + * @param socket the socket to use for communication + * @throws IOException if an I/O error occurs + */ + public TcpTransport(Socket socket) throws IOException { + this.socket = socket; + this.in = new DataInputStream(socket.getInputStream()); + this.out = new DataOutputStream(socket.getOutputStream()); + } + + /** + * Reads a string from the socket. + * + * @return the read string + * @throws IOException if an I/O error occurs + */ + public String read() throws IOException { + int length = in.readInt(); + byte[] payload = new byte[length]; + in.readFully(payload); + return new String(payload, StandardCharsets.UTF_8); + } + + /** + * Writes a string to the socket. + * + * @param payload the string to write + * @throws IOException if an I/O error occurs + */ + public void write(String payload) throws IOException { + byte[] rawPayload = payload.getBytes(StandardCharsets.UTF_8); + out.writeInt(rawPayload.length); + out.write(rawPayload); + out.flush(); + } + + /** + * Closes the socket. + * + * @throws IOException if an I/O error occurs + */ + public void close() throws IOException { + socket.close(); + } +} diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/transport/TransportLayer.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/transport/TransportLayer.java new file mode 100644 index 0000000..f487a9c --- /dev/null +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/network/transport/TransportLayer.java @@ -0,0 +1,31 @@ +package ch.unibas.dmi.dbis.cs108.casono.server.network.transport; + +import java.io.IOException; + +/** + * Interface for transport layer implementations. + */ +public interface TransportLayer { + /** + * Reads data from the transport layer. + * + * @return the read data as a string + * @throws IOException if an I/O error occurs + */ + String read() throws IOException; + + /** + * Writes data to the transport layer. + * + * @param data the data to write + * @throws IOException if an I/O error occurs + */ + void write(String data) throws IOException; + + /** + * Closes the transport layer. + * + * @throws IOException if an I/O error occurs + */ + void close() throws IOException; +}