轻量级的通信引擎 StriveEngine
如果ESFramework对您的项目来说,太庞大、太重量级;如果您的项目不需要P2P、不需要传文件、不需要群集等功能,只需要简单的TCP/UDP通信。那么,可以考虑使用轻量级的通信引擎StriveEngine。相比较而言,StriveEngine更单纯、更容易上手,也更容易与已存在的遗留系统进行协作。
一.StriveEngine 主要特性
01. 底层采用高效的IOCP(完成端口)模型。
02. 内部自动管理可复用的线程池、以及内存池。
03. 内置多种通信引擎类型:TCP/UDP、文本协议/二进制协议、服务端/客户端。而且,所有这些引擎的使用方式一致。
04. 解决了TCP通信中的粘包以及消息重组问题。
05. 发送消息支持同步、异步两种方式。
06. 服务端引擎支持异步消息队列模式。
07. 客户端TCP引擎支持断线自动重连。
08. 支持 HTML5 Web Sockets。
09. 提供了 Unity3D客户端引擎、WinCE客户端引擎。
10. 支持Sock5代理。
11. 兼容IPv6。
二.StriveEngine与ESFramework/ESPlus/ESPlatform的区别
1. ESFramework 是一个功能强大的通信框架/平台,而StriveEngine是一个单纯高效的通信引擎类库。
2. ESFramework 更贴近应用层(比如支持在线用户管理、文件传送、P2P通道、好友与组、群组广播、服务器群集、性能诊断等),而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> /// 关闭或开启监听器。如果监听器关闭,将不再接受新的TCP连接请求。该方法调用不影响已有TCP连接上的消息接收和处理。 /// </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>
List<byte[]> EndTokens { get; }
}
当使用文本协议时,通常,使用某一个或多个特殊的字符作为消息的结束符(EndToken)。ITextContractHelper的EndToken是一个byte[],其指的是消息的结束符经过编码(比如UTF-8)后得到的字节数组。而在一个系统中,可能会存在多个消息结束符(比如为了兼容多个老的系统),所以,EndTokens是一个集合,其中每一个元素都表示一个结束符。通信引擎即通过ITextContractHelper提供的EndTokens来从网络流中识别完整的消息。(特别说明,若指定EndTokens为null,则表示不需要处理粘包以及进行消息完整性识别,此时,引擎会直接返回接收到的数据)。
如果是使用UTF-8对文本消息(当然,也包括消息结束符)进行编解码,则可以使用StriveEngine内置的DefaultTextContractHelper,其实,它的实现非常简单:
public class DefaultTextContractHelper : ITextContractHelper { public DefaultTextContractHelper(params string[] endTokenStrs) { this.endTokens = new List<byte[]>(); ; if (endTokenStrs == null || endTokenStrs.Length == 0) { return; } foreach (string str in endTokenStrs) { this.endTokens.Add(System.Text.Encoding.UTF8.GetBytes(str)); } } private List<byte[]> endTokens; public List<byte[]> EndTokens { get { return this.endTokens; } } }
如果想像上面说的不需要识别消息完整性,即将EndTokens为null,那么可以使用下面这个ITextContractHelper实现:
public class DefaultTextContractHelper2 : ITextContractHelper { public List<byte[]> EndTokens { get { return null; } } }
(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接口(如何实现该接口,可参考后面demo的源码)。
(2)调用NetworkEngineFactory的创建引擎的方法,得到正确的通信引擎实例。
(3)根据需要,设置引擎实例的某些属性(如MaxMessageSize属性)。
(4)根据需要,预定引擎实例的某些事件(如MessageReceived事件)。
(5)调用引擎实例的Initialize方法启动通信引擎。
五.支持多种客户端平台类型
StriveEngine在客户端除了支持.NET平台外,还支持如下平台:Unity3D、WinCE、HTML5 Web Sockets。
1.Unity3D 平台
StriveEngine.U3D.dll 是专门为Unity3D而准备的,是StriveEngine中客户端引擎向Unity3D的完全移植,命名空间、接口名称等等一切都是完全一样的。也就是说,如果您的客户端环境是Unity3D,那么只需要引用StriveEngine.U3D.dll,就可以了,上面所讲述的一切对它都是有效的。
StriveEngine.U3D使用的是.NET Framework的子集,其可被Unity3D打包发布到pc、web、android、ios等平台。
在一般的应用开发中,服务端和客户端引用同一个StriveEngine.dll 即可;而在Unity3D网络游戏开发中,服务端仍然引用StriveEngine.dll ,而客户端将引用StriveEngine.U3D.dll。
2.WinCE 平台
StriveEngine.CE.dll 是专门为WindowsCE嵌入式和移动设备而准备的,是StriveEngine中客户端引擎向WindowsCE的完全移植,命名空间、接口名称等等一切都是完全一样的。也就是说,如果您的客户端环境是WindowsCE,那么只需要引用StriveEngine.CE.dll,上面所讲述的一切对它都是有效的。
同StriveEngine.U3D一样,在WinCE网络通信系统开发中,服务端仍然引用StriveEngine.dll ,而WinCE客户端将引用StriveEngine.CE.dll。
3.HTML5 Web Sockets
StriveEngine对Web Sockets的支持是内置的。如果客户端的类型是Web Sockets,那么,StriveEngine服务端引擎会自动完成如下几件事情:
(1)自动完成Web Sokects 握手协议。
(2)针对接收到的每个WebSokect数据帧,都会自动将其解析,并从中分离出真正的消息内容 -- 服务端引擎的MessageReceived事件,暴露的就是解析后的真正消息内容。
(3)当您调用SendMessageToClient方法发送消息给客户端时,服务端引擎会自动将该消息封装成WebSokect数据帧,然后再发送出去。
所以,在使用StriveEngine处理Web Sockets客户端时,使用者不需要做任何额外的处理。如果在服务端想知道某个客户端是否为Web Sockets,可以调用服务端引擎的IsWebClient方法。
/// <summary> /// 客户端是否为WebSocket?如果返回null,表示还未接收到来自客户端的任何数据以进行判断或者是客户端不在线。 /// </summary> bool? IsWebClient(IPEndPoint client);
最后请注意:在绝大多数情况下,客户端通过Web Socket发送的都是文本消息,而服务端引擎触发MessageReceived事件的参数是byte[],那么如何将其还原成文本消息了?很简单,只要使用UTF-8将byte[]解析成字符串就可以了(调用System.Text.Encoding.UTF8.GetString()方法)。
六.Demo及下载
通过上述的内容大致了解了StriveEngine后,接下来我们通过两个简单的demo,来看看在实际项目中是如何基于文本协议和二进制协议来使用StriveEngine的。
1.基于文本协议的demo(使用TCP)
该Demo是一个简单的客户端与服务端进行消息通信的demo,消息使用文本协议。demo中,消息的结束符标志符采用的是一个字符('\0'),消息内容使用UTF-8进行编码。
该Demo总共包括三个项目和一个HTML文件:
(1)StriveEngine.SimpleDemoServer:基于StriveEngine开发的服务端。
(2)StriveEngine.SimpleDemoClient:基于StriveEngine开发的客户端。
(3)StriveEngine.SimpleDemo:直接基于.NET的Socket开发的客户端,其目的是为了演示:在客户端不使用StriveEngine的情况下,如何与基于StriveEngine的服务端进行通信。
(4)WebSocketClient.html:基于HTML5 WebSocket的客户端。与前两种客户端公用同一个StriveEngine服务端。
Demo运行起来后的截图如下所示:
加入WebSocket客户端(用浏览器打开WebSocketClient.html页面):
2.基于二进制协议的demo(使用TCP)
该Demo演示了客户端提交运算请求给服务端,服务端处理后,将结果返回给客户端。消息使用二进制协议。demo中,消息头固定为8个字节:前四个字节为一个int,表示消息体的长度;后四个字节也是一个int,表示消息的类型。
该Demo总共包括三个项目:
(1)StriveEngine.BinaryDemoServer:基于StriveEngine开发的服务端。
(2)StriveEngine.BinaryDemo:基于StriveEngine开发的客户端。
(3)StriveEngine.BinaryDemoCore:用于定义客户端和服务端都要用到的公共的消息类型和消息协议的基础程序集。
Demo运行起来后的截图如下所示:
3.基于二进制协议的demo(使用UDP)
该Demo与上面的基于二进制协议的TCP的demo几乎一样,只不过底层使用了StriveEngine的UDP引擎。