消息服务器模型,ESFramework 开发手册(11) -- 服务端信息处理模型 - 傲瑞科技 - 官方博客...

基于ESFramework进行二次开发时,主要是通过自定义信息来实现业务逻辑功能,而自定义消息的处理则是通过ICustomizeHandler接口的HandleInformation(或HandleQuery)方法来进行的。但是,ICustomizeHandler接口的这两个方法是如何被框架调用的了?在介绍之前,我们先要了解一下.NET的线程池(ThreadPool)。

一. 线程池

.NET线程池中有两种类型的线程:工作者线程、完成端口IOCP线程。通过ThreadPool的静态方法SetMinThreads、SetMaxThreads可以对线程池做一些简单的设置。

一般在程序启动时,我们可以调用 System.Threading.ThreadPool.SetMaxThreads(100,

100);  将最大的工作者线程和IOCP线程数量设置为100。

然后,在服务端运行的过程中,可定时调用(比如每秒一次)ThreadPool的GetAvailableThreads方法,来查看线程中可用的(即,空闲的)工作者线程和IOCP线程的数量。这样就可以实时监控线程池中线程的状态。

ESFramework对ICustomizeHandler接口的调用都是在线程池的IOCP线程或工作者线程中进行的,至于究竟使用的是哪种类型的线程,取决于信息处理模型的设定。

另外说一下,ESFramework框架本身使用了数个工作者线程,以维持网络引擎的正常运转,但是,ESFramework框架自身的运转并不消耗任何IOCP线程。

二. CustomizeInfoHandleMode

ESFramework服务端为信息处理提供了两种方案,这两种方案通过CustomizeInfoHandleMode枚举进行定义,并对应该枚举有两个取值:IocpDirectly、TaskQueue。

我们知道,ESFramework内核是基于IOCP(Windows系统中最高效的模型)的,即服务端接收到的信息都是在IOCP线程中提交的, 对于提交的信息:

(1)如果信息处理模型被指定为IocpDirectly,则框架将直接在该IOCP线程中调用ICustomizeHandler接口来处理该信息。

(2)如果信息处理模型被指定为TaskQueue,则框架首先会将信息放入到一个任务队列中。然后由工作者线程从队列中依次取出信息,并调用ICustomizeHandler接口进行处理。

这里可以看出,这两种方案有几个明显的区别:

(1)IocpDirectly方案,ICustomizeHandler的调用是在IOCP线程中进行的;而TaskQueue方案,ICustomizeHandler的调用是在工作者线程中进行的。

(2)对于TaskQueue方案,IOCP线程的工作仅仅是将接收的信息放入到任务队列中。

我们可以通过IRapidServerEngine的Advanced控制器的CustomizeInfoHandleMode属性来指定要使用的方案(默认是IocpDirectly), 像这样:

rapidServerEngine.Advanced.CustomizeInfoHandleMode = CustomizeInfoHandleMode.TaskQueue;

对于二次开发人员而言,从一种方案切换到另一种方案,体现在代码上只是上面属性设置的修改,不需要在做任何其它的事情,框架内部会自动完成相应的工作。下面我们将深入这两种方案。

三. IocpDirectly方案

无论是使用IocpDirectly方案还是TaskQueue方案,一个客户端连接最多用到一个IOCP线程(提交接收到的消息的时候才用到)。但是对于IocpDirectly方案,由于提交消息是直接进入ICustomizeHandler的HandleInformation(或HandleQuery)方法调用,所以,IocpDirectly方案有以下特点:

(1)针对一个具体的TCP连接而言,信息处理是串行的。只有当前一个信息处理完成(即HandleInformation /

HandleQuery方法返回)后,才处理该连接上的下一个请求信息。

(2)针对所有的TCP连接而言,信息处理是并行的。即同时有多个HandleInformation / HandleQuery方法

正在执行。

(3)通过观察IOCP线程的使用个数,就可以知道有多少个信息正在被处理。

(4)如果某个信息处理的耗时超过了心跳超时的设定,则对应的客户端会被当作心跳超时掉线,其TCP连接将被服务端主动断开。

四. TaskQueue方案

在TaskQueue方案中,IOCP线程不再重要,我们要关注的是任务队列和工作者线程。前面我们说道,工作者线程从队列中依次取出消息然后调用ICustomizeHandler接口进行处理。用于从事这一工作的工作者线程的数量是可以设定的,通过IRapidServerEngine的Advanced控制器的QueueWorkerThreadCount属性(默认是20), 像这样:

rapidServerEngine.Advanced.QueueWorkerThreadCount = 50;

当然,这个设定的数量必须不能超过ThreadPool的SetMaxThreads方法设定的最大工作者线程数。

另外,通过IRapidServerEngine的Advanced控制器提供的GetTaskQueueInfo方法,我们可以获取任务队列的当前状态:

///

///当CustomizeInfoHandleMode设置为TaskQueue时,获取队列中待处理的任务个数,以及历史中最大的待处理任务个数。///

/// 待处理的任务个数

/// 历史中最大的待处理任务个数

void GetTaskQueueInfo(out int taskCount, out int maxTaskCount);

那么,相比于IocpDirectly方案,TaskQueue方案有以下特点:

(1)针对一个具体的TCP连接而言,信息的处理也不是串行的,而且,后接收到的信息有可能先处理完。

(2)通过与空闲时(比如,程序启动时)线程池中可用的工作者线程数的对比,就可以知道有多少个信息正在被处理。

(3)如果某些信息处理缓慢,那么,通过GetTaskQueueInfo方法可看到队列中待处理的任务越来越多,同时可以观察到当前进程的内存消耗越来越大。从客户端表现来看,就是响应越来越慢。

五. 生产 - 消费模型

我们可以使用生产-消费模型来进行分析,从网络接收到消息,相当于生产消息,HandleInformation /

HandleQuery方法的执行相当于是消费消息。决定服务端吞吐量的因素有很多,但最首要也是最核心的因素就是服务端消费消息的能力。每秒消费掉的消息数量越大,吞吐量也就越大。

当生产消息的速度小于或等于消息消费的速度时,服务端的业务处理是从容不迫的。

但是,当生产消息的速度大于或远远大于消息消费的速度时,并且这一状况一直持续下去时,可以想想会发生什么状况?我们简单推导一下。

1. TaskQueue方案

(1)为任务队列服务的工作者线程都将处于忙碌状态。

(2)队列中待处理的任务越来越多。

(3)内存消耗越来越大,可能导致进程引发OutOfMemoryException(内存溢出)异常。

(4)从客户端来看,新的请求无法被服务端响应。

2. IocpDirectly方案

(1)线程池中可用的IOCP线程会越来越少。

(2)每个TCP连接的Socket缓冲区中排队等待接收的消息会越来越多,当这个缓冲区满时,客户端再向服务端发送消息的方法调用将被阻塞。

(3)如果某些消息处理的异常缓慢,则可能导致某些客户端心跳超时掉线。

当生产消息的速度大于或远远大于消息消费的速度时,提升服务端信息处理能力(这涉及到的诸多方面的优化)是唯一的解决方案。但是,在未提升服务器的处理能力之前,对于这种情况,IocpDirectly方案和TaskQueue方案哪种更好一点了?

虽然,无论哪种方案,客户端的体验都将比较差,但是相比而言,我们推荐是IocpDirectly方案,原因在于:基于上面描述IocpDirectly方案的第(2)点,客户端发送消息的方法调用将被阻塞,这意味着会倒逼客户端降低生产消息的速度。采用IocpDirectly方案是ESFramework框架的默认设置。

-----------------------------------------------------------------------------------------------------------------------------------------------

Q Q:168757008

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
如果在服务发送信息时出现 "The operation is not allowed on non-connected sockets" 错误,这通常意味着您在发送之前没有确保与建立了连接。 在您的代码中,确保在发送信息之前,服务成功建立了连接。可以使用`BluetoothListener.AcceptBluetoothClient()` 方法来接受的连接请求,并返回一个`BluetoothClient` 对象。然后,使用该对象的 `GetStream()` 方法获取蓝牙连接的流,并使用流发送信息。 以下是一个示例代码,展示了在服务发送信息的过程: ```csharp using InTheHand.Net; using InTheHand.Net.Bluetooth; using InTheHand.Net.Sockets; public class BluetoothExample { private BluetoothListener _listener; private BluetoothClient _client; private Guid _serviceGuid = BluetoothService.SerialPort; public void StartListening() { _listener = new BluetoothListener(_serviceGuid); _listener.Start(); // 开始异步接受新的连接请求 _listener.BeginAcceptBluetoothClient(BluetoothCallback, null); } private void BluetoothCallback(IAsyncResult result) { _client = _listener.EndAcceptBluetoothClient(result); _listener.Stop(); // 停止监听 // 处理接收/发送信息 ListenForMessages(); } private void ListenForMessages() { while (_client.Connected) { using (var stream = _client.GetStream()) { byte[] buffer = new byte[1024]; int bytesRead = stream.Read(buffer, 0, buffer.Length); string message = System.Text.Encoding.ASCII.GetString(buffer, 0, bytesRead); // 处理接收到的信息 Console.WriteLine("接收到的信息:" + message); // 发送回复信息 string replyMessage = "回复信息"; byte[] replyBuffer = System.Text.Encoding.ASCII.GetBytes(replyMessage); stream.Write(replyBuffer, 0, replyBuffer.Length); } } } public void Disconnect() { _client.Close(); } public void SendMessage(string message) { if (_client != null && _client.Connected) { using (var stream = _client.GetStream()) { byte[] buffer = System.Text.Encoding.ASCII.GetBytes(message); stream.Write(buffer, 0, buffer.Length); } } else { Console.WriteLine("未连接到"); } } } ``` 在上述代码中,`SendMessage` 方法会首先检查 `_client` 对象是否存在且已连接。如果连接正常,就可以使用 `_client.GetStream()` 方法来获取蓝牙连接的流,并发送信息。否则,会输出 "未连接到" 的提示。 请确保在发送信息之前,服务已经成功建立了连接。否则,会出现 "The operation is not allowed on non-connected sockets" 错误。 希望能对您有所帮助!如果您还有其他问题,请随时提问。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值