Files
rosetta-wss/src/main/java/io/orprotocol/client/Client.java

284 lines
9.3 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package io.orprotocol.client;
import java.util.HashMap;
import org.java_websocket.WebSocket;
import io.orprotocol.BaseFailures;
import io.orprotocol.ProtocolException;
import io.orprotocol.Server;
import io.orprotocol.ServerFailures;
import io.orprotocol.frame.FrameEncoder;
import io.orprotocol.index.ClientIndexer;
import io.orprotocol.packet.Packet;
import io.orprotocol.packet.PacketManager;
import io.orprotocol.util.StringUtil;
/**
* Клиент, подключенный к серверу.
*/
public class Client {
public WebSocket socket;
public String clientId;
/**
* Любые данные, связанные с клиентом.
*/
public HashMap<Class<? extends ECITag>, ECITag> eciTags;
/**
* Интервал отправки heartbeat пакетов в миллисекундах.
*/
public long heartbeatInterval = 0;
/**
* IP-адрес клиента
*/
private String ipAddress;
/**
* Время последнего полученного heartbeat в миллисекундах.
*/
private volatile long lastHeartbeatTime;
private ClientIndexer clientIndexer;
private PacketManager packetManager;
/**
* Версия, которую поддерживает клиент
*/
private int version;
/**
* Создает нового клиента с указанным сокетом.
* Этот метод используется внутри протокола для управления подключениями клиентов.
* @param socket Веб-сокет клиента.
*
*/
public Client(WebSocket socket, long heartbeatInterval, Server server) {
this.socket = socket;
this.clientId = StringUtil.randomString(32);
this.eciTags = new HashMap<Class<? extends ECITag>, ECITag>();
this.heartbeatInterval = heartbeatInterval;
this.lastHeartbeatTime = System.currentTimeMillis();
this.clientIndexer = server.getClientIndexer();
this.packetManager = server.getPacketManager();
}
/**
* Устанавливает версию клиента
* @internal
* @param version
*/
public void setVersion(int version) {
this.version = version;
}
/**
* Получает версию клиента
* @return
*/
public int getVersion() {
return this.version;
}
/**
* Проверяет жив ли клиент на основе времени последнего heartbeat.
* Если с момента последнего heartbeat прошло больше, чем указанный интервал, клиент считается неактивным.
*
* Для того чтобы исключить сетевые задержки, проверка умножает интервал на 2.
* @return
*/
public boolean isAlive() {
return (System.currentTimeMillis() - this.lastHeartbeatTime) <= ((this.heartbeatInterval * 1000) * 2);
}
/**
* Обновляет время последнего полученного heartbeat на текущее время.
*/
public void updateHeartbeat() {
this.lastHeartbeatTime = System.currentTimeMillis();
}
/**
* Получает IP адрес клиента
* @return адрес
*/
public String getIpAddress() {
return this.ipAddress;
}
/**
* Устанавливает IP адрес клиента
* @param ipAddress адрес
*/
public void setIpAddress(String ipAddress){
this.ipAddress = ipAddress;
}
/**
* Получает уникальный идентификатор клиента.
* @return Идентификатор клиента.
*/
public String getClientId() {
return clientId;
}
/**
* Получает данные, связанные с клиентом.
* @return Данные клиента.
*/
public HashMap<Class<? extends ECITag>, ECITag> getEciTags() {
return this.eciTags;
}
/**
* Устанавливает уникальный идентификатор клиента.
* @param clientId Идентификатор клиента.
*/
public void setClientId(String clientId) {
this.clientId = clientId;
}
/**
* Устанавливает данные для клиента по указанному ключу.
* @param key Ключ данных.
* @param value Значение данных.
*/
public <T extends ECITag> void addTag(Class<T> tagClass, T eciTag) {
if (eciTag == null) {
this.eciTags.remove(tagClass);
if (this.clientIndexer != null) {
this.clientIndexer.removeTagIndex(this, tagClass);
}
} else {
this.eciTags.put(tagClass, eciTag);
if (this.clientIndexer != null) {
this.clientIndexer.indexTag(this, tagClass, eciTag);
}
}
}
/**
* Очищает все ECI теги клиента и удаляет его из индекса клиентов.
*/
public void clearTags() {
for (Class<? extends ECITag> tagClass : this.eciTags.keySet()) {
if (this.clientIndexer != null) {
this.clientIndexer.removeTagIndex(this, tagClass);
}
}
this.eciTags.clear();
}
/**
* Удаляет данные клиента по указанному ключу.
* @param <T> Тип данных.
* @param tagClass Класс данных для удаления.
*/
public <T extends ECITag> void removeTag(Class<T> tagClass) {
this.addTag(tagClass, null);
}
/**
* Переиндексирует тег клиента в индексе клиентов.
* @param tagClass
* @param eciTag
*/
public <T extends ECITag> void reindexTag(Class<T> tagClass, T eciTag) {
this.clientIndexer.reindexTag(this, tagClass, eciTag);
}
/**
* Получает данные клиента по указанному ключу.
* @param key Ключ данных.
* @return Значение данных.
*/
@SuppressWarnings("unchecked")
public <T extends ECITag> T getTag(Class<? extends ECITag> eciTagClass) {
return (T) this.eciTags.get(eciTagClass);
}
/**
* Проверяет наличие данных клиента по указанному ключу.
* @param key Ключ данных.
* @return true если данные существуют, иначе false.
*/
public boolean hasTag(Class<? extends ECITag> eciTagClass) {
return this.eciTags.containsKey(eciTagClass);
}
/**
* Получает веб-сокет клиента.
* @return Веб-сокет.
*/
public WebSocket getSocket() {
return socket;
}
/**
* Отключает клиента с указанным кодом.
* @param code Код отключения.
*/
public void disconnect(int code) {
this.socket.close(code);
}
/**
* Отключает клиента с указанным кодом отказа.
* @param code Код отказа.
*/
public void disconnect(BaseFailures code) {
this.disconnect(code.getCode());
}
/**
* Отключает клиента с неизвестной причиной.
*/
public void disconnect() {
this.disconnect(ServerFailures.UNKNOWN_FAILURE);
}
/**
* Отправляет пакет клиенту.
* @param packet Пакет для отправки.
*/
public void send(Packet packet) throws ProtocolException {
if(!this.socket.isOpen()){
return;
}
Integer packetId = this.packetManager.getPacketIdByClass(packet.getClass());
if(packetId == null) {
throw new ProtocolException("Unknown packet class: " + packet.getClass().getName());
}
packet.packetId = packetId;
/**
* Кодируем пакет в байты для отправки клиенту, c версии сервера (latest) до версии клиента,
* для дальнейшей отправки клиенту
*/
FrameEncoder frameEncoder = new FrameEncoder(packet, this, this.packetManager);
byte[] encodedData = frameEncoder.encode();
/**
* Отправляем закодированные данные клиенту (уже закодированы под его версию)
*/
this.socket.send(encodedData);
}
/**
* Проверяем схожесть двух Client
* @param client клиент
* @return true если это один и тот же клиент, false если нет
*/
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
Client client = (Client) obj;
return this.clientId != null && this.clientId.equals(client.clientId);
}
@Override
public int hashCode() {
return this.clientId == null ? 0 : this.clientId.hashCode();
}
}