您的位置:首页 > 理论基础 > 计算机网络

C#中使用异步Socket编程实现TCP网络服务的C/S的通讯构架(二)

2006-11-07 17:08 1516 查看
/// <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

}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐