//////////////////////////////////////////////////////////////////////////////////////////
/*
标题:在c#中使用异步socket编程实现tcp网络服务的c/s的通讯构架(一)—-基础类库部分
当看到.net中tcplistener和tcpclient的时候,我非常高兴,那就是我想要的通讯模式
但是使用之后发现它们的力量太单薄了,我们需要一个更好的类库来替代它们.
下面提供了一些类,可以很好的完成tcp的c/s通讯模式.在本文的第二部分,我将为大家介绍怎么使用它们
主要通过事件来现实整个的功能:
服务器的事件包括:
服务器满
新客户端连接
客户端关闭
接收到数据
客户端使用的事件包括:
已连接服务器
接收到数据
连接关闭
另外为了灵活的处理需求变化,还提供了编码器和报文解析器的实现方法.
注意:该类库没有经过严格的测试,如出现bug,请发送给我,我会觉得你的整个行为是对我的鼓励和支持.
*/
//////////////////////////////////////////////////////////////////////////////////////////
/// <summary>
/// (c)2003-2005 c2217 studio
/// 保留所有权利
///
/// 文件名称: tcpcsframework.cs
/// 文件id:
/// 编程语言: c#
/// 文件说明: 提供tcp网络服务的c/s的通讯构架基础类
/// (使用异步socket编程实现)
///
/// 当前版本: 1.1
/// 替换版本: 1.0
///
/// 作者: 邓杨均
/// email: dyj057@gmail.com
/// 创建日期: 2005-3-9
/// 最后修改日期: 2005-3-17
///
/// 历史修改记录:
///
/// 时间: 2005-3-14
/// 修改内容:
/// 1.创建ibms.net.tcpcsframework命名空间和添加session对象.
/// 2.修改neteventargs类,以适应新添加对象.
/// 3.添加了会话退出类型,更适合实际的情况.
/// 注意:
/// * 强制退出类型是应用程序直接结束,比如通过任务管理器结束
/// 程序或者程序异常退出等,没有执行正常的退出方法而产生的.
/// * 正常的退出类型是应用程序执行正常的退出的方法关键在于
/// 需要调用socket.shutdown( socketshutdown.both )后才调用
/// socket.close()方法,而不是直接的调用socket.close()方法,
/// 如果那样调用将产生强制退出类型.
///
/// 时间: 2005-3-16
/// 修改内容:
/// 1.创建tcpcli,coder,datagramresover对象,把抽象和实现部分分离
/// 2.文件版本修改为1.1,1.0版本仍然保留,更名为:
/// tcpcsframework_v1.0.cs
/// 3.在tcpserver中修改自定义的hashtable为系统hashtable类型
///
/// </summary>
using system;
using system.net.sockets;
using system.net;
using system.text;
using system.diagnostics;
using system.collections;
namespace ibms.net.tcpcsframework
{
/// <summary>
/// 网络通讯事件模型委托
/// </summary>
public delegate void netevent(object sender, neteventargs e);
/// <summary>
/// 提供tcp连接服务的服务器类
///
/// 版本: 1.1
/// 替换版本: 1.0
///
/// 特点:
/// 1.使用hash表保存所有已连接客户端的状态,收到数据时能实现快速查找.每当
/// 有一个新的客户端连接就会产生一个新的会话(session).该session代表了客
/// 户端对象.
/// 2.使用异步的socket事件作为基础,完成网络通讯功能.
/// 3.支持带标记的数据报文格式的识别,以完成大数据报文的传输和适应恶劣的网
/// 络环境.初步规定该类支持的最大数据报文为640k(即一个数据包的大小不能大于
/// 640k,否则服务器程序会自动删除报文数据,认为是非法数据),防止因为数据报文
/// 无限制的增长而倒是服务器崩溃
/// 4.通讯格式默认使用encoding.default格式这样就可以和以前32位程序的客户端
/// 通讯.也可以使用u-16和u-8的的通讯方式进行.可以在该datagramresolver类的
/// 继承类中重载编码和解码函数,自定义加密格式进行通讯.总之确保客户端与服务
/// 器端使用相同的通讯格式
/// 5.使用c# native code,将来出于效率的考虑可以将c++代码写成的32位dll来代替
/// c#核心代码, 但这样做缺乏可移植性,而且是unsafe代码(该类的c++代码也存在)
/// 6.可以限制服务器的最大登陆客户端数目
/// 7.比使用tcplistener提供更加精细的控制和更加强大异步数据传输的功能,可作为
/// tcplistener的替代类
/// 8.使用异步通讯模式,完全不用担心通讯阻塞和线程问题,无须考虑通讯的细节
///
/// 注意:
/// 1.部分的代码由rational xde生成,可能与编码规范不符
///
/// 原理:
///
///
/// 使用用法:
///
/// 例子:
///
/// </summary>
public class tcpsvr
{
#region 定义字段
/// <summary>
/// 默认的服务器最大连接客户端端数据
/// </summary>
public const int defaultmaxclient=100;
/// <summary>
/// 接收数据缓冲区大小64k
/// </summary>
public const int defaultbuffersize = 64*1024;
/// <summary>
/// 最大数据报文大小
/// </summary>
public const int maxdatagramsize = 640*1024;
/// <summary>
/// 报文解析器
/// </summary>
private datagramresolver _resolver;
/// <summary>
/// 通讯格式编码解码器
/// </summary>
private coder _coder;
/// <summary>
/// 服务器程序使用的端口
/// </summary>
private ushort _port;
/// <summary>
/// 服务器程序允许的最大客户端连接数
/// </summary>
private ushort _maxclient;
/// <summary>
/// 服务器的运行状态
/// </summary>
private bool _isrun;
/// <summary>
/// 接收数据缓冲区
/// </summary>
private byte[] _recvdatabuffer;
/// <summary>
/// 服务器使用的异步socket类,
/// </summary>
private socket _svrsock;
/// <summary>
/// 保存所有客户端会话的哈希表
/// </summary>
private hashtable _sessiontable;
/// <summary>
/// 当前的连接的客户端数
/// </summary>
private ushort _clientcount;
#endregion
#region 事件定义
/// <summary>
/// 客户端建立连接事件
/// </summary>
public event netevent clientconn;
/// <summary>
/// 客户端关闭事件
/// </summary>
public event netevent clientclose;
/// <summary>
/// 服务器已经满事件
/// </summary>
public event netevent serverfull;
/// <summary>
/// 服务器接收到数据事件
/// </summary>
public event netevent recvdata;
#endregion
#region 构造函数
/// <summary>
/// 构造函数
/// </summary>
/// <param name="port">服务器端监听的端口号</param>
/// <param name="maxclient">服务器能容纳客户端的最大能力</param>
/// <param name="encodingmothord">通讯的编码方式</param>
public tcpsvr( ushort port,ushort maxclient, coder coder)
{
_port = port;
_maxclient = maxclient;
_coder = coder;
}
/// <summary>
/// 构造函数(默认使用default编码方式)
/// </summary>
/// <param name="port">服务器端监听的端口号</param>
/// <param name="maxclient">服务器能容纳客户端的最大能力</param>
public tcpsvr( ushort port,ushort maxclient)
{
_port = port;
_maxclient = maxclient;
_coder = new coder(coder.encodingmothord.default);
}
// <summary>
/// 构造函数(默认使用default编码方式和defaultmaxclient(100)个客户端的容量)
/// </summary>
/// <param name="port">服务器端监听的端口号</param>
public tcpsvr( ushort port):this( port, defaultmaxclient)
{
}
#endregion
#region 属性
/// <summary>
/// 服务器的socket对象
/// </summary>
public socket serversocket
{
get
{
return _svrsock;
}
}
/// <summary>
/// 数据报文分析器
/// </summary>
public datagramresolver resovlver
{
get
{
return _resolver;
}
set
{
_resolver = value;
}
}
/// <summary>
/// 客户端会话数组,保存所有的客户端,不允许对该数组的内容进行修改
/// </summary>
public hashtable sessiontable
{
get
{
return _sessiontable;
}
}
/// <summary>
/// 服务器可以容纳客户端的最大能力
/// </summary>
public int capacity
{
get
{
return _maxclient;
}
}
/// <summary>
/// 当前的客户端连接数
/// </summary>
public int sessioncount
{
get
{
return _clientcount;
}
}
/// <summary>
/// 服务器运行状态
/// </summary>
public bool isrun
{
get
{
return _isrun;
}
}
#endregion
#region 公有方法
/// <summary>
/// 启动服务器程序,开始监听客户端请求
/// </summary>
public virtual void start()
{
if( _isrun )
{
throw (new applicationexception("tcpsvr已经在运行."));
}
_sessiontable = new hashtable(53);
_recvdatabuffer = new byte[defaultbuffersize];
//初始化socket
_svrsock = new socket( addressfamily.internetwork,
sockettype.stream, protocoltype.tcp );
//绑定端口
ipendpoint iep = new ipendpoint( ipaddress.any, _port);
_svrsock.bind(iep);
//开始监听
_svrsock.listen(5);
//设置异步方法接受客户端连接
_svrsock.beginaccept(new asynccallback( acceptconn ), _svrsock);
_isrun = true;
}
/// <summary>
/// 停止服务器程序,所有与客户端的连接将关闭
/// </summary>
public virtual void stop()
{
if( !_isrun )
{
throw (new applicationexception("tcpsvr已经停止"));
}
//这个条件语句,一定要在关闭所有客户端以前调用
//否则在endconn会出现错误
_isrun = false;
//关闭数据连接,负责客户端会认为是强制关闭连接
if( _svrsock.connected )
{
_svrsock.shutdown( socketshutdown.both );
}
closeallclient();
//清理资源
_svrsock.close();
_sessiontable = null;
}
/// <summary>
/// 关闭所有的客户端会话,与所有的客户端连接会断开
/// </summary>
public virtual void closeallclient()
{
foreach(session client in _sessiontable.values)
{
client.close();
}
_sessiontable.clear();
}
/// <summary>
/// 关闭一个与客户端之间的会话
/// </summary>
/// <param name="closeclient">需要关闭的客户端会话对象</param>
public virtual void closesession(session closeclient)
{
debug.assert( closeclient !=null);
if( closeclient !=null )
{
closeclient.datagram =null;
_sessiontable.remove(closeclient.id);
_clientcount–;
//客户端强制关闭链接
if( clientclose != null )
{
clientclose(this, new neteventargs( closeclient ));
}
closeclient.close();
}
}
/// <summary>
/// 发送数据
/// </summary>
/// <param name="recvdataclient">接收数据的客户端会话</param>
/// <param name="datagram">数据报文</param>
public virtual void send( session recvdataclient, string datagram )
{
//获得数据编码
byte [] data = _coder.getencodingbytes(datagram);
recvdataclient.clientsocket.beginsend( data, 0, data.length, socketflags.none,
new asynccallback( senddataend ), recvdataclient.clientsocket );
}
#endregion
#region 受保护方法
/// <summary>
/// 关闭一个客户端socket,首先需要关闭session
/// </summary>
/// <param name="client">目标socket对象</param>
/// <param name="exittype">客户端退出的类型</param>
protected virtual void closeclient( socket client, session.exittype exittype)
{
debug.assert ( client !=null);
//查找该客户端是否存在,如果不存在,抛出异常
session closeclient = findsession(client);
closeclient.typeofexit = exittype;
if(closeclient!=null)
{
closesession(closeclient);
}
else
{
throw( new applicationexception("需要关闭的socket对象不存在"));
}
}
/// <summary>
/// 客户端连接处理函数
/// </summary>
/// <param name="iar">欲建立服务器连接的socket对象</param>
protected virtual void acceptconn(iasyncresult iar)
{
//如果服务器停止了服务,就不能再接收新的客户端
if( !_isrun)
{
return;
}
//接受一个客户端的连接请求
socket oldserver = ( socket ) iar.asyncstate;
socket client = oldserver.endaccept(iar);
//检查是否达到最大的允许的客户端数目
if( _clientcount == _maxclient )
{
//服务器已满,发出通知
if( serverfull != null )
{
serverfull(this, new neteventargs( new session(client)));
}
}
else
{
session newsession = new session( client );
_sessiontable.add(newsession.id, newsession);
//客户端引用计数+1
_clientcount ++;
//开始接受来自该客户端的数据
client.beginreceive( _recvdatabuffer,0 , _recvdatabuffer.length, socketflags.none,
new asynccallback(receivedata), client);
//新的客户段连接,发出通知
if( clientconn != null )
{
clientconn(this, new neteventargs(newsession ) );
}
}
//继续接受客户端
_svrsock.beginaccept(new asynccallback( acceptconn ), _svrsock);
}
/// <summary>
/// 通过socket对象查找session对象
/// </summary>
/// <param name="client"></param>
/// <returns>找到的session对象,如果为null,说明并不存在该回话</returns>
private session findsession( socket client )
{
sessionid id = new sessionid((int)client.handle);
return (session)_sessiontable[id];
}
/// <summary>
/// 接受数据完成处理函数,异步的特性就体现在这个函数中,
/// 收到数据后,会自动解析为字符串报文
/// </summary>
/// <param name="iar">目标客户端socket</param>
protected virtual void receivedata(iasyncresult iar)
{
socket client = (socket)iar.asyncstate;
try
{
//如果两次开始了异步的接收,所以当客户端退出的时候
//会两次执行endreceive
int recv = client.endreceive(iar);
if( recv == 0 )
{
//正常的关闭
closeclient(client, session.exittype.normalexit);
return;
}
string receiveddata = _coder.getencodingstring( _recvdatabuffer, recv );
//发布收到数据的事件
if(recvdata!=null)
{
session senddatasession= findsession(client);
debug.assert( senddatasession!=null );
//如果定义了报文的尾标记,需要处理报文的多种情况
if(_resolver != null)
{
if( senddatasession.datagram !=null &&
senddatasession.datagram.length !=0)
{
//加上最后一次通讯剩余的报文片断
receiveddata= senddatasession.datagram + receiveddata ;
}
string [] recvdatagrams = _resolver.resolve(ref receiveddata);
foreach(string newdatagram in recvdatagrams)
{
//深拷贝,为了保持datagram的对立性
icloneable copysession = (icloneable)senddatasession;
session clientsession = (session)copysession.clone();
clientsession.datagram = newdatagram;
//发布一个报文消息
recvdata(this,new neteventargs( clientsession ));
}
//剩余的代码片断,下次接收的时候使用
senddatasession.datagram = receiveddata;
if( senddatasession.datagram.length > maxdatagramsize )
{
senddatasession.datagram = null;
}
}
//没有定义报文的尾标记,直接交给消息订阅者使用
else
{
icloneable copysession = (icloneable)senddatasession;
session clientsession = (session)copysession.clone();
clientsession.datagram = receiveddata;
recvdata(this,new neteventargs( clientsession ));
}
}//end of if(recvdata!=null)
//继续接收来自来客户端的数据
client.beginreceive( _recvdatabuffer, 0, _recvdatabuffer.length , socketflags.none,
new asynccallback( receivedata ), client);
}
catch(socketexception ex)
{
//客户端退出
if( 10054 == ex.errorcode )
{
//客户端强制关闭
closeclient(client, session.exittype.exceptionexit);
}
}
catch(objectdisposedexception ex)
{
//这里的实现不够优雅
//当调用closesession()时,会结束数据接收,但是数据接收
//处理中会调用int recv = client.endreceive(iar);
//就访问了closesession()已经处置的对象
//我想这样的实现方法也是无伤大雅的.
if(ex!=null)
{
ex=null;
//donothing;
}
}
}
/// <summary>
/// 发送数据完成处理函数
/// </summary>
/// <param name="iar">目标客户端socket</param>
protected virtual void senddataend(iasyncresult iar)
{
socket client = (socket)iar.asyncstate;
int sent = client.endsend(iar);
}
#endregion
}
/// <summary>
/// 提供tcp网络连接服务的客户端类
///
/// 版本: 1.0
/// 替换版本:
///
/// 特征:
/// 原理:
/// 1.使用异步socket通讯与服务器按照一定的通讯格式通讯,请注意与服务器的通
/// 讯格式一定要一致,否则可能造成服务器程序崩溃,整个问题没有克服,怎么从byte[]
/// 判断它的编码格式
/// 2.支持带标记的数据报文格式的识别,以完成大数据报文的传输和适应恶劣的网
/// 络环境.
/// 用法:
/// 注意:
/// </summary>
public class tcpcli
{
#region 字段
/// <summary>
/// 客户端与服务器之间的会话类
/// </summary>
private session _session;
/// <summary>
/// 客户端是否已经连接服务器
/// </summary>
private bool _isconnected = false;
/// <summary>
/// 接收数据缓冲区大小64k
/// </summary>
public const int defaultbuffersize = 64*1024;
/// <summary>
/// 报文解析器
/// </summary>
private datagramresolver _resolver;
/// <summary>
/// 通讯格式编码解码器
/// </summary>
private coder _coder;
/// <summary>
/// 接收数据缓冲区
/// </summary>
private byte[] _recvdatabuffer = new byte[defaultbuffersize];
#endregion
#region 事件定义
//需要订阅事件才能收到事件的通知,如果订阅者退出,必须取消订阅
/// <summary>
/// 已经连接服务器事件
/// </summary>
public event netevent connectedserver;
/// <summary>
/// 接收到数据报文事件
/// </summary>
public event netevent receiveddatagram;
/// <summary>
/// 连接断开事件
/// </summary>
public event netevent disconnectedserver;
#endregion
#region 属性
/// <summary>
/// 返回客户端与服务器之间的会话对象
/// </summary>
public session clientsession
{
get
{
return _session;
}
}
/// <summary>
/// 返回客户端与服务器之间的连接状态
/// </summary>
public bool isconnected
{
get
{
return _isconnected;
}
}
/// <summary>
/// 数据报文分析器
/// </summary>
public datagramresolver resovlver
{
get
{
return _resolver;
}
set
{
_resolver = value;
}
}
/// <summary>
/// 编码解码器
/// </summary>
public coder servercoder
{
get
{
return _coder;
}
}
#endregion
#region 公有方法
/// <summary>
/// 默认构造函数,使用默认的编码格式
/// </summary>
public tcpcli()
{
_coder = new coder( coder.encodingmothord.default );
}
/// <summary>
/// 构造函数,使用一个特定的编码器来初始化
/// </summary>
/// <param name="_coder">报文编码器</param>
public tcpcli( coder coder )
{
_coder = coder;
}
/// <summary>
/// 连接服务器
/// </summary>
/// <param name="ip">服务器ip地址</param>
/// <param name="port">服务器端口</param>
public virtual void connect( string ip, int port)
{
if(isconnected)
{
//重新连接
debug.assert( _session !=null);
close();
}
socket newsock= new socket(addressfamily.internetwork,
sockettype.stream, protocoltype.tcp);
ipendpoint iep = new ipendpoint( ipaddress.parse(ip), port);
newsock.beginconnect(iep, new asynccallback(connected), newsock);
}
/// <summary>
/// 发送数据报文
/// </summary>
/// <param name="datagram"></param>
public virtual void send( string datagram)
{
if(datagram.length ==0 )
{
return;
}
if( !_isconnected )
{
throw (new applicationexception("没有连接服务器,不能发送数据") );
}
//获得报文的编码字节
byte [] data = _coder.getencodingbytes(datagram);
_session.clientsocket.beginsend( data, 0, data.length, socketflags.none,
new asynccallback( senddataend ), _session.clientsocket);
}
/// <summary>
/// 关闭连接
/// </summary>
public virtual void close()
{
if(!_isconnected)
{
return;
}
_session.close();
_session = null;
_isconnected = false;
}
#endregion
#region 受保护方法
/// <summary>
/// 数据发送完成处理函数
/// </summary>
/// <param name="iar"></param>
protected virtual void senddataend(iasyncresult iar)
{
socket remote = (socket)iar.asyncstate;
int sent = remote.endsend(iar);
debug.assert(sent !=0);
}
/// <summary>
/// 建立tcp连接后处理过程
/// </summary>
/// <param name="iar">异步socket</param>
protected virtual void connected(iasyncresult iar)
{
socket socket = (socket)iar.asyncstate;
socket.endconnect(iar);
//创建新的会话
_session = new session(socket);
_isconnected = true;
//触发连接建立事件
if(connectedserver != null)
{
connectedserver(this, new neteventargs(_session));
}
//建立连接后应该立即接收数据
_session.clientsocket.beginreceive(_recvdatabuffer, 0,
defaultbuffersize, socketflags.none,
new asynccallback(recvdata), socket);
}
/// <summary>
/// 数据接收处理函数
/// </summary>
/// <param name="iar">异步socket</param>
protected virtual void recvdata(iasyncresult iar)
{
socket remote = (socket)iar.asyncstate;
try
{
int recv = remote.endreceive(iar);
//正常的退出
if(recv ==0 )
{
_session.typeofexit = session.exittype.normalexit;
if(disconnectedserver!=null)
{
disconnectedserver(this, new neteventargs(_session));
}
return;
}
string receiveddata = _coder.getencodingstring( _recvdatabuffer,recv );
//通过事件发布收到的报文
if(receiveddatagram != null)
{
//通过报文解析器分析出报文
//如果定义了报文的尾标记,需要处理报文的多种情况
if(_resolver != null)
{
if( _session.datagram !=null &&
_session.datagram.length !=0)
{
//加上最后一次通讯剩余的报文片断
receiveddata= _session.datagram + receiveddata ;
}
string [] recvdatagrams = _resolver.resolve(ref receiveddata);
foreach(string newdatagram in recvdatagrams)
{
//need deep copy.因为需要保证多个不同报文独立存在
icloneable copysession = (icloneable)_session;
session clientsession = (session)copysession.clone();
clientsession.datagram = newdatagram;
//发布一个报文消息
receiveddatagram(this,new neteventargs( clientsession ));
}
//剩余的代码片断,下次接收的时候使用
_session.datagram = receiveddata;
}
//没有定义报文的尾标记,直接交给消息订阅者使用
else
{
icloneable copysession = (icloneable)_session;
session clientsession = (session)copysession.clone();
clientsession.datagram = receiveddata;
receiveddatagram( this, new neteventargs( clientsession ));
}
}//end of if(receiveddatagram != null)
//继续接收数据
_session.clientsocket.beginreceive(_recvdatabuffer, 0, defaultbuffersize, socketflags.none,
new asynccallback(recvdata), _session.clientsocket);
}
catch(socketexception ex)
{
//客户端退出
if( 10054 == ex.errorcode )
{
//服务器强制的关闭连接,强制退出
_session.typeofexit = session.exittype.exceptionexit;
if(disconnectedserver!=null)
{
disconnectedserver(this, new neteventargs(_session));
}
}
else
{
throw( ex );
}
}
catch(objectdisposedexception ex)
{
//这里的实现不够优雅
//当调用closesession()时,会结束数据接收,但是数据接收
//处理中会调用int recv = client.endreceive(iar);
//就访问了closesession()已经处置的对象
//我想这样的实现方法也是无伤大雅的.
if(ex!=null)
{
ex =null;
//donothing;
}
}
}
#endregion
}
/// <summary>
/// 通讯编码格式提供者,为通讯服务提供编码和解码服务
/// 你可以在继承类中定制自己的编码方式如:数据加密传输等
/// </summary>
public class coder
{
/// <summary>
/// 编码方式
/// </summary>
private encodingmothord _encodingmothord;
protected coder()
{
}
public coder(encodingmothord encodingmothord)
{
_encodingmothord = encodingmothord;
}
public enum encodingmothord
{
default =0,
unicode,
utf8,
ascii,
}
/// <summary>
/// 通讯数据解码
/// </summary>
/// <param name="databytes">需要解码的数据</param>
/// <returns>编码后的数据</returns>
public virtual string getencodingstring( byte [] databytes,int size)
{
switch( _encodingmothord )
{
case encodingmothord.default:
{
return encoding.default.getstring(databytes,0,size);
}
case encodingmothord.unicode:
{
return encoding.unicode.getstring(databytes,0,size);
}
case encodingmothord.utf8:
{
return encoding.utf8.getstring(databytes,0,size);
}
case encodingmothord.ascii:
{
return encoding.ascii.getstring(databytes,0,size);
}
default:
{
throw( new exception("未定义的编码格式"));
}
}
}
/// <summary>
/// 数据编码
/// </summary>
/// <param name="datagram">需要编码的报文</param>
/// <returns>编码后的数据</returns>
public virtual byte[] getencodingbytes(string datagram)
{
switch( _encodingmothord)
{
case encodingmothord.default:
{
return encoding.default.getbytes(datagram);
}
case encodingmothord.unicode:
{
return encoding.unicode.getbytes(datagram);
}
case encodingmothord.utf8:
{
return encoding.utf8.getbytes(datagram);
}
case encodingmothord.ascii:
{
return encoding.ascii.getbytes(datagram);
}
default:
{
throw( new exception("未定义的编码格式"));
}
}
}
}
/// <summary>
/// 数据报文分析器,通过分析接收到的原始数据,得到完整的数据报文.
/// 继承该类可以实现自己的报文解析方法.
/// 通常的报文识别方法包括:固定长度,长度标记,标记符等方法
/// 本类的现实的是标记符的方法,你可以在继承类中实现其他的方法
/// </summary>
public class datagramresolver
{
/// <summary>
/// 报文结束标记
/// </summary>
private string endtag;
/// <summary>
/// 返回结束标记
/// </summary>
string endtag
{
get
{
return endtag;
}
}
/// <summary>
/// 受保护的默认构造函数,提供给继承类使用
/// </summary>
protected datagramresolver()
{
}
/// <summary>
/// 构造函数
/// </summary>
/// <param name="endtag">报文结束标记</param>
public datagramresolver(string endtag)
{
if(endtag == null)
{
throw (new argumentnullexception("结束标记不能为null"));
}
if(endtag == "")
{
throw (new argumentexception("结束标记符号不能为空字符串"));
}
this.endtag = endtag;
}
/// <summary>
/// 解析报文
/// </summary>
/// <param name="rawdatagram">原始数据,返回未使用的报文片断,
/// 该片断会保存在session的datagram对象中</param>
/// <returns>报文数组,原始数据可能包含多个报文</returns>
public virtual string [] resolve(ref string rawdatagram)
{
arraylist datagrams = new arraylist();
//末尾标记位置索引
int tagindex =-1;
while(true)
{
tagindex = rawdatagram.indexof(endtag,tagindex+1);
if( tagindex == -1 )
{
break;
}
else
{
//按照末尾标记把字符串分为左右两个部分
string newdatagram = rawdatagram.substring(
0, tagindex+endtag.length);
datagrams.add(newdatagram);
if(tagindex+endtag.length >= rawdatagram.length)
{
rawdatagram="";
break;
}
rawdatagram = rawdatagram.substring(tagindex+endtag.length,
rawdatagram.length – newdatagram.length);
//从开始位置开始查找
tagindex=0;
}
}
string [] results= new string[datagrams.count];
datagrams.copyto(results);
return results;
}
}
/// <summary>
/// 客户端与服务器之间的会话类
///
/// 版本: 1.1
/// 替换版本: 1.0
///
/// 说明:
/// 会话类包含远程通讯端的状态,这些状态包括socket,报文内容,
/// 客户端退出的类型(正常关闭,强制退出两种类型)
/// </summary>
public class session:icloneable
{
#region 字段
/// <summary>
/// 会话id
/// </summary>
private sessionid _id;
/// <summary>
/// 客户端发送到服务器的报文
/// 注意:在有些情况下报文可能只是报文的片断而不完整
/// </summary>
private string _datagram;
/// <summary>
/// 客户端的socket
/// </summary>
private socket _clisock;
/// <summary>
/// 客户端的退出类型
/// </summary>
private exittype _exittype;
/// <summary>
/// 退出类型枚举
/// </summary>
public enum exittype
{
normalexit ,
exceptionexit
};
#endregion
#region 属性
/// <summary>
/// 返回会话的id
/// </summary>
public sessionid id
{
get
{
return _id;
}
}
/// <summary>
/// 存取会话的报文
/// </summary>
public string datagram
{
get
{
return _datagram;
}
set
{
_datagram = value;
}
}
/// <summary>
/// 获得与客户端会话关联的socket对象
/// </summary>
public socket clientsocket
{
get
{
return _clisock;
}
}
/// <summary>
/// 存取客户端的退出方式
/// </summary>
public exittype typeofexit
{
get
{
return _exittype;
}
set
{
_exittype = value;
}
}
#endregion
#region 方法
/// <summary>
/// 使用socket对象的handle值作为hashcode,它具有良好的线性特征.
/// </summary>
/// <returns></returns>
public override int gethashcode()
{
return (int)_clisock.handle;
}
/// <summary>
/// 返回两个session是否代表同一个客户端
/// </summary>
/// <param name="obj"></param>
/// <returns></returns>
public override bool equals(object obj)
{
session rightobj = (session)obj;
return (int)_clisock.handle == (int)rightobj.clientsocket.handle;
}
/// <summary>
/// 重载tostring()方法,返回session对象的特征
/// </summary>
/// <returns></returns>
public override string tostring()
{
string result = string.format("session:{0},ip:{1}",
_id,_clisock.remoteendpoint.tostring());
//result.c
return result;
}
/// <summary>
/// 构造函数
/// </summary>
/// <param name="clisock">会话使用的socket连接</param>
public session( socket clisock)
{
debug.assert( clisock !=null );
_clisock = clisock;
_id = new sessionid( (int)clisock.handle);
}
/// <summary>
/// 关闭会话
/// </summary>
public void close()
{
debug.assert( _clisock !=null );
//关闭数据的接受和发送
_clisock.shutdown( socketshutdown.both );
//清理资源
_clisock.close();
}
#endregion
#region icloneable 成员
object system.icloneable.clone()
{
session newsession = new session(_clisock);
newsession.datagram = _datagram;
newsession.typeofexit = _exittype;
return newsession;
}
#endregion
}
/// <summary>
/// 唯一的标志一个session,辅助session对象在hash表中完成特定功能
/// </summary>
public class sessionid
{
/// <summary>
/// 与session对象的socket对象的handle值相同,必须用这个值来初始化它
/// </summary>
private int _id;
/// <summary>
/// 返回id值
/// </summary>
public int id
{
get
{
return _id;
}
}
/// <summary>
/// 构造函数
/// </summary>
/// <param name="id">socket的handle值</param>
public sessionid(int id)
{
_id = id;
}
/// <summary>
/// 重载.为了符合hashtable键值特征
/// </summary>
/// <param name="obj"></param>
/// <returns></returns>
public override bool equals(object obj)
{
if(obj != null )
{
sessionid right = (sessionid) obj;
return _id == right._id;
}
else if(this == null)
{
return true;
}
else
{
return false;
}
}
/// <summary>
/// 重载.为了符合hashtable键值特征
/// </summary>
/// <returns></returns>
public override int gethashcode()
{
return _id;
}
/// <summary>
/// 重载,为了方便显示输出
/// </summary>
/// <returns></returns>
public override string tostring()
{
return _id.tostring ();
}
}
/// <summary>
/// 服务器程序的事件参数,包含了激发该事件的会话对象
/// </summary>
public class neteventargs:eventargs
{
#region 字段
/// <summary>
/// 客户端与服务器之间的会话
/// </summary>
private session _client;
#endregion
#region 构造函数
/// <summary>
/// 构造函数
/// </summary>
/// <param name="client">客户端会话</param>
public neteventargs(session client)
{
if( null == client)
{
throw(new argumentnullexception());
}
_client = client;
}
#endregion
#region 属性
/// <summary>
/// 获得激发该事件的会话对象
/// </summary>
public session client
{
get
{
return _client;
}
}
#endregion
}
}