【问题标题】:C# send structure objects through socketC#通过socket发送结构对象
【发布时间】:2010-12-28 02:57:42
【问题描述】:

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

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

我的应用是一款具有聊天功能的网络游戏。因此,我想使用具有两个字段的数据结构或类结构,而不仅仅是传输文本:i。数据包类型 ii。数据包类型的数据

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

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

我读过关于序列化/反序列化的文章,他还有路要走吗?

谢谢。


我已经检查了显示为相关主题但仍需要进一步指导的帖子。


【问题讨论】:

  • 我刚刚阅读了以下内容: 二进制格式化程序:二进制格式化程序为紧凑序列化提供二进制编码,用于存储或基于套接字的网络流。当数据要通过防火墙时,BinaryFormatter 类通常不合适。 -------------------- Xml 序列化是否适合通过防火墙?

标签: c# sockets tcp


【解决方案1】:

最终是的:您在谈论序列化。这可以采取多种形式,尤其是在 .NET 中,但最终您需要在以下两者之间做出选择:

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

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

更多地关注自动序列化器:

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

由于您在谈论原始流,我更喜欢基于二进制合约的序列化器 - 但是,我写了 protobuf-net,所以我可能有偏见;-p

与常见的 RPC 堆栈比较:

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

我可以很高兴地编写一个在流上使用 protobuf-net 来表示不同类型的不同消息的示例,但是一个使用 protobuf-net 进行套接字处理的简单示例在其中一个示例项目中(here, in fact)

【讨论】:

    【解决方案2】:

    如果您不需要丰富的序列化 - 如果您只想将结构写入字节数组,请考虑 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;
        }
    }
    

    您可以将该类与 any 结构一起使用。您必须使用 LayoutKind.Sequential 并将自己限制为可 blittable 类型(基本上是相同的基元和数组)才能使用这种方法。就代码、性能和内存而言,它快速高效,但在使用方式上受到了一些限制。

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

    【讨论】:

    • 谢谢!非常适合 ICD 实施。恕我直言,在方法而不是类上使用泛型参数会更好一些,因为编译器可以在大部分时间序列化时推断 T。
    【解决方案3】:

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

    【讨论】:

    • 也许您想详细说明二进制和其他序列化之间的区别,以解释为什么二进制会更有效。
    • 最终,所有的序列化都是“二进制的”——区别在于如何。
    【解决方案4】:

    您可以基于 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 的编组选项。

    【讨论】:

    • 封送处理只是将结构转换为字节数组。正如您所展示的,我假设他想通过网络流传输字节。
    • 是的,我同意你的回答。但是为 2 个值分配一个非托管缓冲区是一个很大的开销。
    【解决方案5】:

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

    【讨论】:

      【解决方案6】:

      您在使用对象序列化方面走在了正确的轨道上。

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

      这将花费更多 CPU 时间来发送和读取消息,因此如果您需要降低带宽要求,这是一个折衷方案。

      【讨论】:

        猜你喜欢
        • 2012-06-19
        • 1970-01-01
        • 1970-01-01
        • 2013-03-23
        • 2015-03-11
        • 1970-01-01
        • 1970-01-01
        • 2023-03-15
        • 1970-01-01
        相关资源
        最近更新 更多