Socket开发探秘--数据封包和拆包
在上篇《Socket开发探秘--基类及公共类的定义 》中介绍过,所有受到的数据包,经过系统的预处理后,都会得到一个PreData的数据实体,该实体包含了协议头、协议内容和所属用户的ID。PreData是定义了一个标准的协议数据格式,包含了协议关键字、协议内容、用户标识的内容。
前面说了,我们数据是通过实体类作为载体的,我们知道,收到的Socket数据经过粗略的解析后,就是PreData类型的数据,这个是通用的数据格式,我们需要进一步处理才能转化为所能认识的数据对象(实体类对象),同样,我们发送数据的时候,内容部分肯定是按照一定协议规则串联起来的数据,那么我们就需要把实体转化为发送的数据格式。综上所述,我们通过实体类,必须实现数据的发送和读取的转换。
前面说了,我们数据是通过实体类作为载体的,我们知道,收到的Socket数据经过粗略的解析后,就是PreData类型的数据,这个是通用的数据格式,我们需要进一步处理才能转化为所能认识的数据对象(实体类对象),同样,我们发送数据的时候,内容部分肯定是按照一定协议规则串联起来的数据,那么我们就需要把实体转化为发送的数据格式。综上所述,我们通过实体类,必须实现数据的发送和读取的转换。
代码
/// <summary>
/// 测试数据的实体类信息
/// </summary>
public class TestDataRequest
{
#region MyRegion
/// <summary>
/// 请求序列
/// </summary>
public string seq;
/// <summary>
/// 用户帐号
/// </summary>
public string userid;
/// <summary>
/// 用户密码
/// </summary>
public string psw;
#endregion
public TestDataRequest(string seq, string userid, string psw)
{
this.seq = seq;
this.userid = userid;
this.psw = psw;
}
public TestDataRequest()
{
}
/// <summary>
/// 转换Socket接收到的信息为对象信息
/// </summary>
/// <param name="data">Socket接收到的信息</param>
public TestDataRequest(string data)
{
string[] dataArray = null;
dataArray = NetStringUtil.UnPack(data);
if (dataArray != null && dataArray.Length > 0)
{
TestDataRequest newAnswerData = new TestDataRequest();
int i = 0;
this.seq = dataArray[i++];
this.userid = dataArray[i++];
this.psw = dataArray[i++];
}
}
/// <summary>
/// 转换对象为Socket发送格式的字符串
/// </summary>
/// <returns></returns>
public override string ToString()
{
string data = "";
data = this.seq + "|" + this.userid + "|" + this.psw.ToString();
data = NetStringUtil.PackSend(DataTypeKey.TestDataRequest, data);
return data;
}
/// 测试数据的实体类信息
/// </summary>
public class TestDataRequest
{
#region MyRegion
/// <summary>
/// 请求序列
/// </summary>
public string seq;
/// <summary>
/// 用户帐号
/// </summary>
public string userid;
/// <summary>
/// 用户密码
/// </summary>
public string psw;
#endregion
public TestDataRequest(string seq, string userid, string psw)
{
this.seq = seq;
this.userid = userid;
this.psw = psw;
}
public TestDataRequest()
{
}
/// <summary>
/// 转换Socket接收到的信息为对象信息
/// </summary>
/// <param name="data">Socket接收到的信息</param>
public TestDataRequest(string data)
{
string[] dataArray = null;
dataArray = NetStringUtil.UnPack(data);
if (dataArray != null && dataArray.Length > 0)
{
TestDataRequest newAnswerData = new TestDataRequest();
int i = 0;
this.seq = dataArray[i++];
this.userid = dataArray[i++];
this.psw = dataArray[i++];
}
}
/// <summary>
/// 转换对象为Socket发送格式的字符串
/// </summary>
/// <returns></returns>
public override string ToString()
{
string data = "";
data = this.seq + "|" + this.userid + "|" + this.psw.ToString();
data = NetStringUtil.PackSend(DataTypeKey.TestDataRequest, data);
return data;
}
以上把数据的处理放在了实体类中进行封包和拆包,是一种比较好的做法,但是由于数据的封包拆包是一个繁琐的过程,代码重复性比较多,而且也容易出错。
这里设计了一个基类,来改进这种方式的数据处理,我们把所有对数据的拆包和封包,利用反射机制,减少我们的代码量,提高代码的优雅性。
代码
public class BaseEntity
{
protected string HeaderKey;
public BaseEntity()
{
}
/// <summary>
/// 转换Socket接收到的信息为对象信息
/// </summary>
/// <param name="data">Socket接收到的信息</param>
public BaseEntity(string data)
{
string[] dataArray = null;
dataArray = NetStringUtil.UnPack(data);
if (dataArray != null && dataArray.Length > 0)
{
int i = 0;
FieldInfo[] fieldArray = ReflectionUtil.GetFields(this);
if (fieldArray == null || dataArray.Length != fieldArray.Length)
{
throw new ArgumentException("收到的信息和字段信息不一致");
}
if (fieldArray != null)
{
foreach (FieldInfo info in fieldArray)
{
string strValue = dataArray[i++];
ReflectionUtil.SetField(this, info.Name, strValue);
}
}
}
}
/// <summary>
/// 转换对象为Socket发送格式的字符串
/// </summary>
/// <returns></returns>
public override string ToString()
{
string data = "";
FieldInfo[] fieldArray = ReflectionUtil.GetFields(this);
StringBuilder sb = new StringBuilder();
if (fieldArray != null)
{
foreach (FieldInfo info in fieldArray)
{
sb.Append(ReflectionUtil.GetField(this, info.Name));
sb.Append("|");
}
}
data = sb.ToString().Trim('|');
if (string.IsNullOrEmpty(HeaderKey))
{
throw new ArgumentNullException("DataTypeKey", "实体类未指定协议类型");
}
data = NetStringUtil.PackSend(HeaderKey, data);
return data;
}
}
{
protected string HeaderKey;
public BaseEntity()
{
}
/// <summary>
/// 转换Socket接收到的信息为对象信息
/// </summary>
/// <param name="data">Socket接收到的信息</param>
public BaseEntity(string data)
{
string[] dataArray = null;
dataArray = NetStringUtil.UnPack(data);
if (dataArray != null && dataArray.Length > 0)
{
int i = 0;
FieldInfo[] fieldArray = ReflectionUtil.GetFields(this);
if (fieldArray == null || dataArray.Length != fieldArray.Length)
{
throw new ArgumentException("收到的信息和字段信息不一致");
}
if (fieldArray != null)
{
foreach (FieldInfo info in fieldArray)
{
string strValue = dataArray[i++];
ReflectionUtil.SetField(this, info.Name, strValue);
}
}
}
}
/// <summary>
/// 转换对象为Socket发送格式的字符串
/// </summary>
/// <returns></returns>
public override string ToString()
{
string data = "";
FieldInfo[] fieldArray = ReflectionUtil.GetFields(this);
StringBuilder sb = new StringBuilder();
if (fieldArray != null)
{
foreach (FieldInfo info in fieldArray)
{
sb.Append(ReflectionUtil.GetField(this, info.Name));
sb.Append("|");
}
}
data = sb.ToString().Trim('|');
if (string.IsNullOrEmpty(HeaderKey))
{
throw new ArgumentNullException("DataTypeKey", "实体类未指定协议类型");
}
data = NetStringUtil.PackSend(HeaderKey, data);
return data;
}
}
以上的是实体类的基类,它封装了数据的拆包和封包过程,只需要在子类代码中指定协议头就可以了。子类的代码如下所示。
代码
/// <summary>
/// 测试请求
/// </summary>
public class TestDataRequest : BaseEntity
{
#region 字段信息
/// <summary>
/// 请求序列
/// </summary>
public string Seq;
/// <summary>
/// 用户帐号
/// </summary>
public string UserId;
/// <summary>
/// 用户密码
/// </summary>
public string Password;
/// <summary>
/// 消息时间
/// </summary>
public DateTime CreateDate = DateTime.Now;
#endregion
public TestDataRequest()
{
this.HeaderKey = DataTypeKey.TestDataRequest;
}
public TestDataRequest(string seq, string userid, string psw)
{
this.Seq = seq;
this.UserId = userid;
this.Password = psw;
this.HeaderKey = DataTypeKey.TestDataRequest;
}
/// <summary>
/// 转换Socket接收到的信息为对象信息
/// </summary>
/// <param name="data">Socket接收到的信息</param>
public TestDataRequest(string data) : base(data)
{
this.HeaderKey = DataTypeKey.TestDataRequest;
}
}
/// 测试请求
/// </summary>
public class TestDataRequest : BaseEntity
{
#region 字段信息
/// <summary>
/// 请求序列
/// </summary>
public string Seq;
/// <summary>
/// 用户帐号
/// </summary>
public string UserId;
/// <summary>
/// 用户密码
/// </summary>
public string Password;
/// <summary>
/// 消息时间
/// </summary>
public DateTime CreateDate = DateTime.Now;
#endregion
public TestDataRequest()
{
this.HeaderKey = DataTypeKey.TestDataRequest;
}
public TestDataRequest(string seq, string userid, string psw)
{
this.Seq = seq;
this.UserId = userid;
this.Password = psw;
this.HeaderKey = DataTypeKey.TestDataRequest;
}
/// <summary>
/// 转换Socket接收到的信息为对象信息
/// </summary>
/// <param name="data">Socket接收到的信息</param>
public TestDataRequest(string data) : base(data)
{
this.HeaderKey = DataTypeKey.TestDataRequest;
}
}
下面的代码是收到数据包,利用实体类构造函数,解析为实体类的操作,以及构造实体类,通过ToString()方式把实体类信息转化为可以发送的数据包的操作。
代码
private void TestDataHandle(PreData data)
{
TestDataRequest request = new TestDataRequest(data.Content);
Log.WriteInfo(string.Format("############{0}", request.ToString()));
TestDataAnswerData answerData = new TestDataAnswerData(request.Seq, request.UserId, request.Password);
ShopClientManager.This.AddSend(data.UserId, answerData.ToString(), true);
}
{
TestDataRequest request = new TestDataRequest(data.Content);
Log.WriteInfo(string.Format("############{0}", request.ToString()));
TestDataAnswerData answerData = new TestDataAnswerData(request.Seq, request.UserId, request.Password);
ShopClientManager.This.AddSend(data.UserId, answerData.ToString(), true);
}
我编写的测试例子中,实体类的继承图如下所示。
专注于代码生成工具、.Net/.NetCore 框架架构及软件开发,以及各种Vue.js的前端技术应用。著有Winform开发框架/混合式开发框架、微信开发框架、Bootstrap开发框架、ABP开发框架、SqlSugar开发框架等框架产品。
转载请注明出处:撰写人:伍华聪 http://www.iqidi.com
转载请注明出处:撰写人:伍华聪 http://www.iqidi.com