可扩展的SockBase设计和实现(1)
目录
摘要
基于Sockets网络编程存在的问题
可扩展的SockBase设计
SockBase的编程实现
从SockBase继承及其使用方法
摘要
System.Net 命名空间为当前网络上使用的多种协议提供了简单的编程接口,如果需要底层控制更多的编程而言,开发人员就需要使用System.Net.Sockets 命名空间了。System.Net.Sockets为需要严密控制网络访问的开发人员提供了 Windows Sockets (Winsock) 接口的托管实现。 但是Sockets编程既烦杂,又毫无可扩展性,需要开发人员自己控制消息的接受和发送以及处理,这些与业务逻辑有关的工作在编程之时就需要写入代码,一旦需求发生变化,又得改写Sockets的接受消息列表和处理。同时对于命令字符串的构造都需要底层的程序员去控制,不仅容易出错,而且不易改变。面对复杂多变的业务逻辑,这样的架构毫无可重用而言,同时给程序员提出了很高的要求,很大程度的工作量放在了做底层的重复性的劳动上。因此,为了提供一种易于扩展的Sockets编程架构,使得开发人员将注意力放在业务逻辑上,我们提出了设计可扩展的SockBase思路,同时实现了这个架构,经验表明,不仅解决了上述存在的问题,而且取得了非常好的效果。
基于Sockets网络编程存在的问题
在一般的基于Sockets网络编程中,不难出现以下代码:
while(sock != null){
temp=ReadMsg(); //调用sock.Recevie(..)函数,把byte[]转成字符串
if( temp.Trim() == "Login")
{
// do some thing…
sock.Send( TransMsg("OK"));
}
else if (temp.Trim() == "show")
{
// do some thing…
sock.Send( TransMsg(ips));
}
else if (temp.Trim() == "Upload"){
// do some thing…
sock.Send(TransMsg("OK"));
// do some thing…
sock.Send(TransMsg("OK"));
}
else if (temp.Trim() == "list"){
// do some thing…
sock.Send(TransMsg(files));
}
else if (temp.Trim() == "Get"){
// do some thing…
sock.Send(TransMsg("OK"));
temp = ReadMsg().Trim();
// do some thing…
}
}
从上面的代码中,我们可以注意到,对于所有的从客户端发来的消息,都是统一在这个while()循环中进行处理的…
对于消息命令的接收,显然一般都是和上面的代码方式类似,统一放到一个地方来执行.但是上述代码对于消息的派发是使用的Switch case的结构.这样就带来一个问题.Switch Case是在程序代码编写阶段写的,也就是所谓的硬性编入.即在程序运行过程中不可修改.这样就使得程序不能在运行过程中对用户不同的输入/不同的条件发送不同的消息,或是用户自定义的,或是程序完成后新加入的扩展的命令..同时,也还是由于Switch case结构,使得对于消息的处理也固定下来了,同样也不能动态的去修改消息处理函数.这样使得程序的扩展性很差,而且对于底层的如上述代码,对于Socket的操作,完全不能直接使用到别的软件中.(因为消息命令,处理函数不一定是完全一样的).
也就是说,在通常的Sockets的网络开发中,开发人员自己控制消息的接受和发送以及处理,这些与业务逻辑有关的工作在编程之时就需要写入代码,一旦需求发生变化,又得改写Sockets的接受消息列表和处理。同时对于命令字符串的构造都需要底层的程序员去控制,不仅容易出错,而且不易改变。面对复杂多变的业务逻辑,这样的架构毫无可重用而言,同时给程序员提出了很高的要求,很大程度的工作量放在了做底层的重复性的劳动上。
可扩展的SockBase设计
针对上面的问题,我们提出了可扩展的SockBase.可扩展性主要在于能接收任意的消息,而且能对同一个消息在不同的情况下面有不同的处理函数..
我们想到了Windows的消息处理机制.当我们要处理一个系统消息的时候,或是处理我们自定义的消息的时候,首先,我们把自定义消息加入到程序的消息列表中去,同时通过Windows编程中的消息映射的方式,运行增加对处理此消息的函数.使操作系统在收到这个消息后,能够找到我们对其进行绑定的消息处理函数,进而调用..
回到Sockets中来,我们先做出一个类似Windows消息映射表样的东西.其中有两个元素,一个就是收到的消息命令,另一个就是收到此消息后的处理函数,在程序开发者开发过程中,只要在具体的消息接收前先对消息映射表进行初始化,就够了.SockBase会自动的调用相应的消息处理函数.
SockBase的编程实现
上面的部分都是理论.现在我们开始完成SockBase的实现代码.
1.定义消息映射表
根据上面所提到的,需要有一个类似消息映射表的东西.这里,我们使用Hashtable来存储消息和处理函数的数据..由于Hashtable是一种键/值型的集合,所以我们把消息命令做为键,对应的消息处理函数做为值.由于消息有很多种,而且我们希望对于所有的消息,都能在一个地方去调用相应的处理函数.所以我们使用了.NET的委托做为Hashtable中的值.
定义的委托如下:
Public delegate Command(string args);
使用方法如下:
Hashtable Commands = new Hashtable();
Commands.Add( /*消息命令*/, new Command( /*具体的处理函数*/));
调用的时候只要
((Command)Commands[/*消息命令*/])(/*参数*/);
就可以了~
2,SockBase的具体实现
好,现在,关于消息映射表的准备工作已完成了.现在开始SockBase的实现:P
(1) 构造函数以及变量的声明,实现
public class SocketBase:IDisposable{
//待处理的命令处理集合
protected Hashtable m_CommandHandlerList;
protected NetworkStream readStream;
protected NetworkStream writeStream;
protected Socket m_sock;
//通过构造函数将Socket的实例传进来.
public SocketBase(Socket sock){
m_sock = sock;
readStream = new NetworkStream(m_sock);
writeStream = new NetworkStream(m_sock);
}
public void Dispose()
{
// 关闭本地套节子
try
{
if (m_sock!= null)
{
if(m_sock.Connected)
{
m_sock.Shutdown(SocketShutdown.Both);
m_sock.Close();
}
m_sock = null;
}
}
catch(Exception ex)
{
}
}
}
(2) 发送和接收函数
准备工作已完成了.现在就是我们开始对m_sock进行消息接收,以及对消息进行派发了.
首先是消息发送和接收.由于Socket的不确定性,所以很容易出现发送的多个消息在接收的时候混在一起了,所以我们决定每发一个消息就发送固定大小的包,接收时了接收相应大小的包.
在SockBase中定义一个包的固定大小:
private static int DefaulteBufferSize =5120;
public int BufferSize
{
get{
if(m_BufferSize!=0)
return m_BufferSize;
else
return DefaulteBufferSize;
}
set{m_BufferSize=value;}
}
再就是发送,接收函数
public string ReceiveMsg()
{
byte[] Recs=new byte[BufferSize];
int count = 0;
int num;
do {
num = ReadStream.Read(Recs,count,BufferSize-count);
if( num == 0){
throw new Exception("客户端不正常关闭");
}
count += num;
} while( count < BufferSize);
return System.Text.Encoding.GetEncoding(Encoding).GetString(Recs).Replace("/0","");
}
public void Send(string msg)
{
byte[] sender = new Byte[BufferSize];
byte[] temp = System.Text.Encoding.Unicode.GetBytes(msg) ;
Array.Copy( temp,sender,temp.Length);
WriteStream.Write(sender,0,sender.Length);
WriteStream.Flush();
}
(3) 消息派发函数
好了,下面就是对消息进行派发的函数了:
public void CmdHandler(string ClientMessage)
{
//解析出命令
string[] cmdList=ClientMessage.Split(‘;’);
string cmdText = "";
for(int i=1;i<cmdList.Length;i++)
{
if(i==cmdList.Length-1)
{
cmdText += cmdList[i];
}
else
{
cmdText += cmdList[i]+":";
}
}
//寻找合适的匹配处理
if(m_CommandHandlerList.ContainsKey( cmdList[0] ) ) {
( ( Command ) m_ConnamdHandlerList[ cmdList[0] ) ( cmdText);
}
}
我们通过对m_CommandHandlerList中所有的键(即注册的消息命令)进行判断,如果和接收到的消息的命令是相同的,就直接去调用存在此Hashtable中对应的值(即Command委托)..
(4) SockBase运行的起点
最后的部分,整个SockBase运行的起点:
public void ListenSocket()
{
try
{
while(m_sock!=null&&m_sock.Connected)
{
//截获消息,并作出相应的处理
CmdHandler(ReceiveMsg());
}
}
}
现在我们只要直接在m_CommandHandlerList中加入我们要处理的消息的命令和处理函数,再运行ListenSocket(),就可以对接收到的消息进行相应的处理了..
从SockBase继承及其使用方法
上面实现了SockBase的基本的构架.对于大部分的Sockets网络编程,都可适用.下面就是使用的方法..
这里,我们从SockBase直接继承而来一个Client_ListenThread.在此类中,我们通过构造函数,将相应的Socket的实例传给m_sock.再对消息映射表进行初始化,用一个线程专门运行ListenSockt来对接收到的消息进行派发,调用其处理函数.
public class Client_ListenThread : SocketBase
{
#region 所有字段包含命令字段
#endregion
#region 所有方法
public Client_ListenThread(Socket Client_socket) : base(socket)
{
LoadCommandHandlerList();
}
//装载所有的命令处理队列
public void LoadCommandHandlerList()
{
CommandHandlerItem.Add(“GetFile” , new Command(GetFileHandler);
CommandHandlerItem.Add(“FileOK”, Command(FileOKHandler);
}
//以下为所有命令处理函数
private void GetFileHandler(string cmdText)
{
//检查文件是否存在
if((new FileManager()).CheckFileExist(cmdTxt))
{
Send(“OK”);
}
else
{
Send(“Failure”);
}
}
private void FileOKHandler(string cmdText)
{
Dispose();
}
#endregion
}
通过下面这个函数将其运行:
listen_socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
…
listen_socket.Listen(-1);
while(true){
Client_ListenThread clientthread=new Client_ListenThread(listen_socket.Accept());
if ( clientthread.Sock.Connected )
{
Thread fileThread = new Thread(new ThreadStart(clientthread.ListenSocket));
fileThread.IsBackground=true;
fileThread.Start();
}
}
总结
通过上述的SockBase,我们可以在不改变SockBase的前提下,对消息映射表进行动态的修改.这样使得开发人员将注意力放在业务逻辑上,极大的方便了基于Sockets的网络编程开发.