一个.net客户端通讯框架的设计(三)---设计IO连接
2010-12-09 12:02 姜 萌@cnblogs 阅读(2864) 评论(8) 编辑 收藏 举报IAsyncIOService(对应代码里的IAsyncConnector)
这是一个异步IO服务接口,含有一下几个方法。

{
void Connect(EndPoint endPoint);
ConnectionStatus ConnectionStatus { get; }
void Send<T>(T msg);
void Close();
void Close(Boolean isImmediately);
void Abort();
}
异步模型
win32支持多种异步I/O模型,比如从最简单的select到复杂但高效的IOCP,.NET将这些底层进行了很好的封装,为开发人员提供了非常方便好用异步API。对于.NET上异步Socket的使用这里就不复述了,大家可以参阅MSDN上的相关内容。
在使用异步I/O的时候有些地方需要注意:一是网络上每次传来的数据并不是我们想读多少就传多少,并且不同于阻塞I/O编程,我们read 10个字节的数据,只有读完10个字节后才会进行下一步,对于.NET异步编程,,我们要自己去计算每个消息的完整性并记录未读完的数据以防止连包、粘包的发生。二是SocketAsyncEventArgs对象的复用性,我们需要适当使用池化来减少频繁的新对象创建。其实这个框架稍加改动既是一个服务器框架。
池化SocketAsyncEventArgs
.NET的异步Socket API需要频繁使用SocketAsyncEventArgs实例,我们需要设计一个池来存储、提供SocketAsyncEventArgs实例的复用,GenericPool<T>是我写的一个简单的泛型池实现。这里有个细节需要注意,每个对象入池和从池中去除前都可能需要一些清理/初始化之类的操作,一种方案是让每个池化的类型都实现一个接口,但是这种方式过于暴力和死板,我们定义一个IPoolAspectHandler<T>来解除Pool和池化对象间的耦合性。
{
void OnInitialize(T obj);
void OnPut(T obj);
void OnResolve(T obj);
void OnClear(T obj);
}
PoolAspectHandlerAdapter
public class PoolAspectHandlerAdapter<T> : IPoolAspectHandler<T>
{
#region IPoolAspectHandler<T> Members
public void OnInitialize(T obj)
{
}
public void OnPut(T obj)
{
}
public void OnResolve(T obj)
{
}
public void OnClear(T obj)
{
}
#endregion
}
SocketRecvEventArgsAspectHandler
class SocketRecvEventArgsAspectHandler : IPoolAspectHandler<SocketAsyncEventArgs>
{
public Action<SocketAsyncEventArgs> OnInitializeEvent;
public Action<SocketAsyncEventArgs> OnClearupEventReference;
#region IPoolAspectHandler<SocketAsyncEventArgs> Members
private static readonly int RECV_BUFFER_SIZE = 10;
public void OnInitialize(SocketAsyncEventArgs obj)
{
if(OnInitializeEvent != null)
OnInitializeEvent(obj);
var buffer = FastBuffer.Allocate(512);
buffer.AutoExtend = true;
var bufferStatus = new BufferStatus(RECV_BUFFER_SIZE)
{
Buffer = buffer
};
obj.UserToken = bufferStatus;
var recvBuffer = new byte[RECV_BUFFER_SIZE];
obj.SetBuffer(recvBuffer, 0, recvBuffer.Length);
}
public void OnPut(SocketAsyncEventArgs obj)
{
}
public void OnResolve(SocketAsyncEventArgs obj)
{
}
public void OnClear(SocketAsyncEventArgs obj)
{
obj.BufferList = null;
obj.UserToken = null;
}
#endregion
}
SocketSendEventArgsAspectHandler
class SocketSendEventArgsAspectHandler : IPoolAspectHandler<SocketAsyncEventArgs>
{
public Action<SocketAsyncEventArgs> OnInitializeEvent;
public Action<SocketAsyncEventArgs> OnClearupEventReference;
#region IPoolAspectHandler<SocketAsyncEventArgs> Members
public void OnInitialize(SocketAsyncEventArgs obj)
{
if(OnInitializeEvent != null)
OnInitializeEvent(obj);
}
public void OnPut(SocketAsyncEventArgs obj)
{
obj.UserToken = null;
}
public void OnResolve(SocketAsyncEventArgs obj)
{
}
public void OnClear(SocketAsyncEventArgs obj)
{
obj.SetBuffer(0, 0);
obj.BufferList = null;
}
#endregion
}

{
public static readonly int DEFAULT_MAXSIZE = 1000;
private ConcurrentQueue<T> _store = new ConcurrentQueue<T>();
private IPoolAspectHandler<T> _aspectHandler;
private int _maxSize = DEFAULT_MAXSIZE;
public static readonly GenericPool<T> Instance;
static GenericPool()
{
Instance = new GenericPool<T>();
}
public GenericPool()
: this(DEFAULT_MAXSIZE, new PoolAspectHandlerAdapter<T>())
{
}
public GenericPool(int maxSize, IPoolAspectHandler<T> aspectHandler)
{
this._maxSize = maxSize;
this._aspectHandler = aspectHandler;
}
public T Resolve()
{
T result = default(T);
_store.TryDequeue(out result);
if (result == null)
{
lock (_store)
{
_store.TryDequeue(out result);
if (result == null)
{
var obj = Activator.CreateInstance<T>();
this._aspectHandler.OnInitialize(obj);
if (Put(obj))
_store.TryDequeue(out result);
else
throw new InvalidOperationException("池已满");
}
}
}
return result;
}
public bool Put(T obj)
{
if (_store.Count < _maxSize)
{
this._aspectHandler.OnPut(obj);
_store.Enqueue(obj);
return true;
}
else
return false;
}
public void Empty()
{
while (!_store.IsEmpty)
{
var obj = Resolve();
this._aspectHandler.OnClear(obj);
}
}
public int Count
{
get
{
return this._store.Count;
}
}
public void Clear(T obj)
{
this._aspectHandler.OnClear(obj);
}
}
IDecoder和IEncoder
每当从网络上获取数据时,都将数据与上一次接消息残留的数据进行拼接并交由IDecode进行处理。通过IDecode接口的两个方法:Decodable和Decode,Decodable判断数据是否完整,返回3种枚举值(OK,NEED_DATA,BAD_DATA),如果读完或者拼接成了一个完整的数据则返回OK,如果缺少数据返回NEED_DATA,如果数据有错误返回BAD_DATA。
IEncoder<T>的作用是将类型为T的对象按照协议序编码为为byte[]数据。
public interface IDecoder
{
DecodeResult Decodable(byte[] data);
void Decode(byte[] data, out int usedSize, out object msg);
}
public enum DecodeResult
{
OK,
NOT_OK,
NEED_DATA
}
public interface IEncoder<T>
{
byte[] Encode(T msg);
}
IoHandler
IoHandler接口有六个方法,SessionOpened,SessionCreated,MessageSent,MessageReceived,ExceptionCaught,SessionClosed。
分别对应连接打开、创建、发送、接受、异常、关闭六种情况。我们可以再SessionCreated中队连接进行安全性审查或是初始化session。
在MessageReceived中进行业务处理,在SessionClosed中持久化用户的会话数据。
IoHandler
public interface IoHandler
{
void SessionOpened();
void SessionCreated();
void MessageSent(object msg);
void MessageReceived(object msg);
void ExceptionCaught();
void SessionClosed();
}对于丢包、粘包、数据拼接的处理
这就是为什么我们需要消息头首先告诉我们整个消息的长度是多少的原因。我们知道了消息长度,就可以判断第一次收到的消息是否完整或者包含写一条消息的数据。将多余的数据存在一个FastBuffer中,在下一次接受到数据时将新数据追加到这个FashBuffer中,再提交给IDecode.Decodable处理即可。
我们来看其完整的实现:

{
#region async sockets
private Socket _socket;
#endregion
#region reused socketasynceventargs
private GenericPool<SocketAsyncEventArgs> _asyncSendArgsPool = new GenericPool<SocketAsyncEventArgs>();
private GenericPool<SocketAsyncEventArgs> _asyncRecvArgsPool = new GenericPool<SocketAsyncEventArgs>();
#endregion
#region fields
//private IoSession session = new IoSession();
private ConnectionStatus _connectionStatus = ConnectionStatus.Unused;
#endregion
#region ctors
public AsyncConnector()
{
var sendAspectHandler = new SocketSendEventArgsAspectHandler();
sendAspectHandler.OnClearupEventReference = (p) =>
{
p.Completed -= _socketArgs_Completed;
};
_asyncSendArgsPool
= new GenericPool<SocketAsyncEventArgs>(GenericPool<SocketAsyncEventArgs>.DEFAULT_MAXSIZE, sendAspectHandler);
var recvAspectHandler = new SocketRecvEventArgsAspectHandler();
recvAspectHandler.OnInitializeEvent += (p) =>
p.Completed += _socketArgs_Completed;
recvAspectHandler.OnClearupEventReference = (p) =>
p.Completed -= _socketArgs_Completed;
_asyncRecvArgsPool
= new GenericPool<SocketAsyncEventArgs>(GenericPool <SocketAsyncEventArgs>.DEFAULT_MAXSIZE, recvAspectHandler);
}
#endregion
#region Property
/*public IoSession Session
{
get
{
return session;
}
}*/
public event ConnectionStatusChangedEventHandler OnConnectionStatusChanged;
public IoHandler Handler
{
get;
set;
}
public IProtocolFactory ProtocolFactory
{
get;
set;
}
#endregion
public void Connect(EndPoint endPoint)
{
_socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
SocketAsyncEventArgs _socketArgs = new SocketAsyncEventArgs();
_socketArgs.Completed += new EventHandler<SocketAsyncEventArgs>(_socketArgs_Connected);
_socketArgs.RemoteEndPoint = endPoint;
ConnectionStatus = IO.ConnectionStatus.Connecting;
if (!_socket.ConnectAsync(_socketArgs))
{
_socketArgs_Connected(_socket, _socketArgs);
}
}
public ConnectionStatus ConnectionStatus
{
get
{
return _connectionStatus;
}
set
{
if (value == _connectionStatus)
return;
if (OnConnectionStatusChanged != null)
OnConnectionStatusChanged(_connectionStatus, value);
_connectionStatus = value;
}
}
public void Close()
{
_socket.Close();
ConnectionStatus = IO.ConnectionStatus.Closed;
}
public void Close(bool isImmediately)
{
_socket.Close();
ConnectionStatus = IO.ConnectionStatus.Closed;
}
public void Abort()
{
_socket.Close();
ConnectionStatus = IO.ConnectionStatus.Closed;
}
public void Dispose()
{
if (_socket != null)
{
if (_socket.Connected)
{
_socket.Shutdown(SocketShutdown.Both);
_socket.Close();
_socket.Dispose();
_socket = null;
ConnectionStatus = IO.ConnectionStatus.Closed;
}
_asyncRecvArgsPool.Empty();
_asyncSendArgsPool.Empty();
}
//session.Dispose();
}
public void Send<T>(T msg)
{
var args = _asyncSendArgsPool.Resolve();
byte[] ret = ProtocolFactory.GetEncoder<T>().Encode(msg);
args.SetBuffer(ret, 0, ret.Length);
_socket.SendAsync(args);
}
private void _socketArgs_Connected(object sender, SocketAsyncEventArgs args)
{
args.Completed -= _socketArgs_Connected;
if (args.SocketError == SocketError.Success)
{
ConnectionStatus = IO.ConnectionStatus.Connected;
var recvArgs = _asyncRecvArgsPool.Resolve();
if (!_socket.ReceiveAsync(recvArgs))
_socketArgs_Received(sender, recvArgs);
}
else
{
ConnectionStatus = IO.ConnectionStatus.Unavailable;
}
}
private void _socketArgs_Completed(object sender, SocketAsyncEventArgs args)
{
switch(args.LastOperation)
{
case SocketAsyncOperation.Send:
_socketArgs_Sended(sender, args);
break;
case SocketAsyncOperation.Receive:
_socketArgs_Received(sender, args);
break;
}
}
private void _socketArgs_Received(object sender, SocketAsyncEventArgs args)
{
var socket = sender as Socket;
if (args.SocketError == SocketError.Success)
{
//check if the buffer is full used(ByteTransfered, OffSet)
var bufferStatus = args.UserToken as BufferStatus;
bufferStatus.Buffer.Append(args.Buffer, args.Offset/*bufferStatus.ReadOffset*/, args.BytesTransferred);
bufferStatus.IncreaseTransfered(args.BytesTransferred);
var decoder = ProtocolFactory.GetDecoder();
var data = bufferStatus.Buffer.copyAvaliableBytes();
var result = decoder.Decodable(data);
switch (result)
{
case DecodeResult.NEED_DATA:
socket.ReceiveAsync(args);
break;
case DecodeResult.NOT_OK:
throw new BadImageFormatException();
case DecodeResult.OK:
int usedSize;
object netMsg = null;
decoder.Decode(data, out usedSize, out netMsg);
ThreadPool.QueueUserWorkItem(Handler.MessageReceived, netMsg);
bufferStatus.Buffer.Reset();
bufferStatus.Buffer.Append(data, usedSize, data.Length - usedSize);
socket.ReceiveAsync(args);
break;
}
}
}
private void _socketArgs_Sended(object sender, SocketAsyncEventArgs args)
{
if (args.SocketError == SocketError.Success)
{
}
else
{
}
_asyncSendArgsPool.Put(args);
}
}
https://files.cnblogs.com/wJiang/ClientConnLib.rar