# gserialize **Repository Path**: sloder/gserialize ## Basic Information - **Project Name**: gserialize - **Description**: 基于代码生成的高性能C#二进制序列化库,比BinaryFormatter快10倍以上,面向.NET Standard2.0。 该项目还包含一个基于此序列化库的RPC框架。 - **Primary Language**: C# - **License**: MPL-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 8 - **Created**: 2023-05-19 - **Last Updated**: 2023-05-19 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 关于 GSerialize & XPRPC GSerialize 是基于代码生成的C#二进制序列化方案,设计目标是尽量快速的同时保证易用性 * 支持绝大多数原生类型 * 支持用SerializableAttribute标记的public class * 支持数组(一维)、List、Dictionary等集合类型 * 快速(比 BinaryFormatter 快一个数量级) * 支持 .NET Standard 2.0 XPRPC 是利用GSerial实现的一个RPC服务框架,设计目标依旧是易用且高效 * 基于角色权限配置的安全机制 * 服务单进程内本地部署或基于TCP连接的分布式部署,客户端/服务器代码在此两种情形下基本一致 * 分布部署支持event夸网络传递,可选支持SSL连接,支持客户端自动重连 * 支持双向自动代理,客户端/服务器可以通过接口互相调用 ## GSerialize 用法 ### GSerialize设计为非常易于使用的方式 ```C# var serializer = Serializer(stream); //Construct a serializer that will store data into a stream //To serialize an object serializer.Serialize(yourClassInstance); //To deserialize an object var yourClassInstance = serializer.Deserialize(); ``` ### 序列化原生类型,目前支持的原生类型有 * Int16/UInt16 * Int32/UInt32 * Int64/UInt64 * Byte/SByte * String * Char * Decimal * Float * Double * DateTime * Guid #### 代码示例 ```C# using var mem = new MemoryStream(); var serializer = new Serializer(mem); UInt16 un16_1 = 123; mem.Seek(0, SeekOrigin.Begin); serializer.Serialize(un16_1); mem.Seek(0, SeekOrigin.Begin); var un16_2 = serializer.Deserialize(); Debug.Assert(un16_1 == un16_2); string str1 = "good idea"; mem.Seek(0, SeekOrigin.Begin); serializer.Serialize(str1); mem.Seek(0, SeekOrigin.Begin); var str2 = serializer.Deserialize(); Debug.Assert(str1 == str2); float f1 = 1.236f; mem.Seek(0, SeekOrigin.Begin); serializer.Serialize(f1); mem.Seek(0, SeekOrigin.Begin); var f2 = serializer.Deserialize(); Debug.Assert(f1 == f2); DateTime dt1 = DateTime.Now; mem.Seek(0, SeekOrigin.Begin); serializer.Serialize(dt1); mem.Seek(0, SeekOrigin.Begin); var dt2 = serializer.Deserialize(); Debug.Assert(dt1 == dt2); Guid guid_1 = Guid.NewGuid(); mem.Seek(0, SeekOrigin.Begin); serializer.Serialize(guid_1); mem.Seek(0, SeekOrigin.Begin); var guid_2 = serializer.Deserialize(); Debug.Assert(guid_1 == guid_2); ``` ### 自定义类型 #### 定义自己的可序列化类型 自定义类型的字段/属性可以是以下类型 * 原生类型 * 泛型 List * 泛型 Dictionary * Enum * Nullable #### 代码示例 ```C# [Serializable] //附加 SerializableAttribute, 标记此类为可序列化 public class OptionalFieldUnit { [NonNull] //NonNull 标记非null的字段/属性,序列化时不会检查是否为null,这能改进性能 public string RequiredField; public string OptionalField; [NonSerialized] //NonSerialized 标记忽略字段/属性,不会被序列化 public string IgnoredField; public string ReadOnlyProperty => PrivateField; //readonly 字段/属性不会被序列化 private string PrivateField; //private 字段/属性不会被序列化 } ``` ## 行为 当Serializer.Serialize/Deserialize方法被调用时,如果针对类型T的序列化代码不存在,则自动生成序列化代码并缓存在内存中,后续则直接调用生成的代码来执行序列化。 当生成类型T相关的序列化代码时,其所在Assembly中的全部Serializable标记public class皆同时生成序列化代码,且其依赖的类型相关序列化代码也会生成。 生成代码过程相对较慢,可能会消耗若干秒。客户程序可以通过调用Serializer.CacheSerialiablesInAssembly方法来预先生成某个Assembly中全部Serializable标记的public class相关序列化代码。 ## 限制 * 为了达到最佳性能,GSerialize.Serializer 不支持引用检测,同一个对象的每个引用都是独立存储一个拷贝,恢复时则解析成不同对象。若待序列化class中含有循环引用,则序列化过程会死循环,因此自定义类型必须保证不能出现内部成员属性/字段间的循环引用。 * 若要检测引用,可使用GSerialize.Serializer2,同一个对象的多个引用只存储一个拷贝,恢复时还是解析成同一个对象。性能上会有少许损失(同等数据量比Serializer慢约10%,但依旧能够快BinaryFormatter一个数量级)。如果重复引用数量足够多,Serializer2可能比Serializer更快因为读写数据量更小。 * GSerialize 不是线程安全的,从多个线程同时访问 Serializer/Serializer2 的同一个实例可能会出错。 * 自定义类型需要支持无参数public构造器 ## GSerialize高性能的实现 * 基于自定义class字段/属性的静态类型来生成代码,而非序列化时通过反射来操作 * 通过unsafe代码来操作底层二进制数据 * 对容器中的数据序列化时,将循环内的重复代码提取到循环外 * 将buffer定义成class member复用,尽可能减少buffer的new操作 * 不使用抽象,避免间接层,所有public class都是sealed的 ## XPRPC 用法 XPRPC 的基本结构是由一个周知的服务器管理器(由IServiceManager接口定义)管理各个发布的接口信息。 服务提供者通过ServiceRunner来发布服务。客户端通过ServiceResolver来解析需要的服务并调用。 ### IServiceManager 该服务由ServiceManagerImpl实现,提供客户端身份验证,登记服务信息,查询服务信息及服务健康检测(TODO)等功能。 LocalManagerRunner用于本地发布 IServiceManager 服务,TcpManagerRunner用于跨网络发布 IServiceManager 服务。 ### ILogger 该服务作为系统的基础服务,随XPRPC一起提供,用于输出日志,目前支持Console、Debug、文件系统三种输出目标。 ### 发布服务 以网络服务发布为例 * 发布 IServiceManager,必须是周知的地址/端口 ```C# var logger = BuilderLogger(); var descManager = new ServiceDescriptor { Name = "service_manager", Description = "服务治理", ServiceHost = "localhost", ServicePort = 3324, //服务器主机名及端口为client周知 AccessToken = "AnyClient" }; using var managerRunner = TcpManagerRunner.Instance; managerRunner.Logger = logger; //需要一个logger作为基础服务 var config = AccessConfig.FromJson(ManagerConfigJson); managerRunner.Config(config); //配置IServiceManager的安全权限 managerRunner.Start(descManager, sslCertificate: null); //发布服务, sslCertificate若不为null则使用此证书建立SSL ``` * 发布 ILogger服务,供其他服务使用 ServiceRunner 用于发布一个除IServiceManager之外的服务,其子类 LocalServiceRunner、TcpServiceRunner分别用于发布本地服务和网络服务。 ```C# var loggerDescriptor = new ServiceDescriptor { Name = "logger", Description = "日志服务", ServicePort = 0, //IServiceManager之外的服务端口任意 }; using var loggerRunner = new TcpServiceRunner( service: logger, descriptor: loggerDescriptor, holdService: false, logger: logger, sslCertificate: null); //发布服务,发布者的clientID必须在IServiceManager中配置有发布服务的权限 loggerRunner.Start(descManager, clientID: "logger_provider",secretKey: "Dx90et54"); ``` * 发布其他服务 类似上面的ILogger服务的发布,若不在ILogger服务的发布进程内,则logger要使用远程获取的ILogger代理 ### 客户端使用服务 ServiceResolver类用于解析服务,其子类LocalServiceResolver、TcpServiceResolver分别用于解析本地和网络服务。 ```C# //构建resolver,必须保证访问权限正确 using var resolver = new TcpServiceResolver(descManager, clientID: "logger_client", secretKey: "02384Je5"); //解析服务,获取远程服务的本地代理,同样需要保证拥有对应的服务的访问权限 var remoteLogger = resolver.GetService("logger"); //RPC调用服务 remoteLogger.Info(tag: "Tcp Test", message: "remote log message."); ```