284 lines
9.3 KiB
Java
284 lines
9.3 KiB
Java
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();
|
||
}
|
||
|
||
}
|