Reputation: 1930
I've been fighting with this all day. I need to send Strings (JSON) between a C# server and a Java client. I have to have the first 4 bytes (always 4 bytes) as the length of the message (the header so we know how long the rest of the message is) and then the body of the message. The streams stay open for the duration of the app's life. Personally I would have just delimited each message with a '\n' and then used readLine()'s but the client NEEDS it this way.
I need the C# side to send and receive these messages as well as the Java side. Not too sure how to encode and decode everything.
Some of the bits I've been playing with:
C# send
byte[] body = Encoding.ASCII.GetBytes(message);
byte[] header = BitConverter.GetBytes((long) body.Length);
foreach (byte t in header)
{
networkStream.WriteByte(t);
}
foreach (byte t in body)
{
networkStream.WriteByte(t);
}
I didn't get to C# receive yet. Java send:
byte[] dataToSend = data.getBytes();
byte[] header = ByteBuffer.allocate(4).putInt(dataToSend.length).array();
ByteArrayOutputStream output = new ByteArrayOutputStream();
output.write(header);
output.write(dataToSend);
output.writeTo(outputStream);
Java receive:
byte[] header = new byte[4];
int bytesRead;
do {
Debug.log("TCPClient- waiting for header...");
bytesRead = reader.read(header);
ByteBuffer bb = ByteBuffer.wrap(header);
int messageLength = bb.getInt();
Debug.log("TCPClient- header read. message length (" + messageLength + ")");
byte[] body = new byte[messageLength];
do {
bytesRead = reader.read(body);
}
while (reader.available() > 0 && bytesRead != -1);
}
while (reader.available() > 0 && bytesRead != -1);
I know the code is not all complete, but can anyone provide any assistance?
Upvotes: 3
Views: 3933
Reputation: 54907
Delimiting messages with '\n'
is not a good idea, unless you’re sure that your messages will only consist of single-line text. If a '\n'
occurs innately within one of your messages, then that message would get split up.
You state that the message length must be exactly 4 bytes; however, the following line generates an 8-byte array:
byte[] header = BitConverter.GetBytes((long) body.Length);
The reason is that, in C#, long
is an alias for the Int64
struct, representing a 64-bit signed integer. What you need is an int
or a uint
, which represents a 32-bit signed or unsigned integer. Thus, just change the above line to:
byte[] header = BitConverter.GetBytes(body.Length);
Another important consideration you need to make is the endianness of your data. Imagine you’re trying to create a 4-byte array for a value of, say, 7. On a big-endian platform, this would get encoded as 0,0,0,7
; on a little-endian platform, it would get encoded as 7,0,0,0
. However, if it gets decoded on a platform having the reverse endianness, the 7
would be interpreted as the most significant byte, giving a value of 117,440,512 (equal to 7×2563) rather than 7.
Thus, the endianness must be the same for both your Java and your C# applications. The Java ByteBuffer
is big-endian by default; however, the C# BitConverter
is architecture-dependant, and may be checked through its IsLittleEndian
static property. You can get C# to always follow the big-endian convention by reversing your array when it is little-endian:
byte[] header = BitConverter.GetBytes(body.Length);
if (BitConverter.IsLittleEndian)
Array.Reverse(header);
Upvotes: 4