C#通过socket发送结构对象

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

我如何通过TCP / IP而不是字符串传输结构对象?

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

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

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

香港专业教育学院阅读有关序列化/反序列化,是他的方式去?

谢谢。


我已经检查了相关主题的post,但仍然希望进一步指导。


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

最终是的:你在谈论序列化。 这可以采用多种forms,尤其是在.NET中,但最终需要选择:

  • 文本与二进制; 直接二进制文件往往比文本小,因为它通常涉及较少的解析等; 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”web服务(包括WSE *)使用XmlSerializer
  • WCF可以使用许多,最常见的DataContractSerializerNetDataContractSerializer ,有时也可以使用XmlSerializer (它也可以配置为使用例如protobuf-net)

我可以愉快地编写一个在流上使用protobuf-net来表示不同类型的不同消息的示例,但是使用protobuf-net的套接字处理的一个简单示例是在其中一个示例项目中( 事实上,这里 )

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

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

例如,考虑C#中的tar应用程序。 tar格式基于512字节块,并且系列中的第一个块具有常规结构。 理想情况下,应用程序只想将磁盘文件中的数据加入到结构中 。 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的generics类。

 internal class RawSerializer { 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等传输它,然后在另一端使用相同的类进行反序列化。

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

使用对象序列化,您处于正确的轨道上。

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

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