分布式服务平台的协议设计

协议设计( Protocol Design

数据包格式设计( Package Structures Design

 

客户端与服务端之间相互通信,有两种形式的数据包参与其中:

·          命令请求数据包( Request package ):发出命令请求

·          命令反馈数据包( Response package ):返回命令执行的结果

 

命令请求数据包设计

在不同的业务交互中,客户端向服务端发送的数据内容千差万别。为了满足这样的内容描述的需求,我们总结出这些需求的共性,将此类数据包的内容抽象为两部分:

·          命令描述:描述这个数据包是为了进行何种业务操作而发出的。

·          数据描述:描述我们进行上述业务操作所需的具体参数和数据。

命令描述

同步的业务操作通过上述两部分内容即可完成业务操作,但异步的业务操作还需要对数据包进行标注,以知晓是哪一次交互。比如说,在 Windows API 中,某些异步 API 的参数中包含命令的序列号(通常是一个 INT32 类型的数字),命令执行的结果将在 Notification 的消息内容中描述,这一消息中包含同样的序列号,这样,程序员就能将每一次发出的命令请求与这次命令执行的结果对应起来。这种异步操作的 Notification 返回的时间是未知的,甚至得到的 Notification event 的顺序也不一定是先前命令发出的顺序,因此,如果没有这个命令的序列号,程序员将无法将某一次命令请求与这次命令执行的结果相对应。

我们的分布式系统设计同样考虑到了同步请求与异步请求。

对于同步请求,命令描述中仅仅包含命令的名称。

对于异步请求,命令描述中除命令名称外,还包含一个 ID ,这个 ID 要保证在一定的时间内的同一种命令的请求中的唯一性。可以采用 INT32 类型的 ID ,也可以采用 GUID 类型。 INT32 类型的 ID 的优点是除了保证唯一性以外还能附带着表示命令请求的顺序,缺点是需要开发者自行维护 ID 的唯一性。 GUID 类型的 ID 的优点是理论上不可能重复,因此一旦生成即可保证唯一性,缺点是不能像 INT32 ID 那样描述命令请求的顺序(但这个顺序不一定是必需的)。

数据描述

当设计一个通用的服务平台的时候,我们无法预先知晓业务交互中的数据部分是什么样的,幸运的是现代编程语言(如 C# Java C++ )大多支持范型编程,所以,我们无法预先知晓数据的结构,但这不妨碍我们在代码中表述命令请求数据包的数据描述部分,采用一个抽象数据类型即可。

同步命令请求数据包的编程描述

 

public struct RequestData<T> { /// <summary>Command string</summary> public string cmd; /// <summary>Command data</summary> public T data; }

 

异步命令请求数据包的编程描述

 

public struct RequestDataAsync<T> { /// <summary>Command content</summary> public CmdAsync cmd; /// <summary>Command data</summary> public T data; } public struct CmdAsync { /// <summary>Command string</summary> public string name; /// <summary>Command sequence Id</summary> public System.Guid id; }

 

命令请求数据包设计

 

 

对于此类数据包,我们依照命令请求数据包的设计,将此类数据包抽象为以下三部分:

  • 命令描述:描述这个数据包是为了进行何种业务操作而发出的。
  • 数据描述:描述我们进行上述业务操作所需的具体参数和数据。
  • 命令反馈:描述数据包被服务端受理的情况。

命令描述

其实就是把“命令请求数据包”中的命令描述复制一遍,原封不动地返回给客户端。

 

数据描述

在原有“命令请求数据包”中的数据描述中,在原来的数据结构的基础上,改写“输出项”,作为业务操作的返回值 /返回参数。

 

命令反馈

包含两项:

  • 返回码:通常用0代表执行正常/成功,其他值表示错误代码。
  • 描述:如果命令执行失败,那么描述里面就尽可能详尽地表述失败的原因。

编程描述(含常用错误代码):

/// <summary> /// Struct definition for test result. /// </summary> public struct Result { #region Const value definition /// <summary>Global PASS result code.</summary> public const int ResultCodePass = 0; /// <summary>Global FAIL result code.</summary> public const int ResultCodeFail = 2; /// <summary>Global Duplicate-style FAIL result code.</summary> public const int ResultCodeFailDuplicate = 1; /// <summary>Global None-style FAIL result code.</summary> public const int ResultCodeFailNone = 3; /// <summary>Global Insert-style FAIL result code.</summary> public const int ResultCodeFailInsert = 4; /// <summary>Global Parse-style FAIL result code.</summary> public const int ResultCodeFailParse = 0xFF; #endregion #region Data fields /// <summary>Result code</summary> public int code; /// <summary>Result description (reason of failure or description of Success)</summary> public string desp; #endregion #region Initialization /// <summary> /// Initializes a new instance of the Result struct with result code. /// </summary> /// <param name="resultCode">result code</param> public Result(int resultCode) : this(resultCode, string.Empty) { } /// <summary> /// Initializes a new instance of the Result struct with result code and description. /// </summary> /// <param name="resultCode">result code</param> /// <param name="description">result description (reason of failure or description of Success)</param> public Result(int resultCode, string description) { this.code = resultCode; this.desp = description; } #endregion #region Quick Value /// <summary> /// Gets a new instance of success result without description. /// </summary> public static Result PASS { get { return new Result(ResultCodePass); } } /// <summary> /// Gets a new instance of success result without description. /// </summary> public static Result FAIL { get { return new Result(ResultCodeFail); } } /// <summary> /// Gets a new instance of success result with description. /// </summary> /// <param name="description">description of Success</param> /// <returns>Result instance</returns> public static Result Pass(string description) { return new Result(ResultCodePass, description); } /// <summary> /// Gets a new instance of failed result with description. /// </summary> /// <param name="description">reason of failure</param> /// <returns>Result instance</returns> public static Result Fail(string description) { return new Result(ResultCodeFail, description); } /// <summary> /// Gets a new instance of Duplicate failed result with description. /// </summary> /// <param name="description">reason of failure</param> /// <returns>Result instance</returns> public static Result Fail_Duplicate(string description) { return new Result(ResultCodeFailDuplicate, description); } /// <summary> /// Gets a new instance of Not-found failed result with description. /// </summary> /// <param name="description">reason of failure</param> /// <returns>Result instance</returns> public static Result Fail_NotFound(string description) { return new Result(ResultCodeFailNone, description); } /// <summary> /// Gets a new instance of Insert failed result with description. /// </summary> /// <param name="description">reason of failure</param> /// <returns>Result instance</returns> public static Result Fail_Insert(string description) { return new Result(ResultCodeFailInsert, description); } /// <summary> /// Gets a new instance of Parse failed result with description. /// </summary> /// <param name="description">reason of failure</param> /// <returns>Result instance</returns> public static Result Fail_Parse(string description) { return new Result(ResultCodeFailParse, description); } #endregion }

 

同步命令反馈数据包的编程描述

 

public struct ResponseData<T> { /// <summary>Command string</summary> public string cmd; /// <summary>Command data</summary> public T data; /// <summary>Command response</summary> public Result response; }

 

异步命令反馈数据包的编程描述

 

public struct ResponseDataAsync<T> { /// <summary>Command content</summary> public CmdAsync cmd; /// <summary>Command data</summary> public T data; /// <summary>Command response</summary> public Result response; }

 

 

数据包序列化( Serialization

我们采用 JSON进行序列化。

C#编程中,我们采用 Json.NET这个类库来实施 JSON序列化。 Json.Net是一个发布在 Codeplex.com上的开源代码库,其许可协议是 MIT

JSON现在的应用非常广泛,因此开源的类库也非常丰富, Java C/C++ Objective C Python PHP等实现版本很容易找到。

JSON这种序列化方式和传统的 Web Service中广泛应用的 XML序列化方式相比,有以下优势:

·         序列化后的字符串格式更简单,更短;

·         生成与解析更轻量;

·         Web前端的 Ajax技术更兼容,甚至可以被 JavaScript直接识别。

除了 JSON这种序列化方式以外,另外一种同样具备竞争力的序列化方式是 Hessian binary web service protocol(简称 Hessian协议)中采用的序列化方式。对于 Hessian协议,在其官方网站 http://hessian.caucho.com 上,给出了多种语言版本的服务端、客户端开源实现,包括 Java .Net/C# Python PHP Objective C等众多版本。

 

通讯协议( Network Communication Protocol

当前项目中采用的通讯协议是 HTTP(S)。未来还将补充使用 TCP Socket通讯。

HTTP(S)的优点:

·         编程简单,且在绝大多数情况下不需要程序员自己考虑多线程并发的情况;

·         穿越防火墙的能力好;

HTTP(S)的缺点:

·         只能从客户端主动向服务端发起,无法从服务端主动向客户端 Push消息;

 

TCP Socket通讯的优点:

·         通讯方式灵活,服务端可主动向客户端 Push消息;

TCP Socket通讯的缺点:

·         编程稍复杂,且在绝大多数情况下需要程序员自己考虑多线程并发的情况;

·         如在公网上应用,很容易被防火墙阻拦;

 

即使采用 TCP Socket通讯,建议仍然保留 HTTP(S)通讯。可以用 TCP Socket通讯来传递消息通知,而用 HTTP(S)通讯来读取消息内容。

 

HTTP(S)通讯中,我们广泛使用 POST方式来发送和接收数据。服务端的示范代码如下:

 

<%@ Page Language="C#" EnableSessionState="false" ResponseEncoding="utf-8" %> <%@ Import Namespace="System.IO" %> <% Stream inputStream = HttpContext.Current.Request.InputStream; StreamReader reader = new StreamReader(inputStream, System.Text.Encoding.UTF8); string responseContent = reader.ReadToEnd(); Response.ContentType = "text/plain"; Response.Write(responseContent); %>

 

以上就是协议设计。

 

 

你可能感兴趣的:(编程,String,struct,serialization,平台,通讯)