前些天看到江大鱼发布了SuperSocket 1.4正式版,由于最近项目使用到了网口的通讯编程,于是,也查看了下SuperSocket源代码,发现架构还是很清晰的,并且易于扩展,具体中文文档详见:http://www.cnblogs.com/jzywh/archive/2011/04/19/SuperSocketDocument.html。
今天我要说的是,如果通过SuperSocket自定义一个应用协议。由于项目中的服务通过TLV的协议进行数据传输,而通过网口是其中的一个通讯方式,当然项目中还包括通过串口、USB设备、USB数据线等等进行数据传输。其中网口通讯,采用TCP通讯的方式。什么是TLV呢,可以参考一下我之前写的一篇文章:《金融系统中PBOC/EMV的TLV的算法实现》,里面对于TLV有了详细的介绍,这里不再敖述。由于项目中的TLV协议还要涉及到一些报文头以及校验码的考虑,本篇文章将跳过这些考虑,直接谈核心的数据包TLV的传输。
具体思路
1、创建一个解决方案TLVSocketDemo。
2、创建项目TLVSocketDemo.Server,这个项目主要作为服务端的基本类库,包含一些AppServer,AppSession的扩展实现。当然SuperSocket 提供了一个名叫"SocketService"的项目,它是一个能够让AppServer运行于其中的容器。于是需要把SocketService相关的一些文件复制到生成输出目录中。
3、AppServer的子类实现:
public
class
TLVProtocolServer : AppServer
{
public
TLVProtocolServer()
:
base
(
new
TLVProtocol())
{
}
}
4、AppSession的子类实现:
public
class
TLVProtocolSession : AppSession
{
public
override
void
HandleExceptionalError(Exception e)
{
}
}
5、TLVEntityListCommandInfo是服务器端收到的一个命令实体类,这个类必须继承于CommandInfo:
public
class
TLVEntityListCommandInfo : CommandInfo
>
{
public
TLVEntityListCommandInfo(
string
key, List data)
:
base
(key, data)
{
}
}
实际上,TLVEntity就是一个TLV的实体结构。TLVEntity类的实现:
[Serializable]
///
///
TLV实体
///
public
class
TLVEntity
{
///
///
标记
///
public
byte
[] Tag {
get
;
set
; }
///
///
数据长度
///
public
byte
[] Length {
get
;
set
; }
///
///
数据
///
public
byte
[] Value {
get
;
set
; }
///
///
标记占用字节数
///
public
int
TagSize {
get
;
set
; }
///
///
数据长度占用字节数
///
public
int
LengthSize {
get
;
set
; }
///
///
子嵌套TLV实体
///
public
TLVEntity Sub_TLVEntity {
get
;
set
; }
}
6、自定义协议:TLVProtocol.cs,继承于ICustomProtocol的范型接口:
public
class
TLVProtocol : ICustomProtocol
{
public
ICommandReader CreateCommandReader(IAppServer appServer)
{
return
new
TLVCommandReader(appServer);
}
}
通过创建(CreateCommandReader方法)来得到字节数组数据的的读取方式。
而TLVCommandReader主要就是作为进行命令解析。
public
class
TLVCommandReader : CommandReaderBase
{
public
TLVCommandReader(IAppServer appServer)
:
base
(appServer)
{
}
public
override
TLVEntityListCommandInfo FindCommandInfo(IAppSession session,
byte
[] readBuffer,
int
offset,
int
length,
bool
isReusableBuffer,
out
int
left)
{
left
=
0
;
AddArraySegment(readBuffer, offset, length, isReusableBuffer);
byte
[] source
=
BufferSegments.ToArrayData();
List list
=
TLVPackage.Construct(source);
return
new
TLVEntityListCommandInfo(
"
ECHO
"
, list);
}
}
}
注意到FindCommandInfo方法可以对字节数组数据进行读取以及构造TLV实体。
TLVPackage.Construct的实现在上一篇文章有提到。
7、编写命令类:
public
class
ECHO : CommandBase
{
public
override
void
ExecuteCommand(TLVProtocolSession session, TLVEntityListCommandInfo commandInfo)
{
string
data
=
BinaryUtil.BinarySerialize(commandInfo.Data);
session.SendResponse(data);
}
}
这里发送给客户端的数据,会先将List<Entity>列表通过序列化,并且再进行Base64编码。通过SendResponse发送到客户端。
8、修改SuperSocket.SocketService.exe.configTLV服务的相关配置。
9、最后将项目生成后,项目的路径输出到SuperSocket.SocketServer.exe对应目录中。至此编译通过以后,TLV服务的服务端已经写好了。是不是很容易呢。
10、接着创建一个TLVSocketDemo.Client的客户端程序:
static
int
port
=
911
;
static
void
Main(
string
[] args)
{
EndPoint serverAddress
=
new
IPEndPoint(IPAddress.Parse(
"
127.0.0.1
"
), port);
using
(Socket socket
=
new
Socket(serverAddress.AddressFamily, SocketType.Stream, ProtocolType.Tcp))
{
socket.Connect(serverAddress);
byte
[] data
=
new
byte
[
0
];
using
(FileStream stream
=
new
FileStream(Application.StartupPath
+
"
\\tlvdata.dat
"
, FileMode.Open, FileAccess.Read))
{
using
(BinaryReader reader
=
new
BinaryReader(stream))
{
reader.BaseStream.Seek(
0
, SeekOrigin.Begin);
data
=
reader.ReadBytes((
int
)reader.BaseStream.Length);
}
}
socket.Send(data, data.Length, SocketFlags.None);
byte
[] temp
=
new
byte
[
5000
];
int
read
=
socket.Receive(temp,
0
,
5000
, SocketFlags.None);
byte
[] response
=
new
byte
[read];
Array.Copy(temp,
0
, response,
0
, read);
string
responseText
=
Encoding.ASCII.GetString(response);
List list
=
BinaryUtil.BinaryDeserialize
>
(responseText);
Console.WriteLine(
"
服务端已经构造{0}个TLV结构.
"
, list.Count);
Console.ReadKey();
}
}
这里说明有一下,通过tlvdata.dat文件读取字节数组数据,然后发送数据到服务端,服务端处理完以后,回发给客户端,返回的是一个Base64编码。然后编译Client的代码,通过完成。
11、最后运行程序,首先运行SuperSocket.SocketService的服务,直接可以点开RunServer.bat,或者也可以通过InstallService.bat进行Windows服务的安装;接着运行客户端程序TLVSocketDemo.Client:
Server端:
Client端:
说明服务端对TLV字节数组数据已经构造了2个TLV结构。通过ExecuteCommand的参数可以得到CommandInfo的Data数据,它就是List<TLVEntity>。
总结:SuperSocket的自定义协议的扩展还是很容易使用的,大家也可以通过该扩展方式自定义你自己的应用协议。
附上Server和Client的Demo源代码:TLVSocketDemo.rar