I am sending my own struct "packet" object through the TCP interface that C# offers with TCPListener and TCPClient.
This is my struct
[Serializable]
struct RemuseNetworkPacket
{
public String ApplicationCode;
public String ReceiverCode;
public RemusePacketType Type;
public uint ID;
public uint cID;
public String Name;
public byte[] Data;
public String Text;
public System.Drawing.Point Coords;
public String Timestamp;
public String Time;
public String SenderName;
public byte[] Serialize()
{
var buffer = new byte[Marshal.SizeOf(typeof(RemuseNetworkPacket))];
var gch = GCHandle.Alloc(buffer, GCHandleType.Pinned);
var pBuffer = gch.AddrOfPinnedObject();
Marshal.StructureToPtr(this, pBuffer, false);
gch.Free();
return buffer;
}
public void Deserialize(byte[] data)
{
var gch = GCHandle.Alloc(data, GCHandleType.Pinned);
this = (RemuseNetworkPacket)Marshal.PtrToStructure(gch.AddrOfPinnedObject(), typeof(RemuseNetworkPacket));
gch.Free();
}
}
I am using the serialization methods within the struct to prepare and retrieve the data before and after sending.
In order to to let the receiver know the incoming data's size, I add some header data to the bytes being sent, in the format of l=212;... which means length = 212; and the ... is the rest of the packet.
On the receiving end I search for this until I find the entire l=xxxx; then I make a new byte array without the header, then attempt to deserialize the data. The packet byte to use for deserialization is: tcp stream's buffer.Length - foundHeaderSize (l=xxxx;)
If I run the client and server on the same machine, it works without errors, however if I have the client and server on separate machines, I get exceptions and it crashes.
The exception takes place when the packet is being deserialized, saying:
*System.Runtime.InteropServices.SafeArrayTypeMismatchException Mismatch has occurred between the runtime type of the array and the sb type recorded in the metadata at System.Runtime.InteropServices.PtrToStructureHelper
Stacktrace: System.Runtime.InteropServices.PtrToStructureHelper (IntPtr ptr, Object structure, Boolean allowValueClasses) at System.Runtime.InteropServices.PtrToStructure(IntPtr ptr, Type structureType..*
I'm asking for help to identify the cause of the problem. Can I not do it like this with objects that came over the network?
Instead of having a string represent your packet length and then subtract by the string's length to know where to start reading, you should implement proper length-prefixing. Length-prefixing combined with a data header will make you able to read every packet according to its size, then the data header will help you determine what to do with the data.
Ordinary length-prefixing adds a fixed header to every "packet" you send. To create this header you convert an integer (the length of your data) to bytes, which will result in 4 bytes, then you add the data header after that and also the rest of the packet (which is the data you want to send).
This will create the following packet structure:
Reading a packet is very simple:
Read the first 4 bytes (
Length
), convert and assign them to an integer variable.Read the next byte (the data header) and put that in a variable.
Read
x
bytes to a byte array (wherex
is the integer you declared in step 1).Use the data header from step 2 to determine what to do with your data (the byte array from step 3).
In one of my previous answers you can see an example of what I just explained above.
Serialized binary data of a structure might differ depending on the platform and OS. e.g. different alignment, different size of long That might be the reason why your code works on the same machine, but not on a different machine.
I would suggest you either use a library like Google ProtoBuf (fast) https://developers.google.com/protocol-buffers/docs/csharptutorial
or rely on the C# object serialization using e.g. XML Serialization (slow) https://msdn.microsoft.com/en-us/library/58a18dwa(v=vs.110).aspx