4

I am developing an app which communicate with a C Program running on windows server, This program is developed using Visual Studio (if this information can be of any help).

The server sends me an integer through socket communication, Before sending server does following things:-

  1. Declare an int
  2. Assign it some value
  3. copy 2 bytes to a char * (say buffer), using memcpy
  4. Add some more data to that buffer
  5. Send that buffer

Now at receiving end I have java implementation, so can't use memcpy directly, I have used

short mId = java.nio.ByteBuffer.wrap(recvBuf, 0, 2).order(ByteOrder.LITTLE_ENDIAN).getShort();

which works, fine but this part of code is called in every few miliseconds, So I am trying to optimize it.. I have also used

short mId =(short)(recvBuf[0] + recvBuf[1]*128);

Which also works fine but I doubt whether it will work if the number increases in future. What is the best way to do the repeat of memcpy in java ?

I have visited this thread but that is not of much help,

EDIT I implemented following four methods which worked for me,

public class CommonMethods {
    /*
     * Returns the byte[] representation of an int in Little Endian format
     * 
     * @param value that should be converted to byte[]
     */
    public static byte[] toByteArray(int value) {
        return new byte[] { (byte) value, (byte) (value >> 8), (byte) (value >> 16), (byte) (value >> 24) };
    }

    /*
     * Returns the int in LittleEndian value of the passed byte[]
     * 
     * @param bytes is the input byte[]
     * 
     * @param offset is the offset to start
     */
    public static int getInt(byte[] bytes, int offset, int length) {
        int retValue = (bytes[offset] & 0xFF);
        byte bVal;
        for (int i = 1; i < length; i++) {
            bVal = bytes[offset + i];
            retValue |= ((bVal & 0xFF) << (8 + (8 * (i - 1))));
        }
        return retValue;
    }

    /*
     * Returns the int in BigEndian from the passed byte[]
     * 
     * @param bytes is the byte[]
     */
    public static int getIntBigEndian(byte[] bytes, int offset, int length) {
        int retValue = (bytes[offset + length - 1] & 0xFF);
        for (int i = 1; i < length; i++) {
            retValue |= ((bytes[offset + length - 1 - i] & 0xFF) << (8 + (8 * (i - 1))));
        }
        return retValue;
    }

    /*
     * Returns the byte[] representation of an int in Big Endian format
     * 
     * @param value that should be converted to byte[]
     */
    public static byte[] toByteArrayBigEndian(int value) {
        return new byte[] { (byte) (value >> 24), (byte) (value >> 16), (byte) (value >> 8), (byte) value };
    }
}
Community
  • 1
  • 1
Amit
  • 13,134
  • 17
  • 77
  • 148

2 Answers2

3

You can receive a ByteBuffer directly. This save some system calls / memory copy.

ByteBuffer buf = ByteBuffer.allocate(1024); // just allocate enough, can be reused later;
buf.reset();  // in case this is reused, you need reset()

SocketChannel channel = socket.getChannel();
channel.read(buf);

short mId = buf.order(ByteOrder.LITTLE_ENDIAN).getShort();

byte[] otherBytes = new byte[....];
buf.get(otherBytes);

Of course you need checking the actual length read. I have omit them for clarity reason. You can google "java nio blocking mode" if you want to know more.

J-16 SDiZ
  • 26,473
  • 4
  • 65
  • 84
1

This code:

short mId =(short)(recvBuf[0] + recvBuf[1]*128);

is definitely not correct. It may work with some values that you've tried, but will definitely not work with other values.

If recvBuf[1] is anything other than zero, this will give incorrect results.

If recvBuf[0] is larger than 127, this will give incorrect results.

Here's why:

  • byte in Java is signed. If the value in the byte is between 128 and 255 it will be interpreted as a negative number (which is probably not what you want)
  • multiplying recvBuf[1] by 128 is incorrect. You need to multiply by 256 in order to scale up the value correctly. Also see the above point about negative numbers

Assuming that you are writing the bytes from C in LITTLE-ENDIAN byte-order, you can do this instead:

int mId = ((recvBuf[0] & 0xFF) | ((recvBuf[1] & 0xFF) << 8));

The & 0xFF is used to mask only the low-order 8 bits of the value in case the high-order bit is set (as Java will sign-extend the byte when converting it to an int).

The << 8 is functionally equivalent to * 256, but is a bit shifting function as opposed to an arithmetic function, which is more appropriate for this use case.

Assigning the result to an int instead of a short will guarantee that values between 32768 and 65535 are correctly interpreted as positive numbers and not negative numbers.

This will work for all values between 0 and 65535. Once your number becomes bigger than that, you won't be able to put it in 2 bytes when sending it anyway. If you want to plan for the future and think you may need more than 2 bytes, then just copy all 4 bytes of your C integer and read the value in Java like this:

long mId = (long)(((recvBuf[0] & 0xFF) | ((recvBuf[1] & 0xFF) << 8) |
              ((recvBuf[2] & 0xFF) << 16) | ((recvBuf[3] & 0xFF) << 24)) & 0xFFFFFFFF);

Also be aware that Java default byte-order is BIG-ENDIAN, not LITTLE-ENDIAN.

David Wasser
  • 93,459
  • 16
  • 209
  • 274