bassprodukt
bassprodukt

Reputation: 121

JAVA - Working with a custom socket protocol

I'm trying to connect via sockets with a C application which expects a certain header and data body, however, I'm getting a bunch of issues when trying to comply with that format, it should be like this:

Header:
- 2 bytes for packet start.
- 4 bytes that indicate the packet size plus header.
- 1 byte for packet type.

Body:
- 4 bytes that indicate quantity of params.

One block of the following for each parameter:
- 4 bytes for parameter longitude.
- X Bytes for message content.
- 1 byte for escape char.

Here's an example of what the server is expecting:

[%%][13 00 00 00][0][12 00 00 00][0b 00 00 00][OPERATIONPERFORMED\0]

The problem I'm having is that when trying to send the information via PrintWriter, the other end is getting the hex values as ASCII chars instead of an actual value. I've already tried with ByteBuffer, DataOutputStream and several options, but still they're coming out in that format or even weirder.

From what I'm investigating it seems the endian is inverted, so they get the right bytes but in the wrong order.

The implementation I'm trying now is the following:

  DataOutputStream stream = new DataOutputStream(socket.getOutputStream());
    PrintWriter writer = new PrintWriter(socket.getOutputStream()); 

    BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));

    // Header
    stream.writeChar(37);
    stream.writeChar(37); 
    stream.writeInt(26);
    stream.writeChar('\\0');

    stream.writeInt(1);
    stream.writeInt(11);

    stream.writeUTF("SET_PROBLE\\0");


    stream.flush();

    StringBuilder sb = new StringBuilder();

    boolean state = true;

    while (state) {
        if (in.ready()) {
            sb.append((char) in.read());
        }
        else {
            if (sb.length() > 0) {
                state = false;
            }
        }
    }

    System.out.println(sb.toString().trim());

}

Can you help me out?

Thanks a lot in advance!

Upvotes: 3

Views: 2984

Answers (1)

Guido Simone
Guido Simone

Reputation: 7952

Tomasz is, of course, correct about using OutputStream directly and sending bytes. I would like to expand on this and spell out why this is true.

Let's review the bytes your server is expecting. To begin with, you need two bytes each of value 37 (ascii %%). DataOutputStream.writeChar is not going to give you that. As the docs clearly state it Writes a char to the underlying output stream as a 2-byte value, high byte first.

stream.writeChar(37);
stream.writeChar(37);

will result in 4 bytes: 0 37 0 37 (or 00 25 00 25 hex). Similarly you need several 4-byte integers. Note that based on your server sample, these integers are serialized in little-endian mode (low byte first). So DataOutputStream.writeInt is not going to help because it Writes an int to the underlying output stream as four bytes, high byte first.

stream.writeInt(26)

will result in 4 bytes - 0 0 0 26 (or 00 00 00 1A hex) when you need (1A 00 00 00). You get the idea. This just is not going to work.

Here is some sample code showing how you might fix these problems, by abandoning DataOutputStream and working with OutputStream directly.

public static void myWriteInt(OutputStream out, int value) throws IOException
{
    out.write(value % 0xff);
    out.write( (value >> 8) % 0xff);
    out.write((value >> 16) % 0xff);
    out.write((value >> 24) % 0xff);

}
public static void main(String[] args) throws Exception
{

    ByteArrayOutputStream out = new ByteArrayOutputStream();
    // Replace with
    // OutputStream out = socket.getOutputStream()

    out.write((byte) 37);
    out.write((byte) 37);

    myWriteInt(out, 26);
    out.write((byte) 0);

    myWriteInt(out, 1);
    myWriteInt(out, 11);

    out.write("SET_PROBLE\0".getBytes("UTF-8"));

    System.out.println(DatatypeConverter.printHexBinary(out.toByteArray()));

To avoid dealing with sockets I am writing to a ByteArrayOutputStream so we can print out the hex values and show that it matches your requirements. Just replace with the socket output stream.

This code prints out the following (I added some spaces for clarity)

2525 1A000000 00 01000000 0B000000 5345545F50524F424C4500

The last chunk is SET_PROBLE followed by a 0.

Also I have included a custom little-endian int marshaller (myWriteInt), but there are other approaches - such as using a temporary ByteBuffer set to little endian mode.

Hope this helps.

Upvotes: 2

Related Questions