【问题标题】:How to Create a Data Packet with Unsigned ints in JAVA如何在 JAVA 中使用无符号整数创建数据包
【发布时间】:2015-06-19 01:46:01
【问题描述】:

我正在开发一个项目,我正在编写一个 Java 客户端,该客户端使用 TCP 通过 SSL 连接与服务进行通信。该服务以网络字节顺序(大端)将所有消息写入客户端字段,并以 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. 请求标志 - bits[16]

如何在 Java 中使用无符号整数创建这种数据结构?

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));

    }
}

}

【问题讨论】:

  • 邮件内容呢?您是否希望收到相同格式的回复消息?时间戳格式到底是什么?
  • 时间戳字段是一个 unix 时间戳值(自 1970 年 1 月 1 日以来的秒数。
  • 返回的消息格式不同。仍在整理返回消息格式的细节
  • 既然您已经发布了代码,请解释您遇到的问题。示例:样本输入、预期输出与实际输出。
  • 查看我关于如何将字节放入 ByteBuffer 并将值提取到标头中的更新答案。

标签: java arrays byte unsigned


【解决方案1】:

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

【讨论】:

  • 所以你是说我应该创建一个类来模拟我的消息格式?
  • 是...发送/接收的每条消息都将实例化此对象,以使用 ByteBuffer 类进行编码/解码,以将值转换为字节,反之亦然。
  • 我问这个问题已经有一段时间了,我才刚刚开始。
  • @user4771960 保持联系,了解这可能对您有用,也可能对您无效。
  • 我想我已经接近我想要的了。上面粘贴代码
【解决方案2】:

使用DataInputStreamDataOutputStream的方法。

【讨论】:

    【解决方案3】:

    当一个字段应该是“未签名”的传出消息时,那么:

    • 需要将其解释为未签名的一方是服务器。
    • 在客户端,您只需确保以不会溢出数字的方式进行计算。

    对于消息大小,例如,如果您的特定客户端设计为从不发送很长的消息(即不超过Integer.MAX_VALUE),例如,如果它总是发送众所周知的长度限制消息有效负载,那么你在计算时实际上可以使用一个普通的int

    如果可以想象它会发送很长的消息,那么您应该使用long 来计算大小(例如long calculatedSize)。然后检查它是否不超过有符号整数的容量,例如:

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

    InvalidMessageException 不是现有类型。只是您将为此创建的异常类型)。

    然后您可以将(int)calculatedSize 用于您的实际数据。这只是删除了最重要的 4 个字节,无论如何您都确保它为零,留下最不重要的 4 个字节。

    Java 将此整数解释为有符号这一事实无关紧要,因为您唯一的用途是将其字节写入消息。

    时间戳字段也是如此。在这里,您很可能需要超出已签名的 int 容量(到 2038 年将突破)。所以使用long。实际上,使用自 epoch 以来的常用 Java 习惯用法 milliseconds,然后当您需要创建消息头时,除以 1000,并执行与我展示的相同的检查和相同的转换以上。

    一般建议:

    • 您最好将消息设计为一个类,该类包含所有各种常量(标头版本、消息类型、未来使用、各种标志)以及时间戳和实际消息有效负载的实例字段。不要担心常量的符号。它们不受计算的影响,因此它们的符号无关紧要。
    • 这个类应该有一个创建消息头的方法,它将使用ByteBuffer(它允许您设置特定的字节顺序),并将各种字段放入其中,一些直接来自常量,还有根据字段计算的消息长度和时间戳,使用 putInt() 表示 4 字节整数,putWord() 表示 2 字节整数。
    • 如果您打算使用普通的 Socket(),则应考虑使用通道,因为它们与 ByteBuffer 对象配合得很好。

    【讨论】:

    • 我想我对 ByteBuffer 不太确定。我使用了一个字节数组。见附件代码。它为不同的消息格式(Null Message)
    • @user4771960 该代码太复杂且难以阅读。 ByteBuffer 会好很多,因为它有专门用于此目的的方法。如果你有不同的消息类型和公共信息,这可能表明你应该创建一个类型层次结构。
    猜你喜欢
    • 2020-01-20
    • 2014-10-22
    • 2014-03-07
    • 2015-09-01
    • 2011-05-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多