I need to stream data to a TCP-server and havnt done it before. The data should be in binary frame format. I googled and found this tutorial which I think describes what I need to do:
http://msdn.microsoft.com/en-us/library/system.net.sockets.tcpclient(v=vs.71).aspx
But I dont know how to create the data in the format needed so I can send it in the correct way. It should be in this format:
Field|Offset| Type | size(octets)
id | 0 |unsign int| 1
name | 1 |Byte Array| 40
grade| 41 |sign float| 8
Example:
Field| Data | InBytes |
id | 133 | 133 |
name | 247 | 247 0 |
grade| 0 | 0 |
Whats the data type I should store the int, byte array, float in and where do I specify offsets and size and finally how should it be sent to the server (in the example they only send a byte array).
A C#-example of how to send data like this using the code in the link provided above would be appreciated.
To represent the data to be serialized (and deserialized) you can use a struct and set the proper metadata, so the CLR do the rest for you. Like others said here you will need to deal with the packet reception on the end point. Also you'll have to consider the charset expected by the receiver, since you have a string field in your data. The following code is an example on how you can implement the struct to convert your managed data to a binary format, with comments.
// setting the layout to sequential will prevent the compiler/JIT
// to reorder the struct fields
// NOTE: Observe here that the Charset used is Ansi. You may need to
// change this depending on the format expected by the receiver.
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
struct DataPacket
{
[MarshalAs(UnmanagedType.U4)]
public uint Id;
// As I understood from your question, the Name field
// has a prefixed size of 40 bytes. Attention here:
// the SizeConst actually means '40 characters', not bytes
// If you choose to use the Unicode charset, set SizeConst = 20
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 40)]
public String Name;
// This will be serialized in little endian format
// in your question this field is 8 bytes long, which
// in c# corresponds to the double type. If you really mean
// the float type (4 bytes), change here.
public double Grade;
// Calling this method will return a byte array with the contents
// of the struct ready to be sent via the tcp socket.
public byte[] Serialize()
{
// allocate a byte array for the struct data
var buffer = new byte[Marshal.SizeOf(typeof(DataPacket))];
// Allocate a GCHandle and get the array pointer
var gch = GCHandle.Alloc(buffer, GCHandleType.Pinned);
var pBuffer = gch.AddrOfPinnedObject();
// copy data from struct to array and unpin the gc pointer
Marshal.StructureToPtr(this, pBuffer, false);
gch.Free();
return buffer;
}
// this method will deserialize a byte array into the struct.
public void Deserialize(ref byte[] data)
{
var gch = GCHandle.Alloc(data, GCHandleType.Pinned);
this = (DataPacket)Marshal.PtrToStructure(gch.AddrOfPinnedObject(), typeof(DataPacket));
gch.Free();
}
}
Usage:
DataPacket packet;
packet.Id = 1234;
packet.Name = "Marvin the paranoid robot";
packet.Grade = 9.2;
// serialize
var bytes = packet.Serialize();
// send via tcp
var tcp = new TcpClient(...);
tcp.GetStream().Write(bytes, 0, bytes.Length);
// deserializing;
DataPacket receivedPacket;
receivedPacket.Deserialize(bytes);
You already have the packet, now you'll need to deal with the packet reception on the receiver. That part you don't need to do all by hand, you can use some tools, like @jgauffin said.
The approach that I would take would be to have two functions for an interface between the rest of my code and the physical representation of the data that is being sent out. This would allow me to encapsulate the physical data representation that is sent out the TCP connection and allow it to change if needed.
So what you probably would want is to have a TCP reader class which provides the frame composing functionality and the conversion from raw bytes of the frame into a data structure and a TCP writer class which takes a data structure and creates a raw byte frame and then writes it to the TCP connection.
I would have a message buffer that would be an array of bytes. This is the message buffer that will be sent out the TCP connection.
Next would be a data structure that contains the data as it is used in the rest of the system.
I would then have a function, say FromTcpToDataStruct (dataStruct, messageArray)
to translate from the raw string of bytes to the data structure I am using, and then FromDataStructToTcp (messageArray, dataStruct)
to translate from the data structure to the raw string.
You have a few issues that you will need to consider. First is the representation of an unsign int and a float on the target server, the Little Endian Big Endian problem. The second is the transformation of a string of bytes from the TCP connection into discrete frames (reading of the bytes from the TCP connection and composing a complete frame of raw bytes using an interim buffer so that the read provides a complete frame of bytes).
You can't just send that data. TCP is stream based which means that your information can either be received using one operation (socket.Receive()
) or by using multiple operations.
The easiest way for you to fix that is to have a packet format which contains a header (a simple length header is enough) and a body (your specified information).
I don't know if you have to implement everything by yourself or if you can use a network library. I've created a library which takes care of all socket IO for you. All you need to do is to take care of the encoding and decoding. I've writing a small example (not finished) for a simple binary protocol.
The coder/decoder classes can be found here: https://github.com/jgauffin/griffin.networking/tree/master/Source/Protocols/SimpleBinary/Griffin.Networking.Protocol.SimpleBinary/Handlers
A Griffin.Networking introduction: http://blog.gauffin.org/2012/05/griffin-networking-a-somewhat-performant-networking-library-for-net/
I would simply use a binary writer to convert the data to a byte array and then send it as in the example.
You'll probably need to check the string to make sure it forms 40 bytes
MemoryStream MS = new MemoryStream();
BinaryWriter Writer = new BinaryWriter(MS);
Writer.Write(MyByte);
Writer.Write(ASCIIEncoding.UTF8.GetBytes(MyString));
Writer.Write(MyDouble);