Socket开发框架之数据传输协议

我在前面一篇随笔《Socket开发框架之框架设计及分析》中,介绍了整个Socket开发框架的总体思路,对各个层次的基类进行了一些总结和抽象,已达到重用、简化代码的目的。本篇继续分析其中重要的协议设计部分,对其中消息协议的设计,以及数据的拆包和封包进行了相关的介绍,使得我们在更高级别上更好利用Socket的特性。

1、协议设计思路

对Socket传输消息的封装和拆包,一般的Socket应用,多数采用基于顺序位置和字节长度的方式来确定相关的内容,这样的处理方式可以很好减少数据大小,但是这些处理对我们分析复杂的协议内容,简直是一场灾难。对跟踪解决过这样协议的开发人员来说会很好理解其中的难处,协议位置一旦变化或者需要特殊的处理,就是很容易出错的,而且大多数代码充斥着很多位置的数值变量,分析和理解都是非常不便的。随着网络技术的发展,有时候传输的数据稍大一点,损失一些带宽来传输数据,但是能成倍提高开发程序的效率,是我们值得追求的目标。例如,目前Web API在各种设备大行其道,相对Socket消息来说,它本身在数据大小上不占优势,但是开发的便利性和高效性,是众所周知的。

借鉴了Web API的特点来考虑Socket消息的传输,如果对于整体的内容,Socket应用也使用一种比较灵活的消息格式,如JSON格式来传输数据,那么我们可以很好的把消息封装和消息拆包解析两个部分,交给第三方的JSON解析器来进行,我们只需要关注具体的消息处理逻辑就可以了,而且对于协议的扩展,就如JSON一样,可以自由灵活,这样瞬间,整个世界都会很清静了。

对于Socket消息的安全性和完整性,加密处理方面我们可以采用 RSA公钥密码系统。平台通过发送平台RSA公钥消息向终端告知自己的RSA公钥,终端回复终端RSA公钥消息,这样平台和终端的消息,就可以通过自身的私钥加密,让对方根据接收到的公钥解密就可以了,虽然加密的数据长度会增加不少,但是对于安全性要求高的,采用这种方式也是很有必要的。

对于数据的完整性,传统意义的CRC校验码其实没有太多的用处了,因为我们的数据不会发生部分的丢失,而我们更应该关注的是数据是否被篡改过,这点我想到了微信公众号API接口的设计,它们带有一个安全签名的加密字符串,也就是对其中内容进行同样规则的加密处理,然后对比两个签名内容是否一致即可。不过对于非对称的加密传输,这种数据完整性的校验也可以不必要。

前面介绍了,我们可以参照Web API的方式,以JSON格式作为我们传输的内容,方便序列号和反序列化,这样我们可以大大降低Socket协议的分析难度和出错几率,降低Socket开发难度并提高开发应用的速度。那么我们应该如何设计这个格式呢?

首先我们需要为Socket消息,定义好开始标识和结束标识,中间部分就是整个通用消息的JSON内容。这样,一条完整的Socket消息内容,除了开始和结束标识位外,剩余部分是一个JSON格式的字符串数据。

我们准备根据需要,设计好整个JSON字符串的内容,而且最好设计的较为通用一些,这样便于我们承载更多的数据信息。

2、协议设计分析和演化

参考微信的API传递消息的定义,我设计了下面的消息格式,包括了送达用户ID,发送用户ID、消息类型、创建时间,以及一个通用的内容字段,这个通用的字段应该是另外一个消息实体的JSON字符串,这样我们整个消息格式不用变化,但是具体的内容不同,我们把这个对象类称之BaseMessage,常用字段如下所示。

上面的Content字段就是用来承载具体的消息数据的,它会根据不同的消息类型,传送不同的内容的,而这些内容也是具体的实体类序列化为JSON字符串的,我们为了方便,也设计了这些类的基类,也就是Socket传递数据的实体类基类BaseEntity。

我们在不同的请求和应答消息,都继承于它即可。我们为了方便让它转换为我们所需要的BaseMessage消息,为它增加一个MsgType协议类型的标识,同时增加PackData的方法,让它把实体类转换为JSON字符串。

例如我们一般情况下的请求Request和应答Response的消息对象,都是继承自BaseEntity的,我们可以把这两类消息对象放在不同的目录下方便管理。

继承关系示例如下所示。

其中子类都可以使用基类的PackData方法,直接序列号为JSON字符串即可,那个PacketData的函数主要就是用来组装好待发送的对象BaseMessage的,函数代码如下所示:

        /// <summary>
        /// 封装数据进行发送
        /// </summary>
        /// <returns></returns>
        public BaseMessage PackData()
        {
            BaseMessage info = new BaseMessage()
            {
                MsgType = this.MsgType,
                Content = this.SerializeObject()
            };
            return info;
        }

有时候我们需要根据请求的信息,用来构造返回的应答消息,因为需要把发送者ID和送达者ID逆反过来。

        /// <summary>
        /// 封装数据进行发送(复制请求部分数据)
        /// </summary>
        /// <returns></returns>
        public BaseMessage PackData(BaseMessage request)
        {
            BaseMessage info = new BaseMessage()
            {
                MsgType = this.MsgType,
                Content = this.SerializeObject(),
                CallbackID = request.CallbackID
            };

            if(!string.IsNullOrEmpty(request.ToUserId))
            {
                info.ToUserId = request.FromUserId;
                info.FromUserId = request.ToUserId;
            }

            return info;
        }

以登陆请求的数据实体对象介绍,它继承自BaseEntity,同时指定好对应的消息类型即可。

    /// <summary>
    /// 登陆请求消息实体
    /// </summary>
    public class AuthRequest : BaseEntity
    {
        #region 字段信息

        /// <summary>
        /// 用户帐号
        /// </summary>
        public string UserId { get; set; }

        /// <summary>
        /// 用户密码
        /// </summary>
        public string Password { get; set; }

        #endregion

        /// <summary>
        /// 默认构造函数
        /// </summary>
        public AuthRequest()
        {
            this.MsgType = DataTypeKey.AuthRequest;
        }

        /// <summary>
        /// 参数化构造函数
        /// </summary>
        /// <param name="userid">用户帐号</param>
        /// <param name="password">用户密码</param>
        public AuthRequest(string userid, string password) : this()
        {
            this.UserId = userid;
            this.Password = password;
        }
    }

这样我们的消息内容就很简单,方便我们传递及处理了。

3、消息的接收和发送

前面我们介绍过了一些基类,包括Socket客户端基类,和数据接收的基类设计,这些封装能够给我提供很好的便利性。

在上面的BaseSocketClient里面,我们为了能够解析不同协议的Socket消息,把它转换为我们所需要的基类对象,那么我们这里引入一个解析器MessageSplitter,这个类主要的职责就是用来分析字节数据,并进行整条消息的提取的。

因此我们把BaseSocketClient的类定义的代码设计如下所示。

    /// <summary>
    /// 基础的Socket操作类,提供连接、断开、接收和发送等相关操作。
    /// </summary>
    /// <typeparam name="TSplitter">对应的消息解析类,继承自MessageSplitter</typeparam>
    public class BaseSocketClient<TSplitter>  where TSplitter : MessageSplitter, new()

MessageSplitter对象,给我们处理低层次的协议解析,前面介绍了我们除了协议头和协议尾标识外,其余部分就是一个JSON的,那么它就需要根据这个规则来实现字节数据到对象级别的转换。

首先需要把字节数据进行拆分,把它完整的一条数据加到列表里面后续进行处理。

其中结尾部分,我们就是需要提取缓存的直接数据到一个具体的对象上了。

RawMessage msg = this.ConvertMessage(MsgBufferCache, from);

这个转换的大概规则如下所示。

这样我们在收到消息后,利用TSplitter对象来进行解析就可以了,如下所示就是对Socket消息的处理。

                    TSplitter splitter = new TSplitter();
                    splitter.InitParam(this.Socket, this.StartByte, this.EndByte);//指定分隔符,用来拆包
                    splitter.DataReceived += splitter_DataReceived;//如果有完整的包处理,那么通过事件通知

数据接收并获取一条消息的直接数据对象后,我们就进一步把直接对象转换为具体的消息对象了

        /// <summary>
        /// 消息分拆类收到消息事件
        /// </summary>
        /// <param name="data">原始消息对象</param>
        void splitter_DataReceived(RawMessage data)
        {
            ReceivePackCount += 1;//增加收到的包数量
            OnReadRaw(data);
        }

        /// <summary>
        /// 接收数据后的处理,可供子类重载
        /// </summary>
        /// <param name="data">原始消息对象(包含原始的字节数据)</param>
        protected virtual void OnReadRaw(RawMessage data)
        {
            //提供默认的包体处理:假设整个内容为Json的方式;
            //如果需要处理自定义的消息体,那么需要在子类重写OnReadMessage方法。
            if (data != null && data.Buffer != null)
            {
                var json = EncodingGB2312.GetString(data.Buffer);
                var msg = JsonTools.DeserializeObject<BaseMessage>(json);

                OnReadMessage(msg);//给子类重载
            }
        }

在更高一层的数据解析上面,我们就可以对对象级别的消息进行处理了

例如我们收到消息后,它本身解析为一个实体类BaseMessage的,那么我们就可以利用BaseMessage的消息内容,也可以把它的Content内容转换为对应的实体类进行处理,如下代码所示是接收对象后的处理。

        void TextMsgAnswer(BaseMessage message)
        {
            var msg = string.Format("来自【{0}】的消息:", message.FromUserId);

            var request = JsonTools.DeserializeObject<TextMsgRequest>(message.Content);
            if (request != null)
            {
                msg += string.Format("{0}  {1}", request.Message, message.CreateTime.IntToDateTime());
            }

            //MessageUtil.ShowTips(msg);
            Portal.gc.MainDialog.AppendMessage(msg);
        }

对于消息的发送处理,我们可以举一个例子,如果客户端登陆后,需要获取在线用户列表,那么可以发送一个请求命令,那么服务器需要根据这个命令返回列表信息给终端,如下代码所示。

        /// <summary>
        /// 处理客户端请求用户列表的应答
        /// </summary>
        /// <param name="data">具体的消息对象</param>
        private void UserListProcess(BaseMessage data)
        {
            CommonRequest request = JsonTools.DeserializeObject<CommonRequest>(data.Content);
            if (request != null)
            {
                Log.WriteInfo(string.Format("############\r\n{0}", data.SerializeObject()));

                List<CListItem> list = new List<CListItem>();
                foreach(ClientOfShop client in Singleton<ShopClientManager>.Instance.LoginClientList.Values)
                {
                    list.Add(new CListItem(client.Id, client.Id));
                }

                UserListResponse response = new UserListResponse(list);
                Singleton<ShopClientManager>.Instance.AddSend(data.FromUserId, response.PackData(data), true);
            }
        }

本文转自博客园伍华聪的博客,原文链接:Socket开发框架之数据传输协议,如需转载请自行联系原博主。

时间: 2024-09-16 17:15:29

Socket开发框架之数据传输协议的相关文章

Socket开发框架之数据加密及完整性检查

在前面两篇介绍了Socket框架的设计思路以及数据传输方面的内容,整个框架的设计指导原则就是易于使用及安全性较好,可以用来从客户端到服务端的数据安全传输,那么实现这个目标就需要设计好消息的传输和数据加密的处理.本篇主要介绍如何利用Socket传输协议来实现数据加密和数据完整性校验的处理,数据加密我们可以采用基于RSA非对称加密的方式来实现,数据的完整性,我们可以对传输的内容进行MD5数据的校验对比. 1.Socket框架传输内容分析 前面介绍过Socket的协议,除了起止标识符外,整个内容是一个

tcp-C# 如何使用socket实现基于TCP协议传输数据

问题描述 C# 如何使用socket实现基于TCP协议传输数据 我使用Socket.Send和Socket.BeginReceive完成了上位机通讯软件的编写,但是连接下位机经过分析抓包软件得到的数据包,发现似乎与标准TCP协议有出入.具体来讲是这样的: 首先根据TCP协议,数据交换的规范应该是这样的http://blog.csdn.net/moonhnney/article/details/5604677 数据交换: a ---->b a 发送数据完毕,(PSH,ACK) aseq = x,a

SuperSocket v1.3发布 轻量级可扩展的Socket开发框架

SuperSocket 是一个轻量级的可扩展的 Socket http://www.aliyun.com/zixun/aggregation/13435.html">开发框架,可用来构建一个基于命令的服务器端 Socket 程序,而无需了解如何使用 Socket,如何维护Socket连接,Socket是如何工作的.该项目使用纯 C# 开发,易于扩展和集成到已有的项目.只要你的已有系统(forum/CRM/MIS/HRM/ERP)是使用.NET开发的,你都能够使用 SuperSocket来轻

Socket开发框架之框架设计及分析

虽然在APP应用.Web应用.Winform应用等大趋势下,越来越多的企业趋向于这些应用系统开发,但是Socket的应用在某些场合是很必要的,如一些停车场终端设备的接入,农业或者水利.压力监测方面的设备数据采集等,以及常见的IM(即时通讯,如腾讯QQ.阿里旺旺等)的客户端,都可以采用Socket框架进行相关的数据采集和信息通讯用途的,Socket应用可以做为APP应用.Web应用和Winform应用的补充. 1.Socket应用场景 一般情况下,客户端和服务端进行Socket连接,需要进行数据的

Socket开发框架之数据采集客户端

虽然目前.NET的主流的开发基本上是基于Web方式(传统的Web方式和Silvelight方式).基于Winform方式(传统的Winform模式和WPF方式等).基于服务应用方式(传统的WebService和WCF服务方式)等主要几种开发,另外,还有一种就是基于Socket协议的开发方式,不同于高级服务层的WebService和WCF服务,基于Socket协议开发是较为底层的开发方式,它往往具有更加灵活,可控性更高的优点,不过相对来说,开发难度也会大一些. 我由于工作需要,需要开发一个数据采集

Java基于socket服务实现UDP协议的方法

  本文实例讲述了Java基于socket服务实现UDP协议的方法.分享给大家供大家参考.具体如下: 示例1: 接收类: ? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 package com.socket.demo; import java.io.IOException; import java.net.DatagramPacket; import java.net

《Linux高性能服务器编程》——1.7 socket和TCP/IP协议族的关系

1.7 socket和TCP/IP协议族的关系 前文提到,数据链路层.网络层.传输层协议是在内核中实现的.因此操作系统需要实现一组系统调用,使得应用程序能够访问这些协议提供的服务.实现这组系统调用的API(Application Programming Interface,应用程序编程接口)主要有两套:socket和XTI.XTI现在基本不再使用,本书仅讨论socket.图1-1显示了socket与TCP/IP协议族的关系. 由socket定义的这一组API提供如下两点功能:一是将应用程序数据从

欧美贸易组织催促新数据传输协议洽谈

1月18日消息,据路透社报道,两家最大的美国和欧洲贸易组织警告如果布鲁塞尔和华盛顿在本月底不完成数据传输协议的谈判,上千家企业和上百万名用户将面临"不堪设想"的巨大后果.美国和华盛顿加速了就促使公司能够在大西洋两岸轻松传输个人数据的新框架的协商,去年进行的上一次协商因出于对美国窥听的担忧而被欧盟最高法院否决. 根据欧盟数据保护法律,公司不能将欧盟公民的个人数据传输给被认为不具备足够隐私保护的国家,美国就是其中一员. 自从去年10月6日欧盟最高法庭裁定15年历史的旧安全港框架并没有合理的

Java基于socket服务实现UDP协议的方法_java

本文实例讲述了Java基于socket服务实现UDP协议的方法.分享给大家供大家参考.具体如下: 示例1: 接收类: package com.socket.demo; import java.io.IOException; import java.net.DatagramPacket; import java.net.DatagramSocket; public class UDPReceiveDemo { public static void main(String[] args) throw