В основном то, что вы описываете, является прокси-сервером.
На данный момент это то, что мне пришло в голову. Сообщите мне о любых сомнениях, чтобы я мог решить их, расширив ответ.
Что такое прокси-сервер?
Прокси-сервер - это сервер, который маршрутизирует входящий трафик c к другим серверам (внутренним или внешним) и действует как посредник между клиентом и конечным сервером.
Есть несколько подходов к вашей проблеме.
Подход 1: Nginx + JSON
В этом случае я бы порекомендовал вам использовать прокси-сервер, например Nginx, который использует протокол HTTP. Затем информация будет передана в виде JSON строк вместо использования сырых двоичных пакетов, что значительно упростит проблему.
Для получения дополнительной информации о NGINX:
Подробнее информация о JSON:
Подход 2: Создание собственного прокси-сервера и использование бинарных пакетов
Для прокси-части вы можете использовать Java Сокеты и класс, который распространяет соединения путем чтения и открытия пакета формируют клиента, где он указывает желаемое место назначения. Тогда у вас будет два варианта:
- Перенаправить потоки сокета (Client-Proxy) на сокет (Proxy-WantedDestination).
- Сообщите WantedDestination, чтобы открыть соединение с клиентом . (ServerSocket на клиенте и Socket на WantedDestination) Таким образом, WantedDestination будет открывать соединение сокета с клиентом вместо того, чтобы клиент открывал соединение с местом назначения Wanted.
Первый метод позволяет вам для регистрации всех входящих и исходящих данных. Второй метод позволяет сохранить WantedDestination в безопасности.
Первый метод:
Client <--> Proxy <--> WantedDestination (2 Sockets)
Второй способ:
Step 1: Client <--> Proxy
Step 2: Proxy <--> WantedDestination
Step 3: Client <---------------> WantedDestination (1 socket)
Как структурировать пакеты
Я обычно структурирую пакеты следующим образом:
- Заголовок пакета
- Длина пакета
- Полезная нагрузка пакета
- Контрольная сумма пакета
Заголовок пакета может использоваться, чтобы определить, исходит ли пакет от вашего программного обеспечения и что вы начинаете считывать данные справа position.
Длина пакета указывает, сколько байтов поток должен прочитать перед попыткой десериализации пакета в его класс оболочки. Представим, что заголовок имеет длину 2 байта и длину 3 байта. Затем, если длина указывает, что пакет имеет длину 30 байт, вы будете знать, что конец пакета - (30 - 3 - 2) = 25 bytes away
.
Полезная нагрузка пакета будет иметь переменный размер и будет содержать несколько байтов фиксированного размера в начало с указанием типа пакета. Тип пакета можно выбрать произвольно. Например, вы можете определить, что пакет типа (byte) 12
должен интерпретироваться как пакет, содержащий данные о совпадении понга.
Наконец, контрольная сумма пакета указывает сумму байтов пакета, который вы может проверить целостность пакета. Java уже предоставляет некоторые алгоритмы контрольной суммы, например CRC32
. Если Packet Checksum = CRC32(Packet header, Packet length, and Packet Payload)
, то данные не повреждены.
В конце концов, пакет представляет собой массив байтов, который может быть передан с использованием Java входных и выходных потоков. Несмотря на это, работа напрямую с байтовыми массивами обычно бывает сложной и утомительной, поэтому я бы рекомендовал вам использовать класс-оболочку для представления пакета, а затем расширить этот класс для создания других пакетов. Например:
package me.PauMAVA.DBAR.common.protocol;
import java.util.Arrays;
import java.util.zip.CRC32;
import java.util.zip.Checksum;
import static me.PauMAVA.DBAR.common.util.ConversionUtils.*;
public abstract class Packet implements Serializable {
public static final byte[] DEFAULT_HEADER = new byte[]{(byte) 0xAB, (byte) 0xBA};
private final byte[] header;
private final byte packetType;
private byte[] packetParameter;
private byte[] packetData;
private byte[] packetCheckSum;
Packet(PacketType type, PacketParameter parameter) {
this(type, parameter, new byte[0]);
}
Packet(PacketType type, PacketParameter parameter, byte[] data) {
this.header = DEFAULT_HEADER;
this.packetType = type.getCode();
this.packetParameter = parameter.getData();
this.packetData = data;
recalculateChecksum();
}
public byte[] getParameterBytes() {
return packetParameter;
}
public PacketParameter getPacketParameter() {
return PacketParameter.getByData(packetParameter);
}
public byte[] getPacketData() {
return packetData;
}
public void setParameter(PacketParameter parameter) {
this.packetParameter = parameter.getData();
recalculateChecksum();
}
public void setPacketData(byte[] packetData) {
this.packetData = packetData;
recalculateChecksum();
}
public void recalculateChecksum() {
Checksum checksum = new CRC32();
checksum.update(header);
checksum.update(packetParameter);
checksum.update(packetType);
if (packetData.length > 0) {
checksum.update(packetData);
}
this.packetCheckSum = longToBytes(checksum.getValue());
}
public byte[] toByteArray() {
return concatArrays(header, new byte[]{packetType}, packetParameter, packetData, packetCheckSum);
}
И тогда пользовательский пакет может быть:
package me.PauMAVA.DBAR.common.protocol;
import java.nio.charset.StandardCharsets;
import static me.PauMAVA.DBAR.common.util.ConversionUtils.subArray;
public class PacketSendPassword extends Packet {
private String passwordHash;
public PacketSendPassword() {
super(PacketType.SEND_PASSWORD, PacketParameter.NO_PARAM);
}
public PacketSendPassword(String passwordHash) {
super(PacketType.SEND_PASSWORD, PacketParameter.NO_PARAM);
super.setPacketData(passwordHash.getBytes(StandardCharsets.UTF_8));
}
@Override
public byte[] serialize() {
return toByteArray();
}
@Override
public void deserialize(byte[] data) throws ProtocolException {
validate(data, PacketType.SEND_PASSWORD, PacketParameter.NO_PARAM);
PacketParameter packetParameter = PacketParameter.getByData(subArray(data, 3, 6));
if (packetParameter != null) {
super.setParameter(packetParameter);
}
byte[] passwordHash = subArray(data, 7, data.length - 9);
super.setPacketData(passwordHash);
this.passwordHash = new String(passwordHash, StandardCharsets.UTF_8);
}
public String getPasswordHash() {
return passwordHash;
}
}
Отправить пакет в потоке будет так же просто, как:
byte[] buffer = packet.serialize();
dout.write(buffer);
Вы можете взглянуть на небольшой протокол, который я разработал для автоматического перезагрузчика сервера Bukkit здесь .
Обратите внимание, что этот метод потребует от вас преобразования между разными типами данных и байтовые массивы, поэтому вам потребуется хорошее понимание чисел c и представления символов в двоичном формате.