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 += ;//增加收到的包数量
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开发框架之数据传输协议的更多相关文章
- Socket开发框架之数据加密及完整性检查
在前面两篇介绍了Socket框架的设计思路以及数据传输方面的内容,整个框架的设计指导原则就是易于使用及安全性较好,可以用来从客户端到服务端的数据安全传输,那么实现这个目标就需要设计好消息的传输和数据加 ...
- Socket层上的协议
Socket层上的协议指的数据传输的格式 HTTP协议 传输格式:假设:这是假设,实际http的格式不是这样的. http1.1,content-type:multipart/form-data,co ...
- socket编程的网络协议
"我们在传输数据时,可以只使用(传输层)TCP/IP协议,但是那样的话,如果没有应用层,便无法识别数据内容" TCP/IP只是一个协议栈,就像程序运行一样,必须要实现运行,同时还要 ...
- 哈工大 计算机网络 实验二 可靠数据传输协议(停等协议与GBN协议)
计算机网络实验代码与文件可见github:计算机网络实验整理 实验名称 可靠数据传输协议(停等协议与GBN协议) 实验目的: 本次实验的主要目的. 理解可靠数据传输的基本原理:掌握停等协议的工作原理: ...
- [深入浅出WP8.1(Runtime)]Socket编程之UDP协议
13.3 Socket编程之UDP协议 UDP协议和TCP协议都是Socket编程的协议,但是与TCP协议不同,UDP协议并不提供超时重传,出错重传等功能,也就是说其是不可靠的协议.UDP适用于一次只 ...
- 网络编程—网络基础概览、socket,TCP/UDP协议
网络基础概览 socket概览 socket模块—TCP/UDP的实现 TCP/UDP总结 网络基础概览 osi七层协议各层主要的协议 # 物理层传输电信号1010101010 # 数据链路层,以太网 ...
- DDTP 分布式数据传输协议白皮书
声明 本文非本人原创,主要参考文献[1]编写的阅读笔记.本博客仅发表在博客园,作者LightningStar,其他平台均为转载. 摘要 本白皮书对全球现有主要个人信息可携带权的实践模式进行梳理,分析其 ...
- 网络协议之:基于UDP的高速数据传输协议UDT
目录 简介 UDT协议 UDT的缺点 总结 简介 简单就是美.在网络协议的世界中,TCP和UDP是建立在IP协议基础上的两个非常通用的协议.我们现在经常使用的HTTP协议就是建立在TCP协议的基础上的 ...
- 揭秘Socket与底层数据传输实现
揭秘socket 什么是socket?socket字面意思其实就是一个插口或者套接字,包含了源ip地址.源端口.目的ip地址和源端口.但是socket在那个位置呢 ,在TCP/IP网络的四层体系和OS ...
随机推荐
- Linux xargs将输出数据流转换成命令参数
200 ? "200px" : this.width)!important;} --> 介绍 我们可以利用管道将一个命令的“标准输出”作为另一个命令的“标准输入”:但是这里的 ...
- java线程与并发(二)
一般而言,线程通常有以下的这么几个状态: 创建状态:准备好了一个多线程操作对象 就绪状态:调用了start()方法,等待CPU调度 运行状态:执行run()方法,正在运行 阻塞状态:暂时停止执行,把资 ...
- 学习Scala第一篇-从hello World开始
最近开始系统性的学习scala.其实之前使用过scala的,比如我在用Gatling这款性能测试工具的时候就接触到了scala了.Gatling本身就是用Scala写的,而且Gatling的性能测试配 ...
- Java使用snakeyaml解析yaml
YAML Yaml是一种"是一个可读性高并且容易被人类阅读,容易和脚本语言交互,用来表达资料序列的编程语言."类似于XML但比XML更简洁,语法详见 http://www.ruan ...
- swift 创建单例模式
一.意图 保证一个类公有一个实例,并提供一个访问它的全局访问点. 二.使用场景 1.使用场景 当类只能有一个实例而且客户可以从一个众所周知的访问点访问它时 当这个唯一实例应该是通过子类化可扩展的,并且 ...
- GUID相关知识
全局唯一标识符(GUID,Globally Unique Identifier)是一种由算法生成的二进制长度为128位的数字标识符.GUID主要用于在拥有多个节点.多台计算机的网络或系统中.在理想 ...
- Zabbix监控系统深度实践
Zabbix监控系统深度实践(企业级分布式系统自动化运维必选利器,大规模Zabbix集群实战经验技巧总结,由浅入深全面讲解配置.设计.案例和内部原理) 姚仁捷 著 ISBN 978-7-121-24 ...
- 大型网站系统与Java中间件实践
大型网站系统与Java中间件实践(贯通分布式高并发高数据高访问量网站架构与实现之权威著作,九大一线互联网公司CTO联合推荐) 曾宪杰 著 ISBN 978-7-121-22761-5 2014年4 ...
- fir.im Weekly - 从零开始创建 Android 新项目
今年的 Google I/O 大会上,人工智能和虚拟现实的产品发布让我们对未来多了几分惊喜.对于开发者部分,Google 发布了 Android N 系统,感受最深的是全新的 Android Stud ...
- Jquery实现一组复选框单选
完整代码: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w ...