C#通过socket发送结构对象

时间:2009-12-18 17:07:14

标签: c# sockets tcp

我在c#中的客户端/服务器编程中做了一些阅读。我对这个过程非常熟悉,可以提出以下问题:

如何通过tcp / ip而不仅仅是字符串传输结构对象?

我的应用是一款具有聊天功能的联网游戏。所以我不想只传输文本,而是想要构建一个包含两个字段的数据结构或类结构:i。包类型ii。数据包类型的数据

我会在应用程序执行期间需要时传输它,并在接收端解码数据对象并将其放在它所属的位置。

我不寻找代码,只是一些想法和搜索语句,我可以提供给谷歌,所以我会;有更好的理解。

我读过有关序列化/反序列化的内容,是他的出路吗?

感谢。


我查看过相关主题的帖子,但仍希望得到进一步的指导。


6 个答案:

答案 0 :(得分:7)

最终是的:你在谈论序列化。这可以采用多种形式,特别是在.NET中,但最终需要在:

之间进行选择
  • text vs binary;直接二进制趋势小于文本,因为它通常涉及较少的解析等; text(xml,json等)通常在流中表示为UTF8(尽管可以进行任何编码)。它们具有广泛的人类可读性,尽管更加冗长,但通常可以很好地压缩。
  • 合同与元数据;基于合同的序列化程序专注于表示数据 - 假设管道的另一端了解结构,但不假设它们共享实现。这具有局限性,因为您不能突然引入一些完全出乎意料的子类,而是使其与平台无关。相比之下,基于元数据的序列化程序在流上发送类型信息(即“这是一个My.Namespace.FooBar实例”。这使得它很容易上班,但很少在不同平台之间工作(通常不在版本之间) - 所有类型信息都可以是详细的
  • 手动与自动;事实上:手动序列化器在带宽方面通常是最有效的,因为您可以手动自定义流中的内容 - 但需要花费很多的努力,您需要了解序列化批次。自动序列化器对于通用用途要好得多(实际上:大多数场景)。除非你别无选择,否则不要手动。自动序列化程序可以处理担心不同类型数据的所有复杂性等。

手动序列化方法包括(仅提及“序列化程序”关键字):TextWriterXmlWriterIXmlSerializableBinaryWriterISerializable。你不想这样做......

更多关注自动序列化器:

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

既然你在谈论原始流,我的偏好是基于二进制契约的序列化器 - 但是,我写了protobuf-net,所以我可能有偏见;-p

与常见的RPC堆栈进行比较:

  • “remoting”使用BinaryFormatter
  • “asmx”网络服务(包括WSE *)使用XmlSerializer
  • WCF可以使用多个,最常见的是DataContractSerializerNetDataContractSerializer,有时还会XmlSerializer(它也可以配置为使用例如protobuf-net)

我很乐意写一个在流上使用protobuf-net来表示不同类型的不同消息的示例,但使用protobuf-net的简单套接字处理示例是在其中一个示例项目中(here, in fact

答案 1 :(得分:6)

如果您不需要丰富的序列化 - 如果您只想将结构写入字节数组,请考虑Marshal类。

例如,考虑使用C#中的tar应用程序。 tar格式基于512字节块,并且系列中的第一个块具有规则结构。理想情况下,应用程序只需要blitt磁盘文件中的数据,直接进入结构Marshal.PtrToStructure方法执行此操作。这是结构。

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

然后这是一个执行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;
    }
}

您可以将该类与任何结构一起使用。您必须使用LayoutKind.Sequential并将自己限制为blittable类型(基本上是基元和相同的数组)才能使用此方法。它在代码,性能和内存方面快速而有效,但它在如何使用方面受到一定限制。

获得字节数组后,可以通过NetworkStream等传输它,然后在另一端使用相同的类进行反序列化。

答案 2 :(得分:5)

序列化是最简单的方法,因为系统直接支持它。但是,对于大而复杂的对象存在一些性能问题。在你的情况下,听起来像序列化是要走的路。如果您想要更低级别的东西,您可以查看BinaryWriter / BinaryReader,它允许您自己完成工作。

答案 3 :(得分:3)

您可以基于Socket创建NetworkStream,并使用任何Stream机制来传输数据。这会将您的问题转换为:如何从/向Stream读取/写入结构。

您可以使用序列化,也可以使用BinaryWriter / BinaryReader。对于一个小结构(就像你描述的那样),我会写一些自定义方法:

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

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

对于较大的结构,我会考虑Cheeso的Marshaling选项。

答案 4 :(得分:1)

.NET的二进制序列化可能是最快的开箱即用选项,假设通信机制的两端都是C#并且可以加载包含消息类型的相同程序集。如果您的结构非常简单,那么滚动您自己的序列化可能就好了。只需在类中定义数据结构,也可以在字符串中定义数据结构。

答案 5 :(得分:1)

使用对象序列化,你在正确的轨道上。

我想到的一件事我还没有提到过,二进制序列化器通常会创建一个较少的字节来通过套接字发送,但是如果您使用XML或JSON序列化程序然后使用CompressionStream压缩结果(GZipStream?)在通过网络流发送之前,您可能会根据对象中的数据类型获得更小的尺寸(当您有大量字符串时,这种方法效果最佳)。

这将需要更多的CPU时间来发送和读取消息,因此如果您需要降低带宽要求,则需要进行权衡。