Merge branch 'feat/network-manager' into 'main'

Add (v1) NetworkManager, Session and Transport

See merge request cs108-fs26/Gruppe-13!17
This commit was merged in pull request #173.
This commit is contained in:
Lars Simon Winzer
2026-03-13 15:49:55 +01:00
10 changed files with 422 additions and 0 deletions
@@ -1,8 +1,22 @@
package ch.unibas.dmi.dbis.cs108.casono.server; 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 class ServerApp {
public static void start(String arg) { public static void start(String arg) {
int port = Integer.parseInt(arg); int port = Integer.parseInt(arg);
System.out.println("You've selected the server. It will accept connections at port " + port); 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();
} }
} }
@@ -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);
}
}
}
@@ -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 {}
@@ -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 {}
@@ -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<Class<?>, List<Consumer<Object>>> 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 <T extends Event> void subscribe(Class<T> eventType, Consumer<T> handler) {
handlers.computeIfAbsent(eventType, k -> new CopyOnWriteArrayList<>())
.add((Consumer<Object>) (Consumer<?>) handler);
}
/**
* Publishes an event to all subscribed handlers.
*
* @param event the event to publish
*/
public <T extends Event> void publish(T event) {
Objects.requireNonNull(event, "event must not be null");
List<Consumer<Object>> subscribers = handlers.get(event.getClass());
if (subscribers != null) {
subscribers.forEach(h -> h.accept(event));
}
}
}
@@ -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;
}
}
}
}
@@ -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;
}
}
@@ -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<SessionId, Session> 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);
}
}
@@ -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();
}
}
@@ -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;
}