Как получить данные из данных сетевого пакета в Java

В C, если у вас есть определенный тип пакета, вы обычно определяете некоторую структуру и приводите char * в указатель на структуру. После этого у вас есть прямой программный доступ ко всем полям данных в сетевом пакете. Вот так :

struct rdp_header {
  int version;
  char serverId[20];
};

Когда вы получаете сетевой пакет, вы можете быстро сделать следующее:

char * packet;
// receive packet
rdp_header * pckt = (rdp_header * packet);
printf("Servername : %20.20s\n", pckt.serverId);

Этот метод действительно отлично работает для протоколов на основе UDP и позволяет очень быстро и очень эффективно анализировать и отправлять пакеты, используя очень мало кода, и тривиальную обработку ошибок (просто проверьте длину пакета). Есть ли аналогичный, такой же быстрый способ сделать то же самое в java? Или вы вынуждены использовать методы, основанные на потоках?


person christopher    schedule 12.01.2009    source источник
comment
Я думаю, что это дерьмовый способ сделать это в C ... Он пренебрегает порядком байтов. Я думаю, что этот тип пакетного ввода-вывода лучше всего выполнять в потоковом режиме или с помощью специальных функций сериализации/десериализации для каждого типа пакета (или команды/подпакета, что угодно).   -  person unwind    schedule 17.04.2009
comment
Он также игнорирует проблемы заполнения и выравнивания.   -  person unwind    schedule 17.04.2009


Ответы (6)


Считайте свой пакет в массив байтов, а затем извлеките из него нужные биты и байты.

Вот пример, без обработки исключений:

DatagramSocket s = new DatagramSocket(port);
DatagramPacket p;
byte buffer[] = new byte[4096];

while (true) {
    p = new DatagramPacket(buffer, buffer.length);
    s.receive(p);

    // your packet is now in buffer[];
    int version = buffer[0] << 24 + buffer[1] << 16 + buffer[2] < 8 + buffer[3];
    byte[] serverId = new byte[20];
    System.arraycopy(buffer, 4, serverId, 0, 20);

     // and process the rest
}

На практике вы, вероятно, получите вспомогательные функции для извлечения полей данных в сетевом порядке из массива байтов или как Tom указывает в комментариях, вы можете использовать ByteArrayInputStream(), из которого вы можете создать DataInputStream(), у которого есть методы для чтения структурированных данных из потока:

...

while (true) {
    p = new DatagramPacket(buffer, buffer.length);
    s.receive(p);

    ByteArrayInputStream bais = new ByteArrayInputStream(buffer);
    DataInput di = new DataInputStream(bais);

    int version = di.readInt();
    byte[] serverId = new byte[20];
    di.readFully(serverId);
    ...
}
person Alnitak    schedule 12.01.2009
comment
Лучше, чем вспомогательные методы, напишите класс, который имеет байтовый ввод в виде поля и читает целые числа с обратным порядком байтов и тому подобное, что имеет смысл на уровне вашего сетевого формата. java.io.DataInputStream был попыткой сделать это для конкретного случая. - person Tom Hawtin - tackline; 13.01.2009

Я не верю, что этот метод может быть реализован на Java, за исключением использования JNI и фактического написания обработчика протокола на C. Другой способ реализовать описанный вами метод - это вариантные записи и объединения, которых в Java тоже нет.

Если бы у вас был контроль над протоколом (это ваш сервер и клиент), вы могли бы использовать сериализованные объекты (включая xml), чтобы получить автоматический (но не такой эффективный во время выполнения) анализ данных, но это все.

В противном случае вы застряли с разбором потоков или массивов байтов (которые можно рассматривать как потоки).

Имейте в виду, что описанный вами метод чрезвычайно подвержен ошибкам и является источником уязвимостей безопасности для любого достаточно интересного протокола, так что это не такая уж большая потеря.

person frankodwyer    schedule 12.01.2009

Я написал кое-что, чтобы упростить такую ​​работу. Как и в большинстве задач, написать инструмент было гораздо проще, чем пытаться сделать все вручную.

Он состоял из двух классов. Вот пример того, как он использовался:

    // Resulting byte array is 9 bytes long.
    byte[] ba = new ByteArrayBuilder()

     .writeInt(0xaaaa5555) // 4 bytes
     .writeByte(0x55) //      1 byte
     .writeShort(0x5A5A) //   2 bytes
     .write( (new BitBuilder())  //     2 bytes---0xBA12                
            .write(3, 5) //     101      (3 bits value of 5)
            .write(2, 3) //        11    (2 bits value of 3)
            .write(3, 2) //          010 (...)
            .write(2, 0) //     00
            .write(2, 1) //       01
            .write(4, 2) //         0002
        ).getBytes();

Я написал ByteArrayBuilder, чтобы просто накапливать биты. Я использовал шаблон цепочки методов (просто возвращая «это» из всех методов), чтобы упростить запись нескольких операторов вместе.

Все методы в ByteArrayBuilder были тривиальны, как 1 или 2 строки кода (я просто писал все в поток вывода данных)

Это нужно для создания пакета, но разорвать его не должно быть сложнее.

Единственный интересный метод в BitBuilder:

public BitBuilder write(int bitCount, int value) {
    int bitMask=0xffffffff;  
    bitMask <<= bitCount;   // If bitcount is 4, bitmask is now ffffff00
    bitMask = ~bitMask;     // and now it's 000000ff, a great mask

    bitRegister <<= bitCount; // make room
    bitRegister |= (value & bitMask); // or in the value (masked for safety)
    bitsWritten += bitCount;
    return this;
}

Опять же, логику можно очень легко инвертировать, чтобы читать пакет вместо его создания.

edit: в этом ответе я предложил другой подход, я опубликую его как отдельный ответ, потому что он совершенно другой.

person Bill K    schedule 13.01.2009

Посмотрите на библиотеку Javolution и ее классы структур, они сделают именно то, о чем вы просите. Фактически, у автора есть именно этот пример, использующий классы Javolution Struct для управления UDP-пакетами.

person Community    schedule 17.04.2009

Это альтернативное предложение для ответа, который я оставил выше. Я предлагаю вам подумать о его реализации, потому что он будет действовать почти так же, как решение C, где вы можете выбирать поля из пакета по имени.

Вы можете начать с внешнего текстового файла примерно так:

OneByte,       1
OneBit,       .1
TenBits,      .10
AlsoTenBits,  1.2
SignedInt,    +4  

Он может указывать всю структуру пакета, включая поля, которые могут повторяться. Язык может быть настолько простым или сложным, насколько вам нужно...

Вы бы создали такой объект:

new PacketReader packetReader("PacketStructure.txt", byte[] packet);

Ваш конструктор будет перебирать файл PacketStructure.txt и сохранять каждую строку в качестве ключа хеш-таблицы, а также точное местоположение ее данных (как битовое смещение, так и размер) в качестве данных.

После того, как вы создали объект, передав ему битовую структуру и пакет, вы можете получить произвольный доступ к данным с помощью таких простых операторов, как:

int x=packetReader.getInt("AlsoTenBits");

Также обратите внимание, что этот материал был бы намного менее эффективным, чем структура C, но не настолько, как вы могли бы подумать - он все же, вероятно, во много раз эффективнее, чем вам нужно. Если все сделано правильно, файл спецификации будет проанализирован только один раз, так что вы получите только незначительный удар по одному поиску хэша и несколько двоичных операций для каждого значения, которое вы читаете из пакета, - совсем неплохо.

Исключением является случай, когда вы анализируете пакеты из высокоскоростного непрерывного потока, и даже в этом случае я сомневаюсь, что быстрая сеть может затопить даже медленный процессор.

person Bill K    schedule 13.01.2009

Короткий ответ, нет, вы не можете сделать это так просто.

Более длинный ответ: если вы можете использовать объекты Serializable, вы можете подключить свой InputStream к ObjectInputStream и использовать его для десериализации ваших объектов. Однако для этого требуется, чтобы у вас был некоторый контроль над протоколом. Это также работает проще, если вы используете TCP Socket. Если вы используете UDP DatagramSocket, вам нужно будет получить данные из пакета, а затем передать их в ByteArrayInputStream.

Если у вас нет контроля над протоколом, вы можете по-прежнему использовать описанный выше метод десериализации, но вам, вероятно, придется реализовать методы readObject() и writeObject(), а не использовать предоставленную вам реализацию по умолчанию. Если вам нужно использовать чужой протокол (например, потому что вам нужно взаимодействовать с собственной программой), это, вероятно, самое простое решение, которое вы найдете.

Кроме того, помните, что Java использует UTF-16 внутри для строк, но я не уверен, что он сериализует их таким образом. В любом случае, вы должны быть очень осторожны при передаче строк туда и обратно программам, отличным от Java.

person James    schedule 13.01.2009