Socket开发探秘--数据封包和拆包
在上篇《
Socket开发探秘--基类及公共类的定义
》中介绍过,所有受到的数据包,经过系统的预处理后,都会得到一个PreData的数据实体,该实体包含了协议头、协议内容和所属用户的ID。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;
}
以上把数据的处理放在了实体类中进行封包和拆包,是一种比较好的做法,但是由于数据的封包拆包是一个繁琐的过程,代码重复性比较多,而且也容易出错。
这里设计了一个基类,来改进这种方式的数据处理,我们把所有对数据的拆包和封包,利用反射机制,减少我们的代码量,提高代码的优雅性。
代码
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;
}
}
以上的是实体类的基类,它封装了数据的拆包和封包过程,只需要在子类代码中指定协议头就可以了。子类的代码如下所示。
代码
///
<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;
}
}
下面的代码是收到数据包,利用实体类构造函数,解析为实体类的操作,以及构造实体类,通过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
);
}
我编写的测试例子中,实体类的继承图如下所示。