Хэндшейк, сервисы, аннотационные блокировки в протоколе, репозитории

This commit is contained in:
RoyceDa
2026-02-03 05:42:46 +02:00
parent 4c290a01ac
commit 9b715df09d
12 changed files with 457 additions and 20 deletions

View File

@@ -11,6 +11,7 @@ import org.java_websocket.handshake.ClientHandshake;
import org.java_websocket.server.WebSocketServer;
import io.orprotocol.client.Client;
import io.orprotocol.lock.ThreadLocker;
import io.orprotocol.packet.Packet;
import io.orprotocol.packet.PacketExecutor;
import io.orprotocol.packet.PacketManager;
@@ -22,6 +23,7 @@ public class Server extends WebSocketServer {
private ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
private Context context;
private ServerListener listener;
private ThreadLocker threadLocker = new ThreadLocker();
/**
* Конструктор сервера
@@ -144,10 +146,27 @@ public class Server extends WebSocketServer {
*/
return;
}
executor.onPacketReceived(packet, client);
/**
* Проверяем наличие блокировки для данного пакета и ключа в аннотации @Lock.
*/
if(!threadLocker.acquireLock(packet, executorClass)) {
/**
* Если блокировка уже существует, значит другой поток обрабатывает пакет
* с таким же значением lockFor, отклоняем текущий пакет.
*/
return;
}
try {
executor.onPacketReceived(packet, client);
} finally {
/**
* Снимаем блокировку после обработки пакета.
*/
threadLocker.releaseLock(packet, executorClass);
}
} catch (Exception e) {
System.out.println("Error while processing packet " + packetClass.getName());
System.out.println(e.getStackTrace());
e.printStackTrace();
}
}

View File

@@ -46,7 +46,7 @@ public class Client {
this.eciTags = new HashMap<Class<? extends ECITag>, ECITag>();
this.heartbeatInterval = heartbeatInterval;
this.lastHeartbeatTime = System.currentTimeMillis();
this.packetManager = new PacketManager();
this.packetManager = packetManager;
}
/**
@@ -57,7 +57,7 @@ public class Client {
* @return
*/
public boolean isAlive() {
return (System.currentTimeMillis() - this.lastHeartbeatTime) * 2 <= this.heartbeatInterval * 1000;
return (System.currentTimeMillis() - this.lastHeartbeatTime) <= ((this.heartbeatInterval * 1000) * 2);
}
/**

View File

@@ -0,0 +1,23 @@
package io.orprotocol.lock;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Аннотация для указания блокировки
* при обработке пакета.
* Мультипоточная блокировка, то есть блокировка сработает только
* если другой поток уже обрабатывает пакет с полем lockFor, однако другие потоки
* могут обрабатывать пакеты с другими значениями lockFor параллельно.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Lock {
/**
* По какому полю в пакете
* будет осуществляться блокировка
*/
String lockFor();
}

View File

@@ -0,0 +1,82 @@
package io.orprotocol.lock;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.concurrent.ConcurrentHashMap;
import io.orprotocol.client.Client;
import io.orprotocol.packet.Packet;
import io.orprotocol.packet.PacketExecutor;
/**
* Менеджер блокировок для обработки пакетов с аннотацией @Lock.
*/
public class ThreadLocker {
private final ConcurrentHashMap<String, Boolean> locks = new ConcurrentHashMap<>();
/**
* Пытается захватить блокировку для указанного пакета и ключа в аннотации @Lock.
* @param packet Пакет для которого требуется блокировка
* @param exectuor Класс исполнителя пакета
* @return true, если блокировка успешно захвачена, иначе false.
*/
public boolean acquireLock(Packet packet, Class<? extends PacketExecutor> exectuor) {
try{
Method prMethod = exectuor.getMethod("onPacketReceived", Packet.class, Client.class);
if(prMethod == null) {
return true;
}
Lock lockAnnotation = prMethod.getAnnotation(Lock.class);
if(lockAnnotation == null) {
return true;
}
String fieldName = lockAnnotation.lockFor();
Field field = packet.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
String fieldValue = (String) field.get(packet);
String lockValue = packet.getClass().getName() + "_" + fieldValue;
if(locks.putIfAbsent(lockValue, true) != null) {
/**
* Если блокировка уже существует, значит другой поток обрабатывает пакет
* с таким же значением lockFor, отклоняем текущий пакет.
*/
return false;
}
return true;
}catch(Exception e) {
/**
* Игнорируем ошибки при попытке блокировки,
* чтобы не блокировать обработку пакета из-за ошибок рефлексии
*/
return true;
}
}
/**
* Освобождает блокировку для указанного пакета и ключа в аннотации @Lock.
* @param packet Пакет для которого требуется разблокировка
* @param exectuor Класс исполнителя пакета
*/
public void releaseLock(Packet packet, Class<? extends PacketExecutor> exectuor) {
try{
Method prMethod = exectuor.getMethod("onPacketReceived", Packet.class, Client.class);
if(prMethod == null) {
return;
}
Lock lockAnnotation = prMethod.getAnnotation(Lock.class);
if(lockAnnotation == null) {
return;
}
String fieldName = lockAnnotation.lockFor();
Field field = packet.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
String fieldValue = (String) field.get(packet);
String lockValue = packet.getClass().getName() + "_" + fieldValue;
locks.remove(lockValue);
}catch(Exception e) {
// Игнорируем ошибки при разблокировке
}
}
}