开发者

C# send structure objects through socket

i have done a bit of reading in client/server programming in c#. i am familiar enough with this process to ask the following question:

how do i transmit structure objects through tcp/ip instead of just strings?

my app is a networked game with chat capabilities. so instead of just transmitting text, i would like to imploy a data structure or class structure that will have two fields: i. packet type ii. the data for the packet type

and i would transmit this when needed during the execution of the application, and decode the data object at the receiving end and place it where it belongs.

im not looking for code, just some ideas and search statements i can feed to google so i wi开发者_JAVA百科ll; have a better understanding.

ive read about serialisation/de serialisation, is that he way to go?

thanks.


i have checked the posts that showed up as related topics but would still like further guidence.



Ultimately yes: you are talking about serialization. This can take many forms, especially in .NET, but ultimately you need to pick between:

  • text vs binary; direct binary tends to be smaller than text, as it usually involves less parsing etc; text (xml, json, etc) is usually represented in the stream as UTF8 (although any encoding is possible). They are broadly human-readable, and despite being more verbose can usually be compressed pretty well.
  • contract vs metadata; contract-based serializers focus on representing data - it is assumed that the other end of the pipe understands the structure, but it is not assumed that they share an implementation. This has limitations in that you can't suddenly introduce some completely unexpected subclass, but makes it platform-independent. By contrast, metadata-based serializers send the type information on the stream (i.e. "this is a My.Namespace.FooBar instance). This makes it really easy to get working, but rarely works between different platforms (and often not between versions) - and all that type information can be verbose
  • manual vs automatic; fact: manual serializers can often be the most efficient in terms of bandwidth, since you can hand-customise the heck out of the stream - but it takes lots of effort and you need to understand serialization lots. Automatic serializers are much better for general purpose uses (in reality: most scenarios). Avoid manual unless you have no choice. Automatic serializers handle all the complexities of worrying about different types of data, etc.

Manual serializer approaches include (just mentioning the "serializer" keyword): TextWriter, XmlWriter, IXmlSerializable, BinaryWriter, ISerializable. You don't want to do that...

Focusing more on the automatic serializers:

               | Contract               | Metadata
===============+========================+===========================
  Text         | XmlSerializer          | SoapFormatter
               | DataContractSerializer | NetDataContractSerializer
               | Json.NET               |
---------------+------------------------+---------------------------
  Binary       | protobuf-net           | BinaryFormatter

Since you are talking raw streams, my preference would be a binary contract-based serializer - but then, I wrote protobuf-net, so I may be biased ;-p

For comparison with common RPC stacks:

  • "remoting" uses BinaryFormatter
  • "asmx" web-services (including WSE*) use XmlSerializer
  • WCF can use many, most commonly DataContractSerializer or NetDataContractSerializer, and sometimes XmlSerializer (it can also be configured to use, for example, protobuf-net)

I can happily write an example of using protobuf-net on a stream to represent different messages of different types, but a simple example of socket processing using protobuf-net is in one of the sample projects (here, in fact)


If you don't need the richness of serialization - if you just wanna write a structure to a byte array, consider the Marshal class.

For example, consider a tar app in C#. The tar format is based on 512-byte blocks, and the first block in a series has a regular structure. Ideally the app wants to just blitt the data from the disk file, right into a structure. The Marshal.PtrToStructure method does this. Here's the structure.

    [StructLayout(LayoutKind.Sequential, Size=512)]
    internal struct HeaderBlock
    {
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 100)]
        public byte[]   name;    // name of file. 

        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
        public byte[]   mode;    // file mode

        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
        public byte[]   uid;     // owner user ID

        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
        public byte[]   gid;     // owner group ID

        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 12)]
        public byte[]   size;    // length of file in bytes

        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 12)]
        public byte[]   mtime;   // modify time of file

        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
        public byte[]   chksum;  // checksum for header

        // ... more like that... up to 512 bytes. 

Then here's a generic class that does the blitting.

internal class RawSerializer<T>
{
    public T RawDeserialize( byte[] rawData )
    {
        return RawDeserialize( rawData , 0 );
    }    

    public T RawDeserialize( byte[] rawData , int position )
    {
        int rawsize = Marshal.SizeOf( typeof(T) );
        if( rawsize > rawData.Length )
            return default(T);

        IntPtr buffer = Marshal.AllocHGlobal( rawsize );
        Marshal.Copy( rawData, position, buffer, rawsize );
        T obj = (T) Marshal.PtrToStructure( buffer, typeof(T) );
        Marshal.FreeHGlobal( buffer );
        return obj;
    }

    public byte[] RawSerialize( T item )
    {
        int rawSize = Marshal.SizeOf( typeof(T) );
        IntPtr buffer = Marshal.AllocHGlobal( rawSize );
        Marshal.StructureToPtr( item, buffer, false );
        byte[] rawData = new byte[ rawSize ];
        Marshal.Copy( buffer, rawData, 0, rawSize );
        Marshal.FreeHGlobal( buffer );
        return rawData;
    }
}

You can use that class with any structure. You have to use LayoutKind.Sequential and restrict yourself to blittable types (basically primitives and arrays of same) to use this approach. It's fast and efficient, in terms of code and performance and memory, but it's a little restricted in how it can be used.

Once you have the byte array, you can transmit it over a NetworkStream or etc, and then de-serialize using the same class on the other end.


Serialization is the simplest way to go as the system supports it directly. However there are some performance issues with large and complicated objects. In your case it sounds like serialization is the way to go. If you wnat something lower-level your can check out BinaryWriter/BinaryReader which allows you to do the work yourself.


You can create a NetworkStream based on a Socket, and use any Stream mechanism to transport your data. That translates your question to: How can I read/write a struct from/to a Stream.

You can use Serialization but also a BinaryWriter/BinaryReader. For a small struct (like you describe) I would write some custom methods:

var netStream = new NetworkStream(clientSocket, true);
var writer = new BinaryWriter(netStream);

writer.Write(data.Value1);
writer.Write(data.Value2);

For larger structures I would consider Cheeso's Marshaling option.


.NET's binary serialization would probably be the fastest out-of-the-box option, assuming both sides of the communication mechanism are C# and can load the same assembly, containing the message types. Rolling your own serialization is probably just fine though, if your structures are very simple. Just define your data structure in a class and also a method to convert it to and from a string.


You're on the right track with using object serialization.

One thing I would think about that I haven’t seen mentioned yet is that binary serializers will generally create a less bytes to send over the socket, however if you use a XML or JSON serializer and then compress the results using a CompressionStream (GZipStream?) before you send it over your network stream you may get even smalelr sizes based on the type of data in your object (this works best when you have lots of strings).

This will take more CPU time to send and to read the messages so it’s a tradeoff if you need to lower bandwidth requirements.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜