Инициализация протокола Rosetta
This commit is contained in:
42
src/main/java/com/rosetta/im/Boot.java
Normal file
42
src/main/java/com/rosetta/im/Boot.java
Normal file
@@ -0,0 +1,42 @@
|
||||
package com.rosetta.im;
|
||||
|
||||
import com.rosetta.im.executors.Executor0Handshake;
|
||||
import com.rosetta.im.packet.Packet0Handshake;
|
||||
import com.rosetta.im.protocol.packet.PacketManager;
|
||||
|
||||
public class Boot {
|
||||
|
||||
private PacketManager packetManager;
|
||||
|
||||
public Boot(PacketManager packetManager) {
|
||||
this.packetManager = packetManager;
|
||||
}
|
||||
|
||||
public PacketManager getPacketManager() {
|
||||
return this.packetManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Инициализация всех пакетов и их обработчиков
|
||||
*/
|
||||
public void bootstrap() {
|
||||
this.registerAllPackets();
|
||||
this.registerAllExecutors();
|
||||
this.printBootMessage();
|
||||
}
|
||||
|
||||
private void registerAllPackets() {
|
||||
this.packetManager.registerPacket(0, Packet0Handshake.class);
|
||||
}
|
||||
|
||||
private void registerAllExecutors() {
|
||||
this.packetManager.registerExecutor(0, Executor0Handshake.class);
|
||||
}
|
||||
|
||||
private void printBootMessage() {
|
||||
System.out.println("Bootstrapping completed. All packets and executors are registered.");
|
||||
System.out.println("Total packets registered: " + this.packetManager.totalPackets());
|
||||
System.out.println("Total executors registered: " + this.packetManager.totalExecutors());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,20 +1,28 @@
|
||||
package com.rosetta.im;
|
||||
|
||||
|
||||
import com.rosetta.im.packet.Packet0Handshake;
|
||||
import com.rosetta.im.protocol.Server;
|
||||
import com.rosetta.im.protocol.Settings;
|
||||
import com.rosetta.im.protocol.packet.PacketManager;
|
||||
|
||||
public class Main {
|
||||
public static void main(String[] args) {
|
||||
PacketManager manager = new PacketManager();
|
||||
manager.registerPacket(0, Packet0Handshake.class);
|
||||
|
||||
/**
|
||||
* Регистрация всех пакетов и их обработчиков
|
||||
*/
|
||||
Boot boot = new Boot(manager);
|
||||
boot.bootstrap();
|
||||
|
||||
Server server = new Server(8881, manager);
|
||||
/**
|
||||
* Загрузка настроек сервера
|
||||
*/
|
||||
Settings settings = new Settings(8881, 30);
|
||||
|
||||
/**
|
||||
* Запуск сервера на порту 8881
|
||||
*/
|
||||
Server server = new Server(settings, manager);
|
||||
server.start();
|
||||
|
||||
|
||||
System.out.println("Rosetta server started...");
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,18 @@
|
||||
package com.rosetta.im.executors;
|
||||
|
||||
import com.rosetta.im.packet.Packet0Handshake;
|
||||
import com.rosetta.im.protocol.Client;
|
||||
import com.rosetta.im.protocol.packet.Packet;
|
||||
import com.rosetta.im.protocol.packet.PacketExecutor;
|
||||
|
||||
public class Executor0Handshake implements PacketExecutor {
|
||||
|
||||
@Override
|
||||
public void onPacketReceived(Class<? extends Packet> packet) {
|
||||
|
||||
public void onPacketReceived(Packet packet, Client client) {
|
||||
Packet0Handshake handshake = (Packet0Handshake) packet;
|
||||
String publicKey = handshake.getPublicKey();
|
||||
String privateKey = handshake.getPrivateKey();
|
||||
int b = 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -26,6 +26,11 @@ public class Packet0Handshake extends Packet {
|
||||
* Публичный и приватный ключи клиента
|
||||
*/
|
||||
private String publicKey;
|
||||
/**
|
||||
* Приватный ключ клиента
|
||||
* Это не совсем приватный ключ, а лишь необратимо зашифрованная его версия
|
||||
* для идентификации клиента на сервере.
|
||||
*/
|
||||
private String privateKey;
|
||||
/**
|
||||
* Версия протокола клиента
|
||||
@@ -80,4 +85,52 @@ public class Packet0Handshake extends Packet {
|
||||
);
|
||||
}
|
||||
|
||||
public String getPublicKey() {
|
||||
return publicKey;
|
||||
}
|
||||
|
||||
public String getPrivateKey() {
|
||||
return privateKey;
|
||||
}
|
||||
|
||||
public int getProtocolVersion() {
|
||||
return protocolVersion;
|
||||
}
|
||||
|
||||
public int getHeartbeatInterval() {
|
||||
return heartbeatInterval;
|
||||
}
|
||||
|
||||
public Device getDevice() {
|
||||
return device;
|
||||
}
|
||||
|
||||
public HandshakeStage getHandshakeStage() {
|
||||
return handshakeStage;
|
||||
}
|
||||
|
||||
public void setHandshakeStage(HandshakeStage handshakeStage) {
|
||||
this.handshakeStage = handshakeStage;
|
||||
}
|
||||
|
||||
public void setDevice(Device device) {
|
||||
this.device = device;
|
||||
}
|
||||
|
||||
public void setHeartbeatInterval(int heartbeatInterval) {
|
||||
this.heartbeatInterval = heartbeatInterval;
|
||||
}
|
||||
|
||||
public void setProtocolVersion(int protocolVersion) {
|
||||
this.protocolVersion = protocolVersion;
|
||||
}
|
||||
|
||||
public void setPrivateKey(String privateKey) {
|
||||
this.privateKey = privateKey;
|
||||
}
|
||||
|
||||
public void setPublicKey(String publicKey) {
|
||||
this.publicKey = publicKey;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -7,40 +7,133 @@ import org.java_websocket.WebSocket;
|
||||
|
||||
import com.rosetta.im.protocol.util.StringUtil;
|
||||
|
||||
/**
|
||||
* Клиент, подключенный к серверу.
|
||||
*/
|
||||
public class Client {
|
||||
|
||||
public WebSocket socket;
|
||||
public String clientId;
|
||||
public Map<String, Object> clientData;
|
||||
/**
|
||||
* Интервал отправки heartbeat пакетов в миллисекундах.
|
||||
*/
|
||||
public long heartbeatInterval = 0;
|
||||
/**
|
||||
* Время последнего полученного heartbeat в миллисекундах.
|
||||
*/
|
||||
private volatile long lastHeartbeatTime;
|
||||
|
||||
public Client(WebSocket socket) {
|
||||
/**
|
||||
* Создает нового клиента с указанным сокетом.
|
||||
* Этот метод используется внутри протокола для управления подключениями клиентов.
|
||||
* @param socket Веб-сокет клиента.
|
||||
*
|
||||
*/
|
||||
public Client(WebSocket socket, long heartbeatInterval) {
|
||||
this.socket = socket;
|
||||
this.clientId = StringUtil.randomString(32);
|
||||
this.clientData = new HashMap<>();
|
||||
this.heartbeatInterval = heartbeatInterval;
|
||||
this.lastHeartbeatTime = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
/**
|
||||
* Проверяет жив ли клиент на основе времени последнего heartbeat.
|
||||
* Если с момента последнего heartbeat прошло больше, чем указанный интервал, клиент считается неактивным.
|
||||
*
|
||||
* Для того чтобы исключить сетевые задержки, проверка умножает интервал на 2.
|
||||
* @return
|
||||
*/
|
||||
public boolean isAlive() {
|
||||
return (System.currentTimeMillis() - this.lastHeartbeatTime) * 2 <= this.heartbeatInterval * 1000;
|
||||
}
|
||||
|
||||
/**
|
||||
* Обновляет время последнего полученного heartbeat на текущее время.
|
||||
*/
|
||||
public void updateHeartbeat() {
|
||||
this.lastHeartbeatTime = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
/**
|
||||
* Получает уникальный идентификатор клиента.
|
||||
* @return Идентификатор клиента.
|
||||
*/
|
||||
public String getClientId() {
|
||||
return clientId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Получает данные, связанные с клиентом.
|
||||
* @return Данные клиента.
|
||||
*/
|
||||
public Map<String, Object> getClientData() {
|
||||
return clientData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Устанавливает уникальный идентификатор клиента.
|
||||
* @param clientId Идентификатор клиента.
|
||||
*/
|
||||
public void setClientId(String clientId) {
|
||||
this.clientId = clientId;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Устанавливает данные для клиента по указанному ключу.
|
||||
* @param key Ключ данных.
|
||||
* @param value Значение данных.
|
||||
*/
|
||||
public void setData(String key, Object value) {
|
||||
this.clientData.put(key, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Устанавливает данные клиента.
|
||||
* @param data Данные клиента.
|
||||
*/
|
||||
public void setData(Map<String, Object> data) {
|
||||
this.clientData = data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Получает данные клиента по указанному ключу.
|
||||
* @param key Ключ данных.
|
||||
* @return Значение данных.
|
||||
*/
|
||||
public Object getData(String key) {
|
||||
return this.clientData.get(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Получает веб-сокет клиента.
|
||||
* @return Веб-сокет.
|
||||
*/
|
||||
public WebSocket getSocket() {
|
||||
return socket;
|
||||
}
|
||||
|
||||
/**
|
||||
* Отключает клиента с указанным кодом.
|
||||
* @param code Код отключения.
|
||||
*/
|
||||
public void disconnect(int code) {
|
||||
this.socket.close(code);
|
||||
}
|
||||
/**
|
||||
* Отключает клиента с указанным кодом отказа.
|
||||
* @param code Код отказа.
|
||||
*/
|
||||
public void disconnect(Failures code) {
|
||||
this.disconnect(code.getCode());
|
||||
}
|
||||
|
||||
/**
|
||||
* Отключает клиента с неизвестной причиной.
|
||||
*/
|
||||
public void disconnect() {
|
||||
this.disconnect(Failures.UNKNOWN_FAILURE);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
55
src/main/java/com/rosetta/im/protocol/Failures.java
Normal file
55
src/main/java/com/rosetta/im/protocol/Failures.java
Normal file
@@ -0,0 +1,55 @@
|
||||
package com.rosetta.im.protocol;
|
||||
|
||||
/**
|
||||
* Перечисление кодов ошибок, используемых в протоколе.
|
||||
*/
|
||||
public enum Failures {
|
||||
/**
|
||||
* Код ошибки, указывающий на несоответствие данных.
|
||||
*/
|
||||
DATA_MISSMATCH(3001),
|
||||
/**
|
||||
* Код ошибки, указывающий на незавершенное рукопожатие.
|
||||
*/
|
||||
HANDSHAKE_NOT_COMPLETED(3002),
|
||||
/**
|
||||
* Код ошибки, указывающий на некорректный пакет.
|
||||
*/
|
||||
BAD_PACKET(3003),
|
||||
/**
|
||||
* Код ошибки, указывающий на некорректный пакет.
|
||||
*/
|
||||
INVALID_PACKET(3003),
|
||||
/**
|
||||
* Код ошибки, указывающий на тайм-аут бездействия.
|
||||
*/
|
||||
INACTIVITY_TIMEOUT(3004),
|
||||
/**
|
||||
* Код ошибки, указывающий на неизвестный тип пакета.
|
||||
*/
|
||||
PACKET_ID_FAILURE(3998),
|
||||
/**
|
||||
* Код ошибки, указывающий на неизвестный тип пакета.
|
||||
*/
|
||||
UNSUPPORTED_PACKET(3998),
|
||||
/**
|
||||
* Код ошибки, указывающий на неизвестную ошибку.
|
||||
*/
|
||||
UNKNOWN_FAILURE(3999);
|
||||
|
||||
|
||||
private final int code;
|
||||
|
||||
Failures(int code) {
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
/**
|
||||
* Получает код ошибки.
|
||||
* @return Код ошибки.
|
||||
*/
|
||||
public int getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -2,20 +2,27 @@ package com.rosetta.im.protocol;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.java_websocket.WebSocket;
|
||||
import org.java_websocket.handshake.ClientHandshake;
|
||||
import org.java_websocket.server.WebSocketServer;
|
||||
|
||||
import com.rosetta.im.protocol.packet.Packet;
|
||||
import com.rosetta.im.protocol.packet.PacketExecutor;
|
||||
import com.rosetta.im.protocol.packet.PacketManager;
|
||||
|
||||
public class Server extends WebSocketServer {
|
||||
|
||||
private PacketManager packetManager;
|
||||
private Settings settings;
|
||||
private ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
|
||||
|
||||
public Server(int port, PacketManager packetManager) {
|
||||
super(new InetSocketAddress(port));
|
||||
public Server(Settings settings, PacketManager packetManager) {
|
||||
super(new InetSocketAddress(settings.port));
|
||||
this.settings = settings;
|
||||
this.packetManager = packetManager;
|
||||
}
|
||||
|
||||
@@ -30,41 +37,97 @@ public class Server extends WebSocketServer {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMessage(WebSocket arg0, String arg1) {
|
||||
|
||||
public void onMessage(WebSocket socket, String message) {
|
||||
/**
|
||||
* Обновляем время последнего полученного heartbeat.
|
||||
* Так как клиент отпраивл нам пакет, он живой.
|
||||
*/
|
||||
Client client = socket.getAttachment();
|
||||
client.updateHeartbeat();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMessage(WebSocket socket, ByteBuffer byteBuffer) {
|
||||
Client client = socket.getAttachment();
|
||||
|
||||
byte[] bytes = byteBuffer.array();
|
||||
Stream stream = new Stream(bytes);
|
||||
int packetId = stream.readInt16();
|
||||
Class<? extends Packet> packetClass = this.packetManager.getPacketClass(packetId);
|
||||
if(packetClass == null){
|
||||
System.out.println("Received unknown packet with id: " + packetId);
|
||||
/**
|
||||
* Обновляем время последнего полученного heartbeat.
|
||||
* Так как клиент отпраивл нам пакет, он живой.
|
||||
*/
|
||||
client.updateHeartbeat();
|
||||
|
||||
if(!this.packetManager.hasPacketSupported(packetId)){
|
||||
/**
|
||||
* Если пакет не поддерживается, отключаем клиента с соответствующим кодом ошибки.
|
||||
*/
|
||||
client.disconnect(Failures.UNSUPPORTED_PACKET);
|
||||
return;
|
||||
}
|
||||
if(!this.packetManager.hasExecutorDelegated(packetId)){
|
||||
/**
|
||||
* Если для пакета не назначен обработчик, отключаем клиента с соответствующим кодом ошибки.
|
||||
*/
|
||||
client.disconnect(Failures.UNSUPPORTED_PACKET);
|
||||
return;
|
||||
}
|
||||
Class<? extends Packet> packetClass = this.packetManager.getPacketClass(packetId);
|
||||
|
||||
try {
|
||||
Packet packet = packetClass.getConstructor().newInstance();
|
||||
packet.packetId = packetId;
|
||||
/**
|
||||
* Читаем данные пакета из потока.
|
||||
*/
|
||||
packet.read(stream);
|
||||
System.out.println("Received packet: " + packetClass.getSimpleName());
|
||||
/**
|
||||
* Получаем обработчик пакета и вызываем его метод обработки.
|
||||
*/
|
||||
Class<? extends PacketExecutor> executorClass = this.packetManager.getExecutors().get(packetId);
|
||||
PacketExecutor executor = executorClass.getConstructor().newInstance();
|
||||
executor.onPacketReceived(packet, client);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
System.out.println("Error while processing packet " + packetClass.getName());
|
||||
System.out.println(e.getStackTrace());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onOpen(WebSocket socket, ClientHandshake arg1) {
|
||||
Client client = new Client(socket);
|
||||
/**
|
||||
* Создаем нового клиента при открытии соединения.
|
||||
* Передаем интервал heartbeat из настроек сервера.
|
||||
* Если клиент не отправляет heartbeat в указанный интервал, его можно отключить.
|
||||
*/
|
||||
Client client = new Client(socket, this.settings.heartbeatInterval);
|
||||
socket.setAttachment(client);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
|
||||
/**
|
||||
* Настраиваем планировщик для проверки активности клиентов.
|
||||
*/
|
||||
this.inactivityShedulerTweaker();
|
||||
|
||||
int port = this.getPort();
|
||||
System.out.println("\u001B[32mServer started at x.x.x.x:" + port + "\u001B[0m");
|
||||
}
|
||||
|
||||
/**
|
||||
* Планировщик для проверки активности клиентов.
|
||||
* Если планировщик обнаруживает неактивного клиента, он отключает его с соответствующим кодом ошибки.
|
||||
*/
|
||||
public void inactivityShedulerTweaker() {
|
||||
this.scheduler.scheduleAtFixedRate(() -> {
|
||||
for(WebSocket socket : this.getConnections()) {
|
||||
Client client = socket.getAttachment();
|
||||
if(!client.isAlive()) {
|
||||
client.disconnect(Failures.INACTIVITY_TIMEOUT);
|
||||
}
|
||||
}
|
||||
}, this.settings.heartbeatInterval, this.settings.heartbeatInterval, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
18
src/main/java/com/rosetta/im/protocol/Settings.java
Normal file
18
src/main/java/com/rosetta/im/protocol/Settings.java
Normal file
@@ -0,0 +1,18 @@
|
||||
package com.rosetta.im.protocol;
|
||||
|
||||
public class Settings {
|
||||
/**
|
||||
* Порт сервера
|
||||
*/
|
||||
public int port = 8881;
|
||||
/**
|
||||
* Интервал отправки heartbeat пакетов в секундах.
|
||||
* Если клиент не отправляет heartbeat пакеты в течение этого времени, сервер может считать его отключенным.
|
||||
*/
|
||||
public long heartbeatInterval = 30;
|
||||
|
||||
public Settings(int port, long heartbeatInterval) {
|
||||
this.port = port;
|
||||
this.heartbeatInterval = heartbeatInterval;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
package com.rosetta.im.protocol.packet;
|
||||
|
||||
import com.rosetta.im.protocol.Client;
|
||||
|
||||
public interface PacketExecutor {
|
||||
public void onPacketReceived(Class <? extends Packet> packet);
|
||||
public void onPacketReceived(Packet packet, Client client);
|
||||
}
|
||||
|
||||
@@ -2,20 +2,48 @@ package com.rosetta.im.protocol.packet;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
/**
|
||||
* Менеджер сетевых пакетов и их обработчиков.
|
||||
*/
|
||||
public class PacketManager {
|
||||
|
||||
private HashMap<Integer, Class<? extends Packet>> packets;
|
||||
private HashMap<Integer, Class<? extends PacketExecutor>> executors;
|
||||
|
||||
public PacketManager() {
|
||||
this.packets = new HashMap<>();
|
||||
this.executors = new HashMap<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Регистрирует пакет с указанным ID.
|
||||
* @param packetId ID пакета
|
||||
* @param packet Класс пакета
|
||||
*/
|
||||
public void registerPacket(int packetId, Class<? extends Packet> packet) {
|
||||
this.packets.put(packetId, packet);
|
||||
}
|
||||
|
||||
public boolean isPacketRegistred(Packet packet) {
|
||||
return this.packets.containsValue(packet.getClass());
|
||||
/**
|
||||
* Проверяет, зарегистрирован ли обработчик для пакета с указанным ID.
|
||||
* @param packetId ID пакета
|
||||
* @return true, если обработчик зарегистрирован, иначе false.
|
||||
*/
|
||||
public boolean hasExecutorDelegated(int packetId) {
|
||||
return this.executors.containsKey(packetId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Проверяет, поддерживается ли пакет с указанным ID.
|
||||
* @param packetId ID пакета
|
||||
* @return true, если пакет поддерживается, иначе false.
|
||||
*/
|
||||
public boolean hasPacketSupported(int packetId) {
|
||||
return this.packets.containsKey(packetId);
|
||||
}
|
||||
|
||||
public HashMap<Integer, Class<? extends PacketExecutor>> getExecutors() {
|
||||
return this.executors;
|
||||
}
|
||||
|
||||
public Integer getPacketIdByClass(Class<? extends Packet> packetClass) {
|
||||
@@ -31,5 +59,33 @@ public class PacketManager {
|
||||
return this.packets.get(packetId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Регистрирует обработчик пакета с указанным ID.
|
||||
* @param packetId ID пакета
|
||||
* @param executor Обработчик пакета
|
||||
*/
|
||||
public void registerExecutor(int packetId, Class <? extends PacketExecutor> executor) {
|
||||
if (this.executors == null) {
|
||||
this.executors = new HashMap<>();
|
||||
}
|
||||
this.executors.put(packetId, executor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Возвращает общее количество зарегистрированных обработчиков пакетов.
|
||||
* @return Количество обработчиков пакетов.
|
||||
*/
|
||||
public Integer totalExecutors() {
|
||||
return this.executors.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Возвращает общее количество зарегистрированных пакетов.
|
||||
* @return Количество пакетов.
|
||||
*/
|
||||
public Integer totalPackets() {
|
||||
return this.packets.size();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user