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;
}

在接下来的工作中,就需要继承以上的基类,完成相关的对象和数据的处理了。

本人是实际中,编写了一个测试的例子,大致的基类使用情况如下所示。





标签: none

添加新评论