无符号短字节数组

Posted

技术标签:

【中文标题】无符号短字节数组【英文标题】:Unsigned short to byte array 【发布时间】:2015-04-30 17:10:28 【问题描述】:

我必须将一个短作为无符号短发送到 TCPServer。

问题是 Java 不支持 usigned 短裤: 我尝试过的:

byte[] data = new byte[3];
short port = 5025;

data[0] = 1;
data[1] = (byte)(port & 0xff);
data[2] = (byte)((port >> 8) & 0xff);

这就是我在 C++ 中将数据转换为无符号 short 的方式

// Bytes to Short (uint16)
unsigned short port = (data[1] << 8) | data[2];

那么如何在 Java 中解决这个问题呢? (我不想更改 C++ 代码中的任何内容)

编辑:// 我的新 Java 代码:

byte[] data = new byte[3];
short port = 1151; // short or int doesn't matter in this case

ByteBuffer buffer = ByteBuffer.allocate(5);
buffer.put((byte) 1);
buffer.putShort(port);

out.write(buffer.array());

C++ 代码:(相同)

unsigned short port = (data[1] << 8) | data[2];

如果端口在 0-1151 和 16384-32767 之间,我得到了正确的端口,但为什么它不能与其他端口一起使用?

【问题讨论】:

你需要知道数字是如何工作的。 1151 = 0x047f。将其分成两个字节 04 和 7f。两者都是正数。 1152 = 0x0480。 04和80。80是负数(-128),char是一个有符号变量,因此如果调整大小,负数将被保留。 data[1] &lt;&lt; 804&lt;&lt;8 并变成 0400。很好。 data[2] 是 80(-128),但它即将与 16 位数字组合,所以它变为 FF80(带符号的 16 位中的 -128)。 0400|FF80=FF80(65408 无符号 16 位)。您必须修复 C++ 代码。它坏了。 【参考方案1】:

没关系。只需将其放在short 中即可。签名short 无关紧要; short 仍然是 16 位。

重要的是这里的字节序。如果你通过网络发送,它是大端的。

这是 ByteBuffer 的默认值,也是 Java 的所有数字基元类型的默认值。

那么,你是做什么的?对于您的特定示例,这是:

// Just for a short...
final ByteBuffer buf = ByteBuffer.allocate(3);
buf.put((byte) 1);
buf.putShort(myShort);
final byte[] contents = buf.array();
// send the byte[]

现在,如果您在ByteBuffer 中有更多.put*(),请分配任何必要的空间等。


但是。您说您不想对您的 C++ 代码进行任何更改……这不能跨体系结构移植。如果您希望在 C++ 中通过网络读取/写入 16 位值,请使用 ntohs()/htons()

(也许有比这更好的 API;我已经有一段时间没有使用 C/C++ 进行高级网络编程了)

【讨论】:

再次阅读我的答案。特别是 C++ 部分。 但我不想更改 C++ 中的任何内容,因为另一个用 C++ 编写的客户端也在连接。 是的,您是否保证所有 C++ 客户端都将使用具有相同字节序的 CPU 架构?提示:您的 C++ 程序不适用于具有不同字节序的架构。 但是,Java 没有无符号原始数字类型这一事实根本不是问题所在;你的问题是字节序。尝试将 ByteBuffer 的字节序更改为小字节序(提示:这是 .order() 方法),但这样做基本上违反了万物联网的基本规则,该规则规定必须发送所有网络数据/以大端格式接收。 ntoh*/hton*() 家族的定义并非一时兴起。 是的,我确定。在我想使用它的 PC 上,它可以工作。 :) 但我必须找到一个 java 解决方案【参考方案2】:

我的观点略有不同。 OP 正确使用位移来进入小端,因此 C++ 可移植性会很好,除非他正在处理非常规大小的字节。通信协议违背了网络上的大端约定,但有时支持遗留系统就是这样。

如果端口变量在提供的代码之外有用户,请使用 int 并只发送您想要的位,就像您在上面的 Java 示例中所做的那样。如果您要绕过那个端口,那么必须继续旋转该死的符号位,这很糟糕,迟早您会搞砸的。如果没有其他人需要使用端口,则符号无关紧要。

byte[] data = new byte[3];
int port = 5025; // short or int doesn't matter in this case

data[0] = 1;
data[1] = (byte)(port & 0xff);
data[2] = (byte)((port >> 8) & 0xff);

当回读并获得 65440 时,看起来您使用了一个字符,并且您的字节通过移位得到了符号扩展。这里有一些测试代码,你可以玩一下看看发生了什么。

#include <cstdio>

int main()

    unsigned short val = 32896;
    char hi = (char)((val >> 8) & 0xFF);
    char lo = (char)(val &0xFF);
    printf("Watch what the sign bit can do to the bytes here:\n");
    printf("Value: %d, raw in hex: %04x, Hi byte: %02x, Low byte: %02x\n", val, val, hi, lo);



    printf("This one only works if the low byte doesn't sign extend\n");
    char datas[3] = 0, hi, lo;
    unsigned short port = (datas[1] << 8) | datas[2];
    printf("Reassembled short: %u, In Hex: %04x\n", port, port);

    printf("This one works, but will not for an integer\n");
    port = (datas[1] << 8) | (datas[2] & 0xFF);
    printf("Reassembled short: %u, in Hex: %04x\n", port, port);
    unsigned int bigport = (datas[1] << 8) | (datas[2] & 0xFF);
    printf("Reassembled int: %u, in Hex: %04x\n", bigport, bigport);

    printf("With unsigned characters it just works\n");
    unsigned char datau[3] = 0, hi, lo;
    port = (datau[1] << 8) | datau[2];
    printf("Reassembled short: %u, In Hex: %04x\n", port, port);
    bigport = (datau[1] << 8) | (datau[2] & 0xFF);
    printf("Reassembled int: %u, in Hex: %04x\n", bigport, bigport);

输出:

Watch what the sign bit can do to the bytes here:
Value: 32896, raw in hex: 8080, Hi byte: ffffff80, Low byte: ffffff80
This one only works if the low byte doesn't sign extend
Reassembled short: 65408, In Hex: ff80
This one works, but will not for an integer
Reassembled short: 32896, in Hex: 8080
Reassembled int: 4294934656, in Hex: ffff8080
This one just works
Reassembled short: 32896, In Hex: 8080
Reassembled int: 32896, in Hex: 8080

那么发生了什么?

(datas[1] << 8) | datas[2]

这两个数字都必须按比例放大并进行签名,因此 0x80 变为 0xFF80。实际上,它们变成了整数,但那是另一回事了。

(0xFF80 << 8) | 0xFF80

简化为

0x8000 | 0xFF80

这就是 OR

0xFF80

AKA 65408,而不是 32896。

在这种情况下 unsigned char 是你的朋友。 Java 可能有问题,但 C++ 肯定坏了。

【讨论】:

谢谢!但我只收到端口 40975。如果我在 C++ 中打印 data[1] 和 data[2],我会得到以下信息:ffffffa1 13 C++ 中的数据数组是一种 char。是这个问题吗? 我已经把数组的typ改成了unsigned char,但是没有区别 哦,如果我在 java 中将字节设置为 0xa0 和 0x0f 一切正常。所以错误在java代码中 最好的判断方法是执行@fge 一直在询问的内容并编辑您的问题以包含更多或全部Java 输出例程和C++ 输入例程。没有它,我们就在黑暗中拍摄。您是否考虑过使用 Wireshark 或类似工具来查看数据包的内容以了解实际发送的内容?

以上是关于无符号短字节数组的主要内容,如果未能解决你的问题,请参考以下文章

如何从 Java 中的 BigInteger 获取无符号字节数组?

汇编语言的基本数据类型

Python numpy'预期无符号字节数据类型的输入数组'

如何对实际上是 int[] 的无符号字节数组进行 base64 编码

Visual C++ 6.0 中“无符号字符”数组的最大允许大小是多少?

Cuda 高效地从字节数组复制到不同大小的共享内存元素