feat: Multiplayer #3

Merged
jzitnik merged 14 commits from multiplayer into main 2026-02-04 10:37:42 +00:00
23 changed files with 394 additions and 27 deletions
Showing only changes of commit ef14edffde - Show all commits

14
.idea/FuzzierSettings.xml generated Normal file
View File

@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="com.mituuz.fuzzier.FuzzierSettings">
<option name="modules">
<map>
<entry key="common" value="$PROJECT_DIR$" />
<entry key="game" value="$PROJECT_DIR$" />
<entry key="game (1)" value="$PROJECT_DIR$" />
<entry key="server" value="$PROJECT_DIR$" />
</map>
</option>
<option name="recentlySearchedFiles" value="rO0ABXNyABxqYXZheC5zd2luZy5EZWZhdWx0TGlzdE1vZGVsBgfGCGLvV2ICAAFMAAhkZWxlZ2F0ZXQAEkxqYXZhL3V0aWwvVmVjdG9yO3hyAB1qYXZheC5zd2luZy5BYnN0cmFjdExpc3RNb2RlbFrW+oYSs63tAgABTAAMbGlzdGVuZXJMaXN0dAAlTGphdmF4L3N3aW5nL2V2ZW50L0V2ZW50TGlzdGVuZXJMaXN0O3hwc3IAI2phdmF4LnN3aW5nLmV2ZW50LkV2ZW50TGlzdGVuZXJMaXN0kUjMLXPfDt4DAAB4cHB4c3IAEGphdmEudXRpbC5WZWN0b3LZl31bgDuvAQMAA0kAEWNhcGFjaXR5SW5jcmVtZW50SQAMZWxlbWVudENvdW50WwALZWxlbWVudERhdGF0ABNbTGphdmEvbGFuZy9PYmplY3Q7eHAAAAAAAAAAAXVyABNbTGphdmEubGFuZy5PYmplY3Q7kM5YnxBzKWwCAAB4cAAAAApzcgBIY29tLm1pdHV1ei5mdXp6aWVyLmVudGl0aWVzLkZ1enp5TWF0Y2hDb250YWluZXIkU2VyaWFsaXplZE1hdGNoQ29udGFpbmVy2S8sP5uny74CAARMAAhmaWxlUGF0aHQAEkxqYXZhL2xhbmcvU3RyaW5nO0wACGZpbGVuYW1lcQB+AA1MAA5tb2R1bGVCYXNlUGF0aHEAfgANTAAFc2NvcmV0ADxMY29tL21pdHV1ei9mdXp6aWVyL2VudGl0aWVzL0Z1enp5TWF0Y2hDb250YWluZXIkRnV6enlTY29yZTt4cHQARC9nYW1lL3NyYy9tYWluL2phdmEvY3ovanppdG5pay9jbGllbnQvdXRpbHMvZXZlbnRzL0V2ZW50TWFuYWdlci5qYXZhdAARRXZlbnRNYW5hZ2VyLmphdmF0ABYvaG9tZS9rdWJhL0NvZGluZy9nYW1lc3IAOmNvbS5taXR1dXouZnV6emllci5lbnRpdGllcy5GdXp6eU1hdGNoQ29udGFpbmVyJEZ1enp5U2NvcmUvP9P07ROG2QIABUkADWZpbGVuYW1lU2NvcmVJAA9tdWx0aU1hdGNoU2NvcmVJABBwYXJ0aWFsUGF0aFNjb3JlSQALc3RyZWFrU2NvcmVMABNoaWdobGlnaHRDaGFyYWN0ZXJzdAAPTGphdmEvdXRpbC9TZXQ7eHAAAAAIAAAAAAAAAAAAAAADc3IAEWphdmEudXRpbC5IYXNoU2V0ukSFlZa4tzQDAAB4cHcMAAAAED9AAAAAAAAIc3IAEWphdmEubGFuZy5JbnRlZ2VyEuKgpPeBhzgCAAFJAAV2YWx1ZXhyABBqYXZhLmxhbmcuTnVtYmVyhqyVHQuU4IsCAAB4cAAAAABzcQB+ABgAAAABc3EAfgAYAAAAAnNxAH4AGAAAAANzcQB+ABgAAAAEc3EAfgAYAAAABXNxAH4AGAAAAAZzcQB+ABgAAAAHeHBwcHBwcHBwcHg=" />
</component>
</project>

1
.idea/compiler.xml generated
View File

@@ -2,6 +2,7 @@
<project version="4"> <project version="4">
<component name="CompilerConfiguration"> <component name="CompilerConfiguration">
<annotationProcessing> <annotationProcessing>
<profile default="true" name="Default" enabled="true" />
<profile name="Maven default annotation processors profile" enabled="true"> <profile name="Maven default annotation processors profile" enabled="true">
<sourceOutputDir name="target/generated-sources/annotations" /> <sourceOutputDir name="target/generated-sources/annotations" />
<sourceTestOutputDir name="target/generated-test-sources/test-annotations" /> <sourceTestOutputDir name="target/generated-test-sources/test-annotations" />

2
.idea/misc.xml generated
View File

@@ -8,5 +8,5 @@
</list> </list>
</option> </option>
</component> </component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_25" default="true" project-jdk-name="openjdk-25" project-jdk-type="JavaSDK" /> <component name="ProjectRootManager" version="2" languageLevel="JDK_X" default="true" project-jdk-name="openjdk-25" project-jdk-type="JavaSDK" />
</project> </project>

View File

@@ -24,5 +24,12 @@
<artifactId>jackson-dataformat-yaml</artifactId> <artifactId>jackson-dataformat-yaml</artifactId>
<version>3.0.4</version> <version>3.0.4</version>
</dependency> </dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.42</version>
<scope>provided</scope>
</dependency>
</dependencies> </dependencies>
</project> </project>

View File

@@ -0,0 +1,6 @@
package cz.jzitnik.common.socket;
import java.io.Serializable;
public interface SocketMessage extends Serializable {
}

View File

@@ -0,0 +1,6 @@
package cz.jzitnik.common.socket.messages;
import cz.jzitnik.common.socket.SocketMessage;
public class Test implements SocketMessage {
}

View File

@@ -1,6 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="Encoding"> <component name="Encoding">
<file url="file://$PROJECT_DIR$/common/src/main/java" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/common/src/main/resources" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/game/src/main/java" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/game/src/main/resources" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/server/src/main/java" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/server/src/main/resources" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/src/main/java" charset="UTF-8" /> <file url="file://$PROJECT_DIR$/src/main/java" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/src/main/resources" charset="UTF-8" /> <file url="file://$PROJECT_DIR$/src/main/resources" charset="UTF-8" />
</component> </component>

16
game/.idea/misc.xml generated
View File

@@ -1,17 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="EntryPointsManager">
<list size="3">
<item index="0" class="java.lang.String" itemvalue="cz.jzitnik.annotations.EventHandler" />
<item index="1" class="java.lang.String" itemvalue="cz.jzitnik.annotations.ui.KeyboardPressHandler" />
<item index="2" class="java.lang.String" itemvalue="cz.jzitnik.annotations.ui.MouseHandler" />
</list>
<writeAnnotations>
<writeAnnotation name="cz.jzitnik.annotations.injectors.InjectConfig" />
<writeAnnotation name="cz.jzitnik.annotations.injectors.InjectDependency" />
<writeAnnotation name="cz.jzitnik.annotations.injectors.InjectState" />
</writeAnnotations>
</component>
<component name="ExternalStorageConfigurationManager" enabled="true" /> <component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="MavenProjectsManager"> <component name="MavenProjectsManager">
<option name="originalFiles"> <option name="originalFiles">
@@ -20,7 +8,5 @@
</list> </list>
</option> </option>
</component> </component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_25" default="true" project-jdk-name="openjdk-25" project-jdk-type="JavaSDK"> <component name="ProjectRootManager" version="2" languageLevel="JDK_X" default="true" project-jdk-name="openjdk-25" project-jdk-type="JavaSDK" />
<output url="file://$PROJECT_DIR$/out" />
</component>
</project> </project>

View File

@@ -179,5 +179,11 @@
<artifactId>jvm</artifactId> <artifactId>jvm</artifactId>
<version>2.5</version> <version>2.5</version>
</dependency> </dependency>
<dependency>
<groupId>org.glassfish.tyrus.bundles</groupId>
<artifactId>tyrus-standalone-client</artifactId>
<version>2.2.2</version>
</dependency>
</dependencies> </dependencies>
</project> </project>

View File

@@ -10,6 +10,7 @@ import cz.jzitnik.client.annotations.injectors.InjectState;
import cz.jzitnik.client.events.KeyboardPressEvent; import cz.jzitnik.client.events.KeyboardPressEvent;
import cz.jzitnik.client.events.MouseAction; import cz.jzitnik.client.events.MouseAction;
import cz.jzitnik.client.events.TerminalResizeEvent; import cz.jzitnik.client.events.TerminalResizeEvent;
import cz.jzitnik.client.socket.SocketEventManager;
import cz.jzitnik.client.states.RunningState; import cz.jzitnik.client.states.RunningState;
import cz.jzitnik.client.states.TerminalState; import cz.jzitnik.client.states.TerminalState;
import cz.jzitnik.client.utils.events.EventManager; import cz.jzitnik.client.utils.events.EventManager;
@@ -23,6 +24,9 @@ public class Cli implements Runnable {
@InjectDependency @InjectDependency
private EventManager eventManager; private EventManager eventManager;
@InjectDependency
private SocketEventManager socketEventManager;
@InjectState @InjectState
private TerminalState terminalState; private TerminalState terminalState;
@@ -31,7 +35,9 @@ public class Cli implements Runnable {
@Override @Override
public void run() { public void run() {
eventManager.start(); // Start event manager thread // Start event manager thread
eventManager.start();
socketEventManager.start();
try (TerminalScreen terminal = new DefaultTerminalFactory() try (TerminalScreen terminal = new DefaultTerminalFactory()
.setMouseCaptureMode(MouseCaptureMode.CLICK_RELEASE_DRAG_MOVE) .setMouseCaptureMode(MouseCaptureMode.CLICK_RELEASE_DRAG_MOVE)

View File

@@ -2,10 +2,12 @@ package cz.jzitnik.client;
import cz.jzitnik.client.annotations.injectors.InjectDependency; import cz.jzitnik.client.annotations.injectors.InjectDependency;
import cz.jzitnik.client.game.setup.GameSetup; import cz.jzitnik.client.game.setup.GameSetup;
import cz.jzitnik.client.socket.Client;
import cz.jzitnik.client.utils.DependencyManager; import cz.jzitnik.client.utils.DependencyManager;
import cz.jzitnik.client.utils.GlobalIOHandlerRepository; import cz.jzitnik.client.utils.GlobalIOHandlerRepository;
import cz.jzitnik.client.utils.ScheduledTaskManager; import cz.jzitnik.client.utils.ScheduledTaskManager;
import cz.jzitnik.client.utils.ThreadManager; import cz.jzitnik.client.utils.ThreadManager;
import jakarta.websocket.DeploymentException;
import org.reflections.Reflections; import org.reflections.Reflections;
import java.io.IOException; import java.io.IOException;
@@ -23,15 +25,23 @@ public class Game {
private ScheduledTaskManager scheduledTaskManager; private ScheduledTaskManager scheduledTaskManager;
@InjectDependency @InjectDependency
private GlobalIOHandlerRepository globalIOHandlerRepository; private GlobalIOHandlerRepository globalIOHandlerRepository;
@InjectDependency
private Client client;
public void start() throws IOException { public void start() throws IOException {
dependencyManager.inject(this); dependencyManager.inject(this);
try {
client.connect();
gameSetup.setup(); gameSetup.setup();
threadManager.startAll(); threadManager.startAll();
scheduledTaskManager.startAll(); scheduledTaskManager.startAll();
globalIOHandlerRepository.setup(); globalIOHandlerRepository.setup();
cli.run(); cli.run();
} catch (DeploymentException e) {
throw new RuntimeException(e);
}
} }
} }

View File

@@ -0,0 +1,14 @@
package cz.jzitnik.client.annotations;
import cz.jzitnik.common.socket.SocketMessage;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface SocketEventHandler {
Class<? extends SocketMessage> value();
}

View File

@@ -0,0 +1,7 @@
package cz.jzitnik.client.events;
import cz.jzitnik.client.utils.events.Event;
import cz.jzitnik.common.socket.SocketMessage;
public record SendSocketMessageEvent(SocketMessage message) implements Event {
}

View File

@@ -0,0 +1,24 @@
package cz.jzitnik.client.events.handlers;
import cz.jzitnik.client.annotations.EventHandler;
import cz.jzitnik.client.annotations.injectors.InjectDependency;
import cz.jzitnik.client.events.SendSocketMessageEvent;
import cz.jzitnik.client.socket.Client;
import cz.jzitnik.client.utils.events.AbstractEventHandler;
import java.io.IOException;
@EventHandler(SendSocketMessageEvent.class)
public class SendSocketMessageEventHandler extends AbstractEventHandler<SendSocketMessageEvent> {
@InjectDependency
private Client client;
@Override
public void handle(SendSocketMessageEvent event) {
try {
client.send(event.message());
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}

View File

@@ -0,0 +1,7 @@
package cz.jzitnik.client.socket;
import cz.jzitnik.common.socket.SocketMessage;
public abstract class AbstractSocketEventHandler<T extends SocketMessage> {
public abstract void handle(T event);
}

View File

@@ -0,0 +1,50 @@
package cz.jzitnik.client.socket;
import cz.jzitnik.client.annotations.Dependency;
import cz.jzitnik.client.annotations.injectors.InjectDependency;
import cz.jzitnik.common.socket.SocketMessage;
import jakarta.websocket.*;
import lombok.extern.slf4j.Slf4j;
import java.io.*;
import java.net.URI;
import java.nio.ByteBuffer;
@Slf4j
@Dependency
@ClientEndpoint
public class Client {
private Session session;
@InjectDependency
private SocketEventManager socketEventManager;
@OnOpen
public void onOpen(Session session) {
this.session = session;
}
@OnMessage
public void onMessage(ByteBuffer buffer) {
try (ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(buffer.array()))) {
SocketMessage message = (SocketMessage) ois.readObject();
socketEventManager.emitEvent(message);
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
public void send(SocketMessage message) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(message);
oos.flush();
session.getBasicRemote().sendBinary(ByteBuffer.wrap(baos.toByteArray()));
}
public void connect() throws DeploymentException, IOException {
WebSocketContainer container = ContainerProvider.getWebSocketContainer();
container.connectToServer(this, URI.create("ws://localhost:8025/ws"));
}
}

View File

@@ -0,0 +1,94 @@
package cz.jzitnik.client.socket;
import cz.jzitnik.client.annotations.Dependency;
import cz.jzitnik.client.annotations.SocketEventHandler;
import cz.jzitnik.client.annotations.injectors.InjectConfig;
import cz.jzitnik.client.annotations.injectors.InjectState;
import cz.jzitnik.client.config.ThreadPoolConfig;
import cz.jzitnik.client.states.RunningState;
import cz.jzitnik.client.utils.DependencyManager;
import cz.jzitnik.common.socket.SocketMessage;
import lombok.extern.slf4j.Slf4j;
import org.reflections.Reflections;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
@Slf4j
@Dependency
public class SocketEventManager extends Thread {
private final DependencyManager dependencyManager;
private ExecutorService eventExecutor;
private final HashMap<Class<? extends SocketMessage>, AbstractSocketEventHandler<?>> handlers = new HashMap<>();
private final BlockingQueue<SocketMessage> eventQueue = new LinkedBlockingQueue<>();
@InjectConfig
private ThreadPoolConfig threadPoolConfig;
@InjectState
private RunningState runningState;
public void emitEvent(SocketMessage event) {
eventQueue.add(event);
}
public SocketEventManager(Reflections reflections, DependencyManager dependencyManager) {
this.dependencyManager = dependencyManager;
setDaemon(true);
var classes = reflections.getTypesAnnotatedWith(SocketEventHandler.class);
for (var clazz : classes) {
SocketEventHandler eventHandler = clazz.getAnnotation(SocketEventHandler.class);
try {
var instance = (AbstractSocketEventHandler<?>) clazz.getDeclaredConstructor().newInstance();
handlers.put(eventHandler.value(), instance);
} catch (InstantiationException | IllegalAccessException | InvocationTargetException |
NoSuchMethodException e) {
log.error("Failed to instantiate socket event handler: {}", clazz.getName(), e);
}
}
}
@Override
public void run() {
for (Object instance : handlers.values()) {
dependencyManager.inject(instance);
}
eventExecutor = Executors.newFixedThreadPool(threadPoolConfig.eventThreadCount());
while (runningState.isRunning()) {
try {
SocketMessage event = eventQueue.take();
handleEvent(event);
} catch (InterruptedException e) {
// The game is shutting down.
eventExecutor.shutdownNow();
Thread.currentThread().interrupt();
}
}
eventExecutor.shutdown();
}
@SuppressWarnings("unchecked")
private <T extends SocketMessage> AbstractSocketEventHandler<T> getHandler(Class<T> type) {
return (AbstractSocketEventHandler<T>) handlers.get(type);
}
@SuppressWarnings("unchecked")
private void handleEvent(SocketMessage event) {
eventExecutor.submit(() -> {
try {
AbstractSocketEventHandler<SocketMessage> handler = getHandler((Class<SocketMessage>) event.getClass());
handler.handle(event);
} catch (Exception e) {
log.error("Error", e);
}
});
}
}

View File

@@ -0,0 +1,15 @@
package cz.jzitnik.client.socket.events;
import cz.jzitnik.client.annotations.SocketEventHandler;
import cz.jzitnik.client.socket.AbstractSocketEventHandler;
import cz.jzitnik.common.socket.messages.Test;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@SocketEventHandler(Test.class)
public class TestHandler extends AbstractSocketEventHandler<Test> {
@Override
public void handle(Test event) {
log.debug("Got test: {}", event);
}
}

View File

@@ -1,5 +1,5 @@
package cz.jzitnik.client.utils.events; package cz.jzitnik.client.utils.events;
public abstract class AbstractEventHandler<T> { public abstract class AbstractEventHandler<T extends Event> {
public abstract void handle(T event); public abstract void handle(T event);
} }

View File

@@ -24,7 +24,7 @@ public class EventManager extends Thread {
private ThreadPoolConfig threadPoolConfig; private ThreadPoolConfig threadPoolConfig;
private ExecutorService eventExecutor; private ExecutorService eventExecutor;
private final HashMap<Class<? extends Event>, AbstractEventHandler<? extends Event>> handlers = new HashMap<>(); private final HashMap<Class<? extends Event>, AbstractEventHandler<?>> handlers = new HashMap<>();
private final BlockingQueue<EventRecord> eventQueue = new LinkedBlockingQueue<>(); private final BlockingQueue<EventRecord> eventQueue = new LinkedBlockingQueue<>();
private final DependencyManager dependencyManager; private final DependencyManager dependencyManager;
@@ -74,7 +74,7 @@ public class EventManager extends Thread {
for (var clazz : classes) { for (var clazz : classes) {
EventHandler eventHandler = clazz.getAnnotation(EventHandler.class); EventHandler eventHandler = clazz.getAnnotation(EventHandler.class);
try { try {
var instance = (AbstractEventHandler<? extends Event>) clazz.getDeclaredConstructor().newInstance(); var instance = (AbstractEventHandler<?>) clazz.getDeclaredConstructor().newInstance();
handlers.put(eventHandler.value(), instance); handlers.put(eventHandler.value(), instance);
} catch (InstantiationException | IllegalAccessException | InvocationTargetException | } catch (InstantiationException | IllegalAccessException | InvocationTargetException |
NoSuchMethodException e) { NoSuchMethodException e) {

View File

@@ -18,5 +18,49 @@
<artifactId>common</artifactId> <artifactId>common</artifactId>
<version>1.0-SNAPSHOT</version> <version>1.0-SNAPSHOT</version>
</dependency> </dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.42</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>jakarta.websocket</groupId>
<artifactId>jakarta.websocket-api</artifactId>
<version>2.3.0-M2</version>
</dependency>
<dependency>
<groupId>org.glassfish.tyrus</groupId>
<artifactId>tyrus-server</artifactId>
<version>2.2.2</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.glassfish.tyrus</groupId>
<artifactId>tyrus-container-grizzly-server</artifactId>
<version>2.2.2</version>
</dependency>
<dependency>
<groupId>org.glassfish.tyrus</groupId>
<artifactId>tyrus-client</artifactId>
<version>2.2.2</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>2.0.17</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.5.25</version>
</dependency>
</dependencies> </dependencies>
</project> </project>

View File

@@ -1,7 +1,18 @@
package cz.jzitnik.server; package cz.jzitnik.server;
public class Main { import jakarta.websocket.DeploymentException;
static void main() { import org.glassfish.tyrus.server.Server;
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;
public class Main {
static void main() throws DeploymentException {
Map<String, Object> properties = new HashMap<>();
Server server = new Server("localhost", 8025, "/", properties, WebSocket.class);
server.start();
new Scanner(System.in).nextLine();
} }
} }

View File

@@ -0,0 +1,53 @@
package cz.jzitnik.server;
import cz.jzitnik.common.socket.SocketMessage;
import cz.jzitnik.common.socket.messages.Test;
import jakarta.websocket.*;
import jakarta.websocket.server.ServerEndpoint;
import lombok.extern.slf4j.Slf4j;
import java.io.*;
@Slf4j
@ServerEndpoint("/ws")
public class WebSocket {
@OnOpen
public void onOpen(Session session) {
log.debug("Client connected: " + session.getId());
try {
SocketMessage response = new Test();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(response);
oos.flush();
session.getBasicRemote().sendBinary(java.nio.ByteBuffer.wrap(baos.toByteArray()));
} catch (IOException e) {
e.printStackTrace();
}
}
// Receive binary data from client
@OnMessage
public void onMessage(byte[] bytes, Session session) {
try (ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
ObjectInputStream ois = new ObjectInputStream(bais)) {
SocketMessage socketMessage = (SocketMessage) ois.readObject();
System.out.println("Received: " + socketMessage);
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
@OnClose
public void onClose(Session session, CloseReason reason) {
System.out.println("Connection closed: " + reason);
}
@OnError
public void onError(Session session, Throwable throwable) {
throwable.printStackTrace();
}
}