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, 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, 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, ECITag> getEciTags() { return this.eciTags; } /** * Устанавливает уникальный идентификатор клиента. * @param clientId Идентификатор клиента. */ public void setClientId(String clientId) { this.clientId = clientId; } /** * Устанавливает данные для клиента по указанному ключу. * @param key Ключ данных. * @param value Значение данных. */ public void addTag(Class 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 tagClass : this.eciTags.keySet()) { if (this.clientIndexer != null) { this.clientIndexer.removeTagIndex(this, tagClass); } } this.eciTags.clear(); } /** * Удаляет данные клиента по указанному ключу. * @param Тип данных. * @param tagClass Класс данных для удаления. */ public void removeTag(Class tagClass) { this.addTag(tagClass, null); } /** * Переиндексирует тег клиента в индексе клиентов. * @param tagClass * @param eciTag */ public void reindexTag(Class tagClass, T eciTag) { this.clientIndexer.reindexTag(this, tagClass, eciTag); } /** * Получает данные клиента по указанному ключу. * @param key Ключ данных. * @return Значение данных. */ @SuppressWarnings("unchecked") public T getTag(Class eciTagClass) { return (T) this.eciTags.get(eciTagClass); } /** * Проверяет наличие данных клиента по указанному ключу. * @param key Ключ данных. * @return true если данные существуют, иначе false. */ public boolean hasTag(Class 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(); } }