Reading byte stream returned from JavaEE server

We have a JavaEE server and servlets serving data for mobile clients (first JavaME, now iPhone coming soon). The servlet writes data using the following code:

DataOutputStream dos = new DataOutputStream(out);

dos.writeInt(someInt);

dos.writeUTF(someString);

      

... etc.

This data is returned to the client as bytes in the HTTP response block to reduce the number of bytes transferred.

In an iPhone application, the response payload is loaded into an NSData object. Now, after spending hours and hours trying to figure out how to read data in an Objective-C application, I'm almost ready to give up as I haven't found a good way to read data in NSInteger and NSString (as per the above protocol)

Does anyone have pointers on how to read data from a binary protocol written by a java application? Any help is greatly appreciated!

Thank!

+1


source to share


4 answers


You will need to do the demarcation yourself; fortunately it's pretty straightforward. Java DataOutputStream

class writes integers in big-endian (network) format. So, to split an integer, we grab 4 bytes and unpack them into a 4 byte integer.

For UTF-8 strings, DataOutputStream

first writes a 2-byte value indicating the number of bytes to follow. We read this and then read the subsequent bytes. Then, to decode the string, we can use the method NSString

initWithBytes:length:encoding:

like so:



NSData *data = ...;  // this comes from the HTTP request
int length = [data length];
const uint8_t *bytes = (const uint8_t *)[data bytes];

if(length < 4)
    ; // oops, handle error

// demarshall the big-endian integer from 4 bytes
uint32_t myInt = (bytes[0] << 24) | (bytes[1] << 16) | (bytes[2] << 8) | (bytes[3]);
// convert from (n)etwork endianness to (h)ost endianness (may be a no-op)
// ntohl is defined in <arpa/inet.h>
myInt = ntohl(myInt);

// advance to next datum
bytes += 4;
length -= 4;

// demarshall the string length
if(length < 2)
    ; // oops, handle error
uint16_t myStringLen = (bytes[0] << 8) | (bytes[1]);
// convert from network to host endianness
myStringLen = ntohs(myStringLen);
bytes += 2;
length -= 2;

// make sure we actually have as much data as we say we have
if(myStringLen > length)
    myStringLen = (uint16_t)length;

// demarshall the string
NSString *myString = [[NSString alloc] initWithBytes:bytes length:myStringLen encoding:NSUTF8StringEncoding];
bytes += myStringLen;
length -= myStringLen;

      

You can (and probably should) write functions for the unmarshall, so you don't have to repeat this code for every field you want to use for the unmarshall. Also, be careful buffer overflow ... You are processing data sent over the network, which you must always trust. Always check your data and always check the buffer length.

+2


source


The main thing is to understand the format of the binary data. It doesn't matter what is written as long as you know what bytes mean.

So your docs for DataOutputStream are your best bet. They indicate all (hopefully) what the binary data will look like.



Then I would try in principle to come up with a class on the iPhone that would read the same format in the appropriate data structure. I don't know Objective C at all, but I'm sure it can't be too hard to read 4 bytes, know that the first byte is the most significant (and so on) and does the appropriate bit-twiddling to get the integer kind of correct. (Basically read a byte, shift it left 8, read the next byte and add it to the result, shift the rest of the 8 bits, etc.). There may be better ways to do this, but get what works first.When you have unit tests around it all, you can go for optimizing it.

+1


source


Don't forget that Objective-C is just C in a pretty dress - and C is great for this kind of bit creepy. To a large extent, you should just define a C structure that looks like your data, and cast a pointer to your data into a pointer to that structure. Now, which types to use, and if you need to swap bytes, will depend on how Java creates this stream; that you will need to spend time on Java documentation.

In essence, it is a designer scent. You are having this problem because you have invalidated the assumptions about your client platform. If that's an option, I would recommend that you suggest a second, more portable interface for the same functionality (just adding Wi-Fi wrappers, thank you ML or whatever should be enough). This will save you time if you are porting to another platform that does not use Java.

0


source


Carl, if you really can't change what the server provides, have a look at this class . This should be the pointer you are looking for. However: the idea of ​​using Java's native serialization as a transport format doesn't seem like a good idea. My first choice would be JSON . If it was too big, I would rather use something like Thrift or Protobuffers . They also provide binary serialization - but in different languages. (There is also old ASN1 - but it hurts)

0


source







All Articles