Как получить данные из сетевых пакетов данных в Java - PullRequest
5 голосов
/ 13 января 2009

В 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 такой же быстрый способ сделать то же самое? Или вы вынуждены использовать потоковые методы?

Ответы [ 6 ]

3 голосов
/ 13 января 2009

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

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

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
}

На практике вы, вероятно, в конечном итоге получите вспомогательные функции для извлечения полей данных в сети порядке из байтового массива или как Том в комментариях, которые вы можете использовать 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);
    ...
}
2 голосов
/ 13 января 2009

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

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

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

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

1 голос
/ 17 апреля 2009

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

1 голос
/ 13 января 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, чтобы просто накапливать биты. Я использовал шаблон цепочки методов (просто возвращая «this» из всех методов), чтобы было проще написать кучу операторов вместе.

Все методы в 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: в этом ответе я предложил другой подход, я опубликую его как отдельный ответ, потому что он совершенно другой.

0 голосов
/ 14 января 2009

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

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

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

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

0 голосов
/ 14 января 2009

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

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

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

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

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

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

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

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

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

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

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

...