C# Deserializing a Struct After Receiving It Through Tcp

C# Deserializing a struct after receiving it through TCP

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:

[Length (4 bytes)][Header (1 byte)][Data (x byte(s))]

Reading a packet is very simple:

  1. Read the first 4 bytes (Length), convert and assign them to an integer variable.

  2. Read the next byte (the data header) and put that in a variable.

  3. Read x bytes to a byte array (where x is the integer you declared in step 1).

  4. 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.

Deserializing data sent via TCP

In your sendData method you serialize the object to a memory stream and then read it back into a buffer of 2048 bytes before writing it to the network stream. If the serialzed object is > 2048 bytes you would have a problem. I would try just serializing directly to the network stream or at least using the same kind of code as in your ReadData method where you write byte by byte.

Edit:

The size of the data is probably not the problem. You should still avoid the hard coded buffer size and stick with the code change you mention in the comment. Given your comment the problem lies elsewhere.
In both your sender and your receiver you write to a memory stream and then read from it. You can't do that unless you set the current position in the stream back to zero between the write and read.

So in your sendData method on the client add the line

mstream.Seek(0, SeekOrigin.Begin);

right after

bform.Serialize(mstream, data);

And in the ReadData method of your server add the line

memStream.Seek(0, SeekOrigin.Begin);

right before

Packet packet = (Packet)bform.Deserialize(memStream);

That way the memory stream is set to the beginning before you try and read from it.

I think you could just skip the memory stream all together and just read and write to the network stream, but you might have other reasons for it.

How to serialize a complex structure to send via tcp/ip?

I change the serialize function inside struct Transfer_packet to this and it works fine for me butt i want a smart solution not this one, marshalling and Iserialize adds some headers or change the actual byte array to some thing that i don't know.

 public struct Transfer_packet 
{
public short _packet_type; // 0 is action 1 is data
public int _packet_len; // length of data
public byte[] _data;//Content of data it's Length depends on objects types

public byte[] serialize()
{
byte[] arr;
MemoryStream ms = new MemoryStream();
arr = BitConverter.GetBytes(this._packet_type);
// Array.Reverse(arr);
ms.Write(arr, 0, arr.Length);
arr = BitConverter.GetBytes(this._packet_len);
// Array.Reverse(arr);
ms.Write(arr,0,arr.Length);
ms.Write(this._data, 0, this._data.Length);
arr = ms.ToArray();
return arr;
}
}

Having trouble serializing these values in C# to be properly read in C++ as a known struct

While you cannot pre-initialize struct fields, nor directly put the size in MarshalAs attribute for ByValue 2D array, there is a little work-around you can do. You can define two structs like this:

const short SP_PACKET_SIZE = 200;
const short NAME_SIZE = 64;

struct spPacketStruct
{
public int Size;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = SP_PACKET_SIZE)]
private fixedString[] names;
public fixedString[] Names { get { return names ?? (names = new fixedString[SP_PACKET_SIZE]); } }
[MarshalAs(UnmanagedType.ByValArray, SizeConst = SP_PACKET_SIZE)]
private double[] value;
public double[] Value { get { return value ?? (value = new double[SP_PACKET_SIZE]); } }
}

struct fixedString
{
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = NAME_SIZE)]
public string Name;
}

By having additional struct to be a member of the original struct, you are able to specify the length of both dimensions by setting SizeConst in the original struct to the first dimension and setting it to the second dimension in the new struct. Making the field private and creating properties for them is merely for convenience, so you don't have to assign the array yourself when creating the struct.

Then you can serialize/deserialize the struct like this (code from this answer: https://stackoverflow.com/a/35717498/9748260):

public static byte[] GetBytes<T>(T str)
{
int size = Marshal.SizeOf(str);
byte[] arr = new byte[size];
GCHandle h = default;

try
{
h = GCHandle.Alloc(arr, GCHandleType.Pinned);
Marshal.StructureToPtr(str, h.AddrOfPinnedObject(), false);
}
finally
{
if (h.IsAllocated)
{
h.Free();
}
}

return arr;
}

public static T FromBytes<T>(byte[] arr) where T : struct
{
T str = default;
GCHandle h = default;

try
{
h = GCHandle.Alloc(arr, GCHandleType.Pinned);
str = Marshal.PtrToStructure<T>(h.AddrOfPinnedObject());
}
finally
{
if (h.IsAllocated)
{
h.Free();
}
}

return str;
}

And one last thing when trying to serialize/deserialize structs like this, be aware of the struct alignment as it can mess with the struct size

C# TCP data transfer with BinarySerializer

NetworkStream.Read() doesn't block until it reads the requested number of bytes:

"This method reads data into the buffer parameter and returns the number of bytes successfully read. If no data is available for reading, the Read method returns 0. The Read operation reads as much data as is available, up to the number of bytes specified by the size parameter. If the remote host shuts down the connection, and all available data has been received, the Read method completes immediately and return zero bytes."

You must

1) Know how many bytes you are expecting

and

2) Loop on Read() until you have received the expected bytes.

If you use a higher-level protocol like HTTP or Web Sockets they will handle this "message framing" for you. If you code on TCP/IP directly, then that's your responsibility.

How to rebuild a struct from bytes after transfer [C#]

If you have the definitions of the struct in c++ you can define it in c# (it may require a little finagling). Then just read the size off of the socket and use Marshal.PtrToStructure to marshal it to the c# defined structure.

/* Define you structure like this (possibly) */
struct MESSAGE
{
[MarshalAs(UnmanageType.ByValArray, SizeConst=8)]
byte[] cCommand;
[MarshalAs(UnmanagedType.LPStr)]
string sParameter;
};

/* Read data into an instance of MESSAGE like this */
byte[] bytes = new byte[Marshal.SizeOf(MESSAGE)];

socket.Receive(bytes, 0, bytes.Length);
IntPtr ptr = Marshal.AllocHGlobal(bytes.Length);

try
{
Marshal.Copy(bytes, 0, ptr, bytes.Length);
m = (MESSAGE)Marshal.PtrToStructure(ptr, typeof(MESSAGE));
}
finally
{
Marshal.FreeHGlobal(ptr);
}

C# TCP Connection Uses too Much Memory when Receiving Deserialized Classes

I finally found what was wrong. Basically the Garbage Collector isn't able to dispose the XMLConverter if the istance was created with that type of constructor

new XmlSerializer(type, otherTypes);

Instead of that class, I have used the Json serializer contained in the package Newtonsoft.Json. It now works well and it is way much faster!

Sending a bit type data through TCP socket method in C#

The marshalling method to get your byte array should work.

Now onto your data structure:

ResourceID  Int   0
SPSType Byte 2
Auto Mode Bit 3.0
... Bit 3.n
MES Mode Bit 3.7

I bring your attention to the numeric column containing 0, 2, and 3.x

  1. ResourceID looks to occupy bytes 0 and 1. Two bytes in an Int indicates your PLC is 16-bit. C#'s int is 32-bit which takes up FOUR bytes. You need to explicitly specify either Int16 or UInt16 (probably the unsigned UInt16, unless you MES expects a negative number from the PLC).

    They are also known as short or ushort, but it's always nice to be more explicit by specifying the 16-bitness when dealing with external systems to minimise confusion.

  2. SPSType is simply a byte.

  3. The rest of them are marked as 3.0 ... 3.7. This is the notation for 8 bits (0..7) that occupy byte 3. Which means, yes, you are expected to send one byte containing all the bits. Do bear in mind that bit 0 is the right-most bit, so 0b00000001 is AutoMode, and 0b10000000 is MESMode.



Related Topics



Leave a reply



Submit