如果ESFramework对您的项目来说,太庞大、太重量级;如果您的项目不需要P2P、不需要传文件、不需要群集等功能,只需要简单的TCP/UDP通信。那么,可以考虑使用轻量级的通信引擎StriveEngine。相比较而言,StriveEngine更单纯、更容易上手,也更容易与已存在的遗留系统进行协作。
一.StriveEngine 主要特性
1.底层采用高效的IOCP(完成端口)模型。
2.内部自动管理可复用的线程池、以及内存池。
3.内置多种通信引擎类型:TCP/UDP、文本协议/二进制协议、服务端/客户端。而且,所有这些引擎的使用方式一致。
4.解决了TCP通信中的粘包以及消息重组问题。
5.发送消息支持同步、异步两种方式。
6.服务端引擎支持异步消息队列模式。
7.客户端TCP引擎支持断线自动重连。
8.支持Sock5代理。
9.兼容IPv6。
二.StriveEngine与ESFramework/ESPlus/ESPlatform的区别
1.ESFramework 是一个功能强大的通信框架/平台,而StriveEngine是一个单纯高效的通信引擎类库。
2.ESFramework 更贴近应用层,而StriveEngine更贴近Socket层。
3.ESFramework 使用UserID标志每一个在线的客户端,而StriveEngine则使用低阶的IPEndPoint来标志每一个在线的客户端。
4.可以认为StriveEngine就是ESFramework底层使用的内核。
5.ESFramework 定义了底层通信消息的格式(对ESFramework使用者是透明的),而StriveEngine对通信消息的格式未作任何定义。
6.ESFramework和StriveEngine都提供了服务端引擎和客户端引擎。当涉及到要开发新的其它平台引擎来与ESFramework或StriveEngine协作时,比如:
(1)如果要开发其它平台的客户端引擎与ESFramework的服务端引擎协作,则必需实现ESPlus协议格式;
(2)如果要开发其它平台的客户端引擎与StriveEngine的服务端引擎协作,由于StriveEngine未定义任何消息格式,所以不存在要必需实现既有协议格式的问题。
(3)与(2)同理,使用 StriveEngine的客户端引擎,可以与任何其它现有的服务端协作。
7.对于那些涉及到在线用户管理、以UserID为中心的应用(比如即时通信应用),或者需要P2P通信、传文件等功能的应用来说,ESFramework更适合;
对于那些仅仅需要简单而高效的通信功能的应用(如数据采集、消息转发等)来说,StriveEngine更合适。
三.StriveEngine 整体结构
StriveEngine内置了多种通信引擎,这些通信引擎都以接口的方式暴露了其功能,而不同的引擎却都依据某些共通的部分,继承了相同的基接口。 引擎接口的继承关系如下所示:
INetworkEngine 是所有引擎的根接口,只要是StriveEngine中的内置引擎,都实现了这个接口。
IServerEngine 是服务端引擎接口。IPassiveEngine 是客户端引擎接口。
ITcpServerEngine 是基于TCP的服务端引擎接口。ITcpPassiveEngine是基于TCP的客户端引擎接口。
IUdpEngine 是基于UDP的通信引擎接口。由于UDP是非连接协议,可以认为UDP通信的双方是对等的,所以,服务端和客户端都使用IUdpEngine即可。
ITextEngine 是基于文本协议的通信引擎的基接口。
下面,我们来详细看看每个接口。
1.INetworkEngine
/// <summary>
/// 所有网络引擎(包括服务端引擎、客户端引擎、TCP引擎、UDP引擎、二进制引擎、文本引擎)的基础接口。
/// </summary>
public interface INetworkEngine : IDisposable
{
/// <summary>
/// 传输层协议类型,TCP或UDP
/// </summary>
ProtocolType ProtocolType { get; }
/// <summary>
/// 引擎实例的创建时间。
/// </summary>
DateTime CreateTime { get; }
/// <summary>
/// 表示要监听本地的哪个端口。如果设定其值小于等于0,则表示由系统自动分配。
/// 注意,引擎初始化完成后,不能再修改该属性。
/// </summary>
int Port { get; set; }
/// <summary>
/// 对于服务端引擎,表示要绑定本地的哪个IP,如果设为null,则表示绑定本地所有IP。
/// 对于客户端引擎,表示要绑定本地的哪个IP与服务器进行通信。如果设为null(其值会在初始化完成后被修改),则自动选择地址列表中的某一个。
/// 注意,引擎初始化完成后,不能再修改该属性。
/// </summary>
string IPAddress { get; set; }
/// <summary>
/// 设置日志记录的文件路径。如果设为null,表示不记录日志。默认值为null。
/// </summary>
string LogFilePath { set; }
/// <summary>
/// Socket(网卡)发送缓冲区的大小。默认为8k。
/// </summary>
int SocketSendBuffSize { get; set; }
/// <summary>
/// Socket(网卡)接收缓冲区的大小。默认为8k。
/// </summary>
int SocketReceiveBuffSize { get; set; }
/// <summary>
/// 网络引擎能够接收的最大的消息尺寸。据此网络引擎可以为每个Session/Connection开辟适当大小的接收缓冲区。
/// 默认为1k。当接收到的消息尺寸超过MaxMessageSize时,将会关闭对应的连接(对TCP)或丢弃数据(对UDP)。
/// </summary>
int MaxMessageSize { get; set; }
/// <summary>
/// 引擎实例是否已经被释放。
/// </summary>
bool Disposed { get; }
/// <summary>
/// 初始化并启动网络引擎。如果修改了引擎配置参数,在应用新参数之前必须先重新调用该方法初始化引擎。
/// </summary>
void Initialize();
/// <summary>
/// 当不再使用当前引擎实例时,释放它。(异步方式)
/// 注意:对于UDP或客户端引擎,如果消息是同步处理的(HandleMessageAsynchronismly为false),请不要在消息处理器中调用Dispose方法,否则,会导致死锁。因为停止引擎要等到最后一条消息处理完毕才会返回。
/// 可以转向调用异步的DisposeAsyn方法。
/// </summary>
void DisposeAsyn();
/// <summary>
/// 当引擎实例被释放时,触发此事件。
/// </summary>
event CbDelegate<INetworkEngine> EngineDisposed;
/// <summary>
/// 接收到一个完整的消息时触发该事件。
/// </summary>
event CbDelegate<IPEndPoint,byte[]> MessageReceived;
/// <summary>
/// 将消息成功发送之后触发该事件
/// </summary>
event CbDelegate<IPEndPoint, byte[]> MessageSent;
}
/// <summary>
/// 传输层协议类型。
/// </summary>
public enum ProtocolType
{
TCP = 0,
UDP
}
/// <summary>
/// 协议的格式。
/// </summary>
public enum ContractFormatStyle
{
/// <summary>
/// 流协议或称二进制协议。
/// </summary>
Stream = 0,
/// <summary>
/// 文本协议,如基于xml的。
/// </summary>
Text
}
(1)StriveEngine中的所有通信引擎在设置完必要的属性后,都必须调用Initialize方法进行初始化,初始化完成后,引擎实例才开始正常工作。
(2)INetworkEngine从IDisposable继承,表明通信引擎内部持有了重要的资源,当不再使用其实例时,要尽快调用IDisposable的Dispose方法释放资源。
(3)当通信引擎被释放后,会触发EngineDisposed事件,并且此后,Disposed属性将返回true。
(4)请根据应用的需要谨慎地设置MaxMessageSize,如果设置的过大,可能会造成内存空间的浪费(特别是对于基于文本协议的服务端引擎)。
(5)通过MessageReceived事件,可以得到通信引擎接收到的所有消息;通过MessageSent事件,可以监控通信引擎发送出去的所有消息。
2.IServerEngine
/// <summary>
/// 服务端引擎接口。
/// </summary>
public interface IServerEngine : INetworkEngine
{
/// <summary>
/// 到目标客户的通道是否繁忙?
/// </summary>
bool ChannelIsBusy(IPEndPoint client);
/// <summary>
/// 给某个客户端同步发信息。注意:如果引擎已经停止或客户端不在线,则直接返回。
/// </summary>
/// <param name="client">接收数据的客户端</param>
/// <param name="msg">消息</param>
void SendMessageToClient(IPEndPoint client, byte[] msg);
/// <summary>
/// 给某个客户端同步发信息。注意:如果引擎已经停止或客户端不在线,则直接返回。
/// </summary>
/// <param name="client">接收数据的客户端</param>
/// <param name="msg">消息</param>
/// <param name="action">当通道繁忙时采取的动作:继续发送或丢弃。</param>
void SendMessageToClient(IPEndPoint client, byte[] msg, ActionTypeOnChannelIsBusy action);
/// <summary>
/// 给某个客户端异步发信息。注意:如果引擎已经停止或客户端不在线,则直接返回。
/// </summary>
/// <param name="client">接收数据的客户端</param>
/// <param name="msg">消息</param>
void PostMessageToClient(IPEndPoint client, byte[] msg);
/// <summary>
/// 给某个客户端异步发信息。注意:如果引擎已经停止或客户端不在线,则直接返回。
/// </summary>
/// <param name="client">接收数据的客户端</param>
/// <param name="msg">消息</param>
/// <param name="action">当通道繁忙时采取的动作:继续发送或丢弃。</param>
void PostMessageToClient(IPEndPoint client, byte[] msg, ActionTypeOnChannelIsBusy action);
}
(1)SendMessageToClient和PostMessageToClient 分别表示同步和异步发送消息给客户端。
(2)ChannelIsBusy 指的是在与目标客户端的TCP连接上,是否有数据正在发送(服务端至客户端)。
(3)ActionTypeOnChannelIsBusy 参数允许我们在通道繁忙时,丢弃不重要的消息。
3.IPassiveEngine
/// <summary>
/// 客户端引擎接口。
/// </summary>
public interface IPassiveEngine :INetworkEngine
{
/// <summary>
/// 服务器地址。
/// </summary>
AgileIPE ServerIPEndPoint { get; set; }
/// <summary>
/// 发送消息的通道是否正忙。使用者可以根据该属性决定是否要丢弃后续某些消息的发送。
/// </summary>
bool ChannelIsBusy { get; }
/// <summary>
/// 将消息异步发送给服务器,不经任何处理,直接发送。注意:如果引擎已经停止,则直接返回。
/// </summary>
/// <param name="msg">要发送的消息</param>
/// <param name="action">当通道繁忙时采取的动作:继续发送或丢弃。</param>
void SendMessageToServer(byte[] msg, ActionTypeOnChannelIsBusy action);
/// <summary>
/// 将消息异步发送给服务器,不经任何处理,直接发送。注意:如果引擎已经停止,则直接返回。
/// </summary>
/// <param name="msg">要发送的消息</param>
void SendMessageToServer(byte[] msg);
/// <summary>
/// 将消息同步发送给服务器,不经任何处理,直接发送。注意:如果引擎已经停止,则直接返回。
/// </summary>
/// <param name="msg">要发送的消息</param>
/// <param name="action">当通道繁忙时采取的动作:继续发送或丢弃。</param>
void PostMessageToServer(byte[] msg, ActionTypeOnChannelIsBusy action);
/// <summary>
/// 将消息同步发送给服务器,不经任何处理,直接发送。注意:如果引擎已经停止,则直接返回。
/// </summary>
/// <param name="msg">要发送的消息</param>
void PostMessageToServer(byte[] msg);
}
(1)SendMessageToServer和PostMessageToServer 分别表示同步和异步发送消息给服务端。
(2)ChannelIsBusy 指的是当前与服务器的TCP连接上,是否有数据正在发送(客户端至服务端)。
(3)ActionTypeOnChannelIsBusy 参数允许我们在通道繁忙时,丢弃不重要的消息。
4.ITcpServerEngine
/// <summary>
/// TCP服务端引擎接口。
/// </summary>
public interface ITcpServerEngine : IServerEngine
{
/// <summary>
/// 引擎所采用的协议类型(二进制、文本)。
/// </summary>
ContractFormatStyle ContractFormatStyle { get; }
/// <summary>
/// 当前在线客户端的数量。
/// </summary>
int ClientCount { get; }
/// <summary>
/// 服务器允许最大的同时在线客户端数。
/// </summary>
int MaxClientCount { get;}
/// <summary>
/// 给每个连接发送数据超时时间(单位:毫秒。默认为-1,表示无限)。如果在指定的时间内未将数据发送完,则关闭对应的连接。 /// </summary>
int WriteTimeoutInMSecs { get; set; }/// <summary>
/// 每个通道连接上允许最大的等待发送【包括投递】以及正在发送的消息个数。
/// 当等待发送以及正在发送的消息个数超过该值时,将关闭对应的连接。如果设置为0,则表示不作限制。默认值为0。 /// </summary>
int MaxChannelCacheSize { get; set; }
/// <summary>
/// 监听器是否开启。
/// </summary>
bool IsListening { get; }
/// <summary>
/// 关闭到某个客户端的连接,将触发SomeOneDisconnected事件。
/// </summary>
void CloseClient(IPEndPoint client);
/// <summary>
/// 关闭所有客户端的连接。
/// </summary>
void CloseAllClient();
/// <summary>
/// 关闭或开启监听器。该方法调用不影响网络引擎的消息接收和处理。
/// </summary>
void ChangeListenerState(bool enabled);
/// <summary>
/// 获取所有在线连接的客户端的地址。
/// </summary>
List<IPEndPoint> GetClientList();
/// <summary>
/// 客户端是否在线?
/// </summary>
bool IsClientOnline(IPEndPoint client);
/// <summary>
/// 当某TCP连接断开时,触发该事件
/// </summary>
event CbDelegate<IPEndPoint> ClientDisconnected;
/// <summary>
/// 当某TCP连接建立成功时,触发此事件。
/// </summary>
event CbDelegate<IPEndPoint> ClientConnected;
/// <summary>
/// 当tcp连接数量发生变化时,触发此事件。
/// </summary>
event CbDelegate<int> ClientCountChanged;
/// <summary>
/// 当连接监听器的状态发生变化时,触发此事件。事件参数为true,表明连接监听器启动;事件参数为false,表明连接监听器已停止。
/// </summary>
event CbDelegate<bool> ListenerStateChanged;
}
(1)WriteTimeoutInMSecs 用于设置发送数据的超时。 最好给该属性赋一个适当的值,因为在某些情况下,发送数据可能会导致很长时间的阻塞。该属性只对同步发送有效。
(2)MaxChannelCacheSize 是服务端的一个安全设置。该设置用于防止服务器为速度慢的通道缓存太多的消息,而导致服务器内存无限制增长。
(3)ChangeListenerState 用于改变服务器的监听状态,其将触发ListenerStateChanged事件,并改变IsListening属性的值。
如果IsListening为false,表示当前不接受新的TCP连接请求。
(4)当有连接建立或断开时,将分别触发ClientConnected和ClientDisconnected事件。
5.ITcpPassiveEngine
/// <summary>
/// 客户端TCP引擎接口。
/// </summary>
public interface ITcpPassiveEngine :IPassiveEngine
{
/// <summary>
/// 引擎所采用的协议类型(二进制、文本)。
/// </summary>
ContractFormatStyle ContractFormatStyle { get; }
/// <summary>
/// 当客户端与服务器的TCP连接断开时,将触发此事件。
/// </summary>
event CbDelegate ConnectionInterrupted;
/// <summary>
/// 自动重连开始时,触发此事件。
/// </summary>
event CbDelegate ConnectionRebuildStart;
/// <summary>
/// 自动重连成功后,触发此事件。
/// </summary>
event CbDelegate ConnectionRebuildSucceed;
/// <summary>
/// 自动重连超过最大重试次数时,表明重连失败,将触发此事件。
/// </summary>
event CbDelegate ConnectionRebuildFailure;
/// <summary>
/// Sock5代理服务器信息。如果不需要代理,则设置为null。
/// </summary>
Sock5ProxyInfo Sock5ProxyInfo { get; set; }
/// <summary>
/// 当前是否处于连接状态。
/// </summary>
bool Connected { get; }
/// <summary>
/// 当与服务器断开连接时,是否自动重连。
/// </summary>
bool AutoReconnect { get; set; }
/// <summary>
/// 当连接断开时,自动重连尝试的最大次数。默认值为int.MaxValue。
/// </summary>
int MaxRetryCount4AutoReconnect { get; set; }
/// <summary>
/// 主动关闭与服务器的连接。如果AutoReconnect为true,将引发自动重连。
/// </summary>
void CloseConnection();
/// <summary>
/// 手动重连。如果当前处于连接状态,则直接返回。
/// </summary>
/// <param name="retryCount">重试次数</param>
/// <param name="retrySpanInMSecs">重试间隔时间,毫秒</param>
void Reconnect(int retryCount, int retrySpanInMSecs);
}
(1)如果AutoReconnect设置为true,表示启用自动重连,那么,当连接断开时,会按以下顺序触发相关事件: ConnectionInterrupted 、ConnectionRebuildStart 、 ConnectionRebuildSucceed/ConnectionRebuildFailure。
(2)注意,如果AutoReconnect设置为true,CloseConnection将会先关闭当前连接,然后再启动自动重连。
6.IUdpEngine
/// <summary>
/// Udp引擎基础接口。
/// </summary>
public interface IUdpEngine :INetworkEngine
{
/// <summary>
/// 向指定的端点发送UDP消息。注意:如果引擎已经停止,则直接返回。
/// </summary>
/// <param name="message">要发送的消息</param>
/// <param name="address">目标端点</param>
void SendMessage(IPEndPoint address, byte[] message);
/// <summary>
/// 向指定的端点投递UDP消息。注意:如果引擎已经停止,则直接返回。
/// </summary>
/// <param name="message">要投递的消息</param>
/// <param name="address">目标端点</param>
void PostMessage(IPEndPoint address, byte[] message);
}
UDP是非连接的协议,所以,UDP引擎不用区分客户端和服务端,或者说,无论是客户端还是服务端,都可以使用IUdpEngine。注意:IUdpEngine也从INetworkEngine继承,所以,它具备了StriveEngine中基础引擎所有的功能。
四.如何使用StriveEngine
在StriveEngine中,我们不能直接new某个通信引擎的class来获得其实例。StriveEngine提供了NetworkEngineFactory,我们可以通过工厂的静态方法来得到通信引擎实例的引用。
1.通信引擎工厂
/// <summary>
/// 通信引擎工厂。
/// </summary>
public static class NetworkEngineFactory
{
/// <summary>
/// 创建使用二进制协议的TCP服务端引擎。对于返回的引擎实例,可以设置其更多属性,然后调用其Initialize方法启动引擎。
/// </summary>
/// <param name="port">服务端引擎监听的端口号</param>
/// <param name="helper">二进制协议助手接口</param>
public static ITcpServerEngine CreateStreamTcpServerEngine(int port, IStreamContractHelper helper);
/// <summary>
/// 创建使用文本协议的TCP服务端引擎。对于返回的引擎实例,可以设置其更多属性,然后调用其Initialize方法启动引擎。
/// 注意:返回的引擎实例,可以强转为ITextEngine接口。
/// </summary>
/// <param name="port">服务端引擎监听的端口号</param>
/// <param name="helper">文本协议助手接口</param>
public static ITcpServerEngine CreateTextTcpServerEngine(int port, ITextContractHelper helper);
/// <summary>
/// 创建使用二进制协议的TCP客户端引擎。对于返回的引擎实例,可以设置其更多属性,然后调用其Initialize方法启动引擎。
/// </summary>
/// <param name="serverIP">要连接的服务器的IP</param>
/// <param name="serverPort">要连接的服务器的端口</param>
/// <param name="helper">二进制协议助手接口</param>
public static ITcpPassiveEngine CreateStreamTcpPassivEngine(string serverIP, int serverPort, IStreamContractHelper helper);
/// <summary>
/// 创建使用文本协议的TCP客户端引擎。对于返回的引擎实例,可以设置其更多属性,然后调用其Initialize方法启动引擎。
/// 注意:返回的引擎实例,可以强转为ITextEngine接口。
/// </summary>
/// <param name="serverIP">要连接的服务器的IP</param>
/// <param name="serverPort">要连接的服务器的端口</param>
/// <param name="helper">文本协议助手接口</param>
public static ITcpPassiveEngine CreateTextTcpPassiveEngine(string serverIP, int serverPort, ITextContractHelper helper);
/// <summary>
/// 创建UDP引擎。对于返回的引擎实例,可以设置其更多属性,然后调用其Initialize方法启动引擎。
/// </summary>
public static IUdpEngine CreateUdpEngine();
}
我们可以根据项目的需要(是TCP还是UDP、是文本协议还是二进制协议、是用于客户端还是用于服务端),来调用NetworkEngineFactory的对应方法获得正确的通信引擎实例。
注意:NetworkEngineFactory创建的所有通信引擎实例,必须要调用其Initialize方法后,引擎才算正常启动。当然,在调用Initialize之前,可以根据需要设置其相关的属性。
2.ContractHelper
StriveEngine内部通过ContractHelper来从接收的网络流中识别完整的消息,针对消息格式为文本和二进制,ContractHelper就划分为对应的ITextContractHelper 和IStreamContractHelper。我们看到,在通过NetworkEngineFactory创建通信引擎实例时,其有个参数是必须传入ContractHelper引用的。所以,在项目中,我们必须实现ITextContractHelper或者是IStreamContractHelper。
(1) ITextContractHelper
/// <summary>
/// 文本协议助手接口。
/// </summary>
public interface ITextContractHelper
{
/// <summary>
/// 消息的结束标识符(经过编码后得到的字节数组)。
/// </summary>
byte[] EndToken { get; }
}
当使用文本协议时,通常,使用某一个或多个特殊的字符作为消息的结束符(EndToken)。ITextContractHelper的EndToken是一个byte[],其指的是消息的结束符经过编码(比如UTF-8)后得到的字节数组。通信引擎即通过ITextContractHelper提供的EndToken来从网络流中识别完整的消息。
如果是使用UTF-8对文本消息(当然,也包括消息结束符)进行编解码,则可以使用StriveEngine内置的DefaultTextContractHelper,其实,它的实现非常简单:
/// <summary>
/// StriveEngine内置的ITextContractHelper实现。使用UTF-8对EndToken进行编码。
/// </summary>
public class DefaultTextContractHelper : ITextContractHelper
{
public DefaultTextContractHelper(string endTokenStr)
{
this.endToken = System.Text.Encoding.UTF8.GetBytes(endTokenStr);
}
private byte[] endToken;
public byte[] EndToken
{
get
{
return this.endToken;
}
}
}
(2)IStreamContractHelper
/// <summary>
/// 二进制协议助手接口。
/// </summary>
public interface IStreamContractHelper
{
/// <summary>
/// 从消息头中解析出消息体的长度。
/// </summary>
/// <param name="head">完整的消息头,长度固定为MessageHeaderLength</param>
/// <returns>消息体的长度</returns>
int ParseMessageBodyLength(byte[] head);
/// <summary>
/// 消息头的长度。
/// </summary>
int MessageHeaderLength { get; }
}
当使用二进制协议时,通常,消息分为消息头(Header)和消息体(Body)两部分,消息头是必须的,而消息体可以为null。消息头的长度是固定的(比如8个字节),且其至少包含了一个字段--消息体的长度(或根据消息头的内容可以间接结算出消息体的长度)。
3.使用StriveEngine的步骤
(1)实现ITextContractHelper或者是IStreamContractHelper接口。
(2)调用NetworkEngineFactory的创建引擎的方法,得到正确的通信引擎实例。
(3)根据需要,设置引擎实例的某些属性(如MaxMessageSize属性)。
(4)根据需要,预定引擎实例的某些事件(如MessageReceived事件)。
(5)调用引擎实例的Initialize方法启动通信引擎。
五.Demo下载
通过上述的内容大致了解了StriveEngine后,接下来我们通过一个简单的demo来看看在实际项目中是如何使用StriveEngine的。
该Demo是一个简单的客户端与服务端进行消息通信的demo,消息使用文本协议。该Demo总共包括三个项目:
(1)StriveEngine.SimpleDemoServer:基于StriveEngine开发的服务端。
(2)StriveEngine.SimpleDemoClient:基于StriveEngine开发的客户端。
(3)StriveEngine.SimpleDemo:直接基于.NET的Socket开发的客户端,其目的是为了演示:在客户端不使用StriveEngine的情况下,如何与基于StriveEngine的服务端进行通信。
Demo运行起来后的截图如下所示:
更多信息请参见:www.oraycn.com