Инициализация протокола 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();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user