Socket开发探秘--基类及公共类的定义
Socket开发是属于通信底层的开发,.NET也提供了非常丰富的类来实现Socket的开发工作,本篇不是介绍这些基础类的操作,而是从一个大的架构方面阐述Socket的快速开发工作,本篇以TCP模式进行程序的开发介绍,以期达到抛砖引玉的目的。
要掌握或者了解Socket开发,必须了解下面所述的场景及知识。
1、TCP客户端,连接服务器端,进行数据通信
2、TCP服务器端,负责侦听客户端连接
3、连接客户端的管理,如登陆,注销等,使用独立线程处理
4、数据接收管理,负责数据的接受,并处理队列的分发,使用独立线程处理,简单处理后叫给“数据处理线程”
5、数据处理线程,对特定的数据,采用独立的线程进行数据处理
6、数据的封包和解包,按照一定的协议进行数据的封装和解包
针对以上内容,可以封装以下功能的操作类作为共用基类:
1、BaseSocketClient,客户端基类
2、BaseSocketServer,TCP服务器管理基类
3、BaseClientManager,连接客户端管理类
4、BaseReceiver,数据接收处理类
5、ThreadHandler,数据独立线程处理类
6、PreData、DataTypeKey、Sign分别是定义数据的基础格式、协议标识、分隔符号等,另外我们定义需要发送的实体类信息,发送和接收通过实体类进行数据转换和解析。
以上类是基类,不能直接使用,在服务器端和客户端都要继承相应的类来完成所需要的工作。
BaseSocketClient只要负责客户端的链接、断开、发送、接收等操作,大致的定义如下:
代码
public
class
BaseSocketClient
{
public
BaseSocketClient()
{
_Name
=
this
.GetType().Name;
}
public
BaseSocketClient(Socket socket) :
this
()
{
_socket
=
socket;
IPEndPoint ipAndPort
=
(IPEndPoint)socket.RemoteEndPoint;
_IP
=
ipAndPort.Address.ToString();
_port
=
ipAndPort.Port;
}
///
<summary>
///
断开连接
///
</summary>
public
virtual
void
DisConnect()
{
.........
}
///
<summary>
///
主动连接
///
</summary>
public
virtual
void
Connect(
string
ip,
int
port)
{
........
}
///
<summary>
///
开始异步接收
///
</summary>
public
void
BeginReceive()
{
.........
}
///
<summary>
///
开始同步接收
///
</summary>
public
void
StartReceive()
{
.........
}
///
<summary>
///
异步发送
///
</summary>
public
void
BeginSend(SendStateObject sendState)
{
........
}
///
<summary>
///
同步发送。直接返回成功失败状态
///
</summary>
public
bool
SendTo(
string
data)
{
.........
}
///
<summary>
///
主动检查连接
///
</summary>
public
virtual
void
CheckConnect()
{
.............
}
protected
virtual
void
OnRead(PreData data)
{
}
}
2、BaseSocketServer,TCP服务器管理基类
该类负责在独立的线程中侦听指定的端口,如果有客户端连接进来,则进行相应的处理,重载处理函数可以实现独立的处理。大致的定义如下。
代码
public
class
BaseSocketServer
{
public
BaseSocketServer()
{
this
._SocketName
=
this
.GetType().Name;
}
///
<summary>
///
启动监听线程
///
</summary>
public
void
StartListen(
string
ip,
int
port)
{
_IP
=
ip;
_port
=
port;
if
(_listenThread
==
null
)
{
_listenThread
=
new
Thread(Listen);
_listenThread.IsBackground
=
true
;
_listenThread.Start();
}
}
///
<summary>
///
检查监听线程
///
</summary>
public
void
CheckListen()
{
if
(_listenThread
==
null
||
(
!
_listenThread.IsAlive))
{
_listenThread
=
new
Thread(Listen);
_listenThread.IsBackground
=
true
;
_listenThread.Start();
}
}
///
<summary>
///
监听线程
///
</summary>
protected
virtual
void
Listen()
{
IPEndPoint ipAndPort
=
new
IPEndPoint(IPAddress.Parse(IP), Port);
TcpListener tcpListener
=
new
TcpListener(ipAndPort);
tcpListener.Start(
50
);
//
配置
while
(
true
)
{
Socket socket
=
tcpListener.AcceptSocket();
AcceptClient(socket);
}
}
///
<summary>
///
接收一个Client
///
</summary>
protected
virtual
void
AcceptClient(Socket socket)
{
}
3、BaseClientManager,连接客户端管理类
由于考虑性能的影响,客户端对象的管理交给一个独立的线程进行处理,一则处理思路清晰,二则充分利用线程的性能。该类主要负责客户端登录超时处理,连接上来的客户端维护,经过登陆验证的客户端维护,客户端登陆验证接口,客户端发送数据处理等功能。
代码
public
class
BaseClientManager
<
T
>
where
T : BaseSocketClient
{
#region
登陆管理
protected
string
_Name
=
"
BaseClientManager
"
;
private
int
_SessionId
=
0
;
private
object
_LockSession
=
new
object
();
private
System.Threading.Timer _CheckInvalidClientTimer
=
null
;
//
检查客户端连接timer
private
System.Threading.Timer _SendTimer
=
null
;
//
发送数据调用timer
///
<summary>
///
已经注册的客户端 关键字userid
///
</summary>
protected
SortedList
<
string
, T
>
_loginClientList
=
new
SortedList
<
string
, T
>
();
///
<summary>
///
连上来的客户端 未注册 关键字Session
///
</summary>
protected
SortedList
<
string
, T
>
_tempClientList
=
new
SortedList
<
string
, T
>
();
///
<summary>
///
构造函数
///
</summary>
public
BaseClientManager()
{
this
._Name
=
this
.GetType().Name;
}
///
<summary>
///
已经注册的客户端 关键字userid
///
</summary>
public
SortedList
<
string
, T
>
LoginClientList
{
get
{
return
_loginClientList; }
set
{ _loginClientList
=
value; }
}
///
<summary>
///
增加一个连上来(未注册)的客户端
///
</summary>
///
<param name="client"></param>
public
void
AddClient(T client)
{
......
}
///
<summary>
///
增加一个已登录的客户端
///
</summary>
public
void
AddLoginClient(T client)
{
......
}
///
<summary>
///
当客户端登陆,加入列表后的操作
///
</summary>
///
<param name="client"></param>
protected
virtual
void
OnAfterClientSignIn(T client)
{
}
///
<summary>
///
验证登录
///
</summary>
public
virtual
bool
CheckClientLogin(
string
userId,
string
psw,
ref
string
memo)
{
return
false
;
}
///
<summary>
///
电召客户端登出
///
</summary>
///
<param name="userId"></param>
public
void
ClientLogout(
string
userId)
{
if
(_loginClientList.ContainsKey(userId))
{
RadioCallClientLogout(_loginClientList[userId]);
}
}
///
<summary>
///
电召客户端登出
///
</summary>
///
<param name="client"></param>
private
void
RadioCallClientLogout(T client)
{
client.DisConnect();
}
///
<summary>
///
移除注册的客户端
///
</summary>
///
<param name="client"></param>
private
void
RemoveLoginClient(T client)
{
......
}
///
<summary>
///
移除客户端后的操作
///
</summary>
///
<param name="client"></param>
protected
virtual
void
OnAfterClientLogout(T client)
{
}
///
<summary>
///
在连接的列表中移除客户端对象
///
</summary>
///
<param name="client"></param>
public
virtual
void
RemoveClient(T client)
{
RemoveLoginClient(client);
RemoveTempClient(client);
}
#endregion
///
<summary>
///
开始客户端连接处理
///
</summary>
public
void
Start()
{
StartSendTimer();
StartCheckInvalidClientTimer();
}
///
<summary>
///
启动客户端发送数据线程
///
</summary>
public
void
StartSendTimer()
{
......
}
///
<summary>
///
启动检查客户端连接timer
///
</summary>
public
void
StartCheckInvalidClientTimer()
{
......
}
///
<summary>
///
检查客户端连接
///
</summary>
///
<param name="stateInfo"></param>
private
void
CheckInvalidClient(Object stateInfo)
{
......
}
public
virtual
void
RemoveInvalidClient()
{
......
}
///
<summary>
///
增加一条客户端发送数据
///
</summary>
public
void
AddSend(
string
userid,
string
send,
bool
isFirst)
{
......
}
}
4、BaseReceiver,数据接收处理类
该基类是所有接受数据的处理类,负责维护数据的队列关系,并进一步进行处理。
代码
public
class
BaseReceiver
{
protected
string
_Name
=
"
BaseReceiver
"
;
protected
Thread _PreDataHandlehread
=
null
;
//
处理数据线程
protected
Fifo
<
PreData
>
_preDataFifo
=
new
Fifo
<
PreData
>
(
50000
);
public
BaseReceiver()
{
_Name
=
this
.GetType().Name;
}
///
<summary>
///
接收处理数据
///
</summary>
public
void
AppendPreData(PreData data)
{
_preDataFifo.Append(data);
}
///
<summary>
///
数据处理
///
</summary>
protected
virtual
void
PreDataHandle()
{
......
}
///
<summary>
///
数据处理
///
</summary>
///
<param name="data"></param>
public
virtual
void
PreDataHandle(PreData data)
{
}
///
<summary>
///
开始数据处理线程
///
</summary>
public
virtual
void
Start()
{
if
(_PreDataHandlehread
==
null
)
{
_PreDataHandlehread
=
new
Thread(
new
ThreadStart(PreDataHandle));
_PreDataHandlehread.IsBackground
=
true
;
_PreDataHandlehread.Start();
}
}
}
5、ThreadHandler,数据独立线程处理类
对每个不同类型的数据(不同的协议类型),可以用独立的线程进行处理,这里封装了一个基类,用于进行数据独立线程的处理。
代码
public
class
ThreadHandler
<
T
>
{
Thread _Handlehread
=
null
;
//
处理数据线程
private
string
_ThreadName
=
""
;
private
Fifo
<
T
>
_DataFifo
=
new
Fifo
<
T
>
();
///
<summary>
///
接收处理数据
///
</summary>
public
virtual
void
AppendData(T data)
{
if
(data
!=
null
)
_DataFifo.Append(data);
}
///
<summary>
///
数据处理
///
</summary>
protected
virtual
void
DataThreadHandle()
{
while
(
true
)
{
T data
=
_DataFifo.Pop();
DataHandle(data);
}
}
///
<summary>
///
数据处理
///
</summary>
///
<param name="data"></param>
public
virtual
void
DataHandle(T data)
{
}
///
<summary>
///
检查数据处理线程
///
</summary>
public
virtual
void
Check()
{
......
}
///
<summary>
///
开始数据处理线程
///
</summary>
public
virtual
void
StartHandleThread()
{
......
}
}
6、PreData、DataTypeKey、Sign
PreData是定义了一个标准的协议数据格式,包含了协议关键字、协议内容、用户标识的内容,代码如下。
代码
///
<summary>
///
预处理的数据
///
</summary>
public
class
PreData
{
private
string
_key;
private
string
_content;
private
string
_userId;
public
PreData()
{
}
public
PreData(
string
key,
string
data)
{
_key
=
key;
_content
=
data;
}
///
<summary>
///
协议关键字
///
</summary>
public
string
Key
{
get
{
return
_key; }
set
{ _key
=
value; }
}
///
<summary>
///
数据内容
///
</summary>
public
string
Content
{
get
{
return
_content; }
set
{ _content
=
value; }
}
///
<summary>
///
客户端过来为用户帐号,或者指定的名称
///
</summary>
public
string
UserId
{
get
{
return
_userId; }
set
{ _userId
=
value; }
}
}
其中的DataTypeKey和Sign定义了一系列的协议头关键字和数据分隔符等信息。
代码
public
class
DataTypeKey
{
///
<summary>
///
认证请求 AUTHR C->S
///
</summary>
public
const
string
AuthenticationRequest
=
"
AUTHR
"
;
///
<summary>
///
认证请求应答AUTHA S->C
///
</summary>
public
const
string
AuthenticationAnswer
=
"
AUTHA
"
;
///
<summary>
///
测试数据TESTR C->S
///
</summary>
public
const
string
TestDataRequest
=
"
TESTR
"
;
///
<summary>
///
测试数据TESTA S->C
///
</summary>
public
const
string
TestDataAnswer
=
"
TESTA
"
;
.........
}
下面是数据分割符号,定义了数据包的开始符号、结束符号,分隔符号和数据分隔符等。
代码
public
class
Sign
{
///
<summary>
///
开始符
///
</summary>
public
const
string
Start
=
"
~
"
;
///
<summary>
///
开始符比特
///
</summary>
public
const
byte
StartByte
=
0x7E
;
///
<summary>
///
结束符
///
</summary>
public
const
string
End
=
"
#
"
;
///
<summary>
///
结束符比特
///
</summary>
public
const
byte
EndByte
=
0x23
;
///
<summary>
///
分隔符
///
</summary>
public
const
string
Separator
=
"
&
"
;
///
<summary>
///
分隔符比特
///
</summary>
public
const
byte
SeparatorByte
=
0x26
;
///
<summary>
///
数据分隔符
///
</summary>
public
const
string
DataSeparator
=
"
|
"
;
///
<summary>
///
数据分隔符比特
///
</summary>
public
const
byte
DataSeparatorByte
=
0x7C
;
}
另外,前面说了,我们数据是通过实体类作为载体的,我们知道,收到的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;
}
在接下来的工作中,就需要继承以上的基类,完成相关的对象和数据的处理了。
本人是实际中,编写了一个测试的例子,大致的基类使用情况如下所示。