Как да създадете пакет с данни с неподписани int в JAVA

Работя върху проект, в който пиша java клиент, комуникиращ с услуга, използваща TCP през SSL връзка. Услугата записва всички съобщения в полетата на клиента в мрежов ред на байтове (big endian) и кодира текст в UTF-8. Клиентът прави същото.

Форматът на съобщението, което клиентът трябва да създаде, за да изпрати до услугата, изглежда така

 |    0      |    1     |     2      |     3      |
 |------------------------------------------------|
 |    Header Version    |       Message Type      |
 |------------------------------------------------|
 |               Message Length                   |
 |------------------------------------------------|
 |               Initial Timestamp                |
 |------------------------------------------------|
 |   Future use         |   Request Flags         |
 |------------------------------------------------|
  1. Версия на заглавката - 16 бита без знак - Стойността винаги е 1
  2. Тип на съобщението - 16 бита без знак
  3. Дължина на съобщението - 32 бита без знак
  4. Първоначално времево клеймо - 32 бита без знак
  5. Запазено - 16 бита без знак - Винаги зададено на 0
  6. Флаг за заявка - битове [16]

Как да създам тази структура от данни в Java с неподписани int??

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer ;


public class NullMessage {

private final int BASE_HEADER_SIZE = 7;

int _headerVersion;
int _messageType;
long _messageLength;
byte[] _options;


public NullMessage( byte[] format ) {

    if (format.length  == BASE_HEADER_SIZE) {

        _headerVersion = (int)( ( ( format[0] << 8 ) & 652080 ) | ( format[1 ] & 255 ));

        _messageType = (int)( ( ( format[2] << 8 ) & 652080 ) | ( format[3] & 255 ));

        _messageLength = (long)(((format[4]<<24)&4278190080l)
                |((format[5]<<16)&16711680l)
                |((format[6]<<8)&65280)
                |(format[7]&255));
    }
    else {
        throw new RuntimeException("Error in creating NullMessage format");
    }

}

public int length() {
    return BASE_HEADER_SIZE + (this._options == null ? 0 : this._options.length );
}


public NullMessage( int headerVersion, int messageType, long messageLength) {
    this._headerVersion = headerVersion;
    this._messageType = messageType;
    this._messageLength = messageLength;
}

public byte[] toByteArray() {

    ByteArrayOutputStream out = new ByteArrayOutputStream();

    try {
            out.write( getHeader() );
            if ( _options != null ){
                out.write( this._options );
            }
        } catch (IOException e) {
            System.out.println( e.toString() ) ;
        }

        return out.toByteArray()  ;

}

// Generate the NullMessage in a byte array format
public byte[] getHeader() {

    byte[] nullMessage = new byte[BASE_HEADER_SIZE];
    nullMessage[0] = (byte)((_headerVersion>>8)&255);
    nullMessage[1] = (byte)((_headerVersion&255));
    nullMessage[2] = (byte)((_messageType>>8)&255);
    nullMessage[3] = (byte)((_messageType&255));
    nullMessage[3] = (byte)((_messageLength>>24)&255);
    nullMessage[4] = (byte)((_messageLength>>16)&255);
    nullMessage[5] = (byte)((_messageLength>>8)&255);
    nullMessage[6] = (byte)((_messageLength&255));

    return nullMessage;
    }


public byte[] getOption() {

    return _options;
}

public int getHeaderVersion() {
    return this._headerVersion;
}

public void setHeaderVersion(int version) {
    this._headerVersion = version;
}

public int getMessageType() {
    return this._messageType;
}

public void setMessageType(int messageType ) {
    this._messageType = messageType;
}

public long getMessageLength() {
    return this._messageLength;
}

public void setMessageLength( long messageLength ) {

    this._messageLength = messageLength;
}

public void print() {
    byte[] head = getHeader();
    for (int j=0; j<head.length; j++) {
        System.out.format("%08X ", head[j]);
    }
    System.out.println();
}

public static void main(String args[]){
    NullMessage test = new NullMessage(1, 0, 0);
    test.print();


    byte[] array = test.toByteArray();

    for (int i=0;i<array.length;i++) {
       System.out.println("myByte["+i+"]:"+ Integer.toBinaryString(array[i] & 255 | 256).substring(1));

    }
}

}


person user4771960    schedule 13.04.2015    source източник
comment
Какво ще кажете за съдържанието на съобщението? Очаквате ли да получите отговор на съобщения в същия формат? Какъв точно е форматът на времевия печат?   -  person RealSkeptic    schedule 13.04.2015
comment
Полето timestamp е стойност на unix timestamp (брой секунди от 1 януари 1970 г.   -  person user4771960    schedule 13.04.2015
comment
Форматът на съобщението, което се връща, е различен. Все още изглаждаме подробностите за формата на обратното съобщение   -  person user4771960    schedule 13.04.2015
comment
Сега, след като сте публикували кода, обяснете проблема, който имате. Пример: Примерни входни данни, очаквани спрямо действителните резултати.   -  person Shar1er80    schedule 31.07.2015
comment
Вижте моя актуализиран отговор за това как да поставите байтовете в ByteBuffer и да извлечете стойностите в заглавката си.   -  person Shar1er80    schedule 31.07.2015


Отговори (3)


Използвайте методите от DataInputStream и DataOutputStream.

person user207421    schedule 13.04.2015

Когато едно поле трябва да бъде "неподписано" за изходящо съобщение, тогава:

  • Страната, която трябва да го интерпретира като неподписан, е сървърът.
  • От страна на клиента просто трябва да се уверите, че правите изчисленията си по такъв начин, че да не превишавате броя.

За размера на съобщението, например, ако вашият конкретен клиент никога не е проектиран да изпраща много дълги съобщения (т.е. не по-дълги от Integer.MAX_VALUE), например, ако винаги изпраща добре познати полезни данни с ограничена дължина, тогава можете всъщност използвайте обикновен int, когато го изчислявате.

Ако е възможно да изпраща много дълги съобщения, тогава трябва да използвате long за изчисляване на размера (напр. long calculatedSize). След това проверете дали не надвишава капацитета на цяло число със знак, напр.:

if ( calculatedSize & 0xffffffffL != calculatedSize ) {
    throw new InvalidMessageException();
}

(InvalidMessageException не е съществуващ тип. Просто тип изключение, което ще създадете за тази цел).

И тогава можете да използвате (int)calculatedSize за действителните си данни. Това просто премахва най-значимите 4 байта, за които така или иначе сте се уверили, че са нула, оставяйки ви с най-малко значимите 4 бита.

Фактът, че Java интерпретира това цяло число като знак, не е от значение, защото единствената употреба, която ще използвате, е да запишете неговите байтове в съобщението.

Същото важи и за полето за времеви печат. Тук е много вероятно да се наложи да надхвърлите капацитета на signed int (който ще се счупи до 2038 г.). Така че работете с long. Всъщност използвайте обичайния идиом на Java за милисекунди от епохата и след това, когато трябва да създадете заглавката на съобщението, разделете на 1000 и направете същата проверка и същото преобразуване, както показах по-горе.

Общи съвети:

  • Вероятно ще направите най-добре да проектирате съобщението си като клас, който има всички различни константи (версия на заглавка, типове съобщения, бъдеща употреба, различни флагове) и полета за екземпляр за времевия печат и действителния полезен товар на съобщението. Не се притеснявайте за знака на вашите константи. Те не подлежат на изчисления и затова знакът им е без значение.
  • Този клас трябва да има метод за създаване на заглавка на съобщение, която ще използва ByteBuffer (което ви позволява да зададете конкретния ред на байтовете) и ще постави различните полета в него, някои директно от константите, както и дължината на съобщението и времевия печат изчислено от полетата, като се използва putInt() за вашите 4-байтови цели числа и putWord() за вашите 2-байтови цели числа.
  • В случай, че сте планирали да работите с обикновен Socket(), трябва да помислите за работа с канали, тъй като те работят много добре с ByteBuffer обекти.
person RealSkeptic    schedule 13.04.2015
comment
Мисля, че не съм много сигурен за ByteBuffer. Използвах ByteArray. Вижте приложения код. Това за различен формат на съобщението (нулево съобщение) - person user4771960; 31.07.2015
comment
@user4771960 Този код е твърде сложен и нечетлив. ByteBuffer би бил много по-добър, тъй като има методи специално за тази цел. И ако имате различни типове съобщения с обща информация, това вероятно е индикация, че трябва да създадете йерархия на типове. - person RealSkeptic; 31.07.2015

Неподписаните типове данни не съществуват в Java. Това, което направих като заместител на unsigned, беше да използвам следващия по-висок тип данни.

public class MessageFormat
{
    // int covers the range of an unsigned short (16 bits)
    public int HeaderVersion;

    public int MessageType;

    // long covers the range of an unsigned int (32 bits)
    public long MessageLength

    public long InitialTimestamp;

    public int Reserved;

    public int RequestFlag;
}

Тъй като говорите за DataPackets от сокети, използвах класа ByteBuffer, за да конвертирам байтови масиви в моите полета и обратно.

Ето пример за ByteBuffer:

byte[] arr = { 0x00, 0x01 };
ByteBuffer wrapped = ByteBuffer.wrap(arr); // big-endian by default
short num = wrapped.getShort(); // 1

ByteBuffer dbuf = ByteBuffer.allocate(2);
dbuf.putShort(num);
byte[] bytes = dbuf.array(); // { 0, 1 }

Сега тромавата част... Начинът, по който оценявахме байтовете беше от 0 до 255, Java оценяваше байт от -127 до +127. Така че това, което трябваше да направя в тази ситуация, беше И байтовете срещу 0xFF (255), за да мога да ги оценя като 0 до 255 вместо -127 до +127.

Ето пример за вземане на short (2 байта) и повишаването му на int, така че да можем да го накараме да представлява кратък диапазон без знак от 0 - 65535

byte[] bytes = { (byte)0xFF, (byte)0xFF }; 
System.out.println(Arrays.toString(bytes));

ByteBuffer bb = ByteBuffer.wrap(bytes);
// As a signed short
System.out.println(bb.getShort());

// Reset back to the beginning
bb.position(0);

// As an "unsigned short", but really an int
System.out.println(bb.getShort() & 0xFFFF);

Изход:

[-1, -1]
-1
65535
person Shar1er80    schedule 13.04.2015
comment
Значи казвате, че трябва да създам клас, който да моделира моя формат на съобщение? - person user4771960; 13.04.2015
comment
Да... Всяко изпратено/получено съобщение би имало инстанциран този обект или за кодиране/декодиране с помощта на класа ByteBuffer за преминаване към/от стойности към байтове и обратно. - person Shar1er80; 13.04.2015
comment
Измина известно време, откакто зададох този въпрос и тепърва започвам с това. - person user4771960; 29.07.2015
comment
@user4771960 Поддържайте връзка за това как това може или не може да работи за вас. - person Shar1er80; 29.07.2015
comment
Мисля, че се доближавам до това, което искам. поставен код по-горе - person user4771960; 31.07.2015
comment
@user4771960 Публикувайте кода, който опитахте във вашия въпрос. Не като редакция на моя отговор - person Shar1er80; 31.07.2015
comment
съжалявам, че съм нов в това - person user4771960; 31.07.2015