我们已经积累了这样的经验:如果有一个大块的数据需要通过Tcp发送,我们会采用异步的方式以避免当前工作线程阻塞。那么,如果我们有多个线程需要同时发送大块的数据了?每个线程都在NetworkStream或Socket上提交异步发送数据的请求会导致数据发送的混乱(多个线程同时在一个Socket上进行写操作),所以,我引入了前面介绍的线程安全的网络流。在引入这个类后,似乎日子已经很好过了,但是新的功能要求使得我需要寻找另外的解决方案。
考虑一下这个情景:我们的即时通信软件AgileIM在和好友进行视频聊天的同时,还要传递文字信息、音频数据、重要文件数据、控制命令等。而需要被传送的这些信息是有优先级顺序的,比如,控制命令的优先级最高,文件数据的优先级较低,而视频数据/音频数据在网络特别繁忙的时候是可以丢弃的。为了管理这些优先级,仅仅依靠ISafeNetworkStream已经不能满足我们的要求,我们需要另外一个组件来为我们解决这个问题,ESFramework提供的ITcpAutoSender组件正是为此目的的。
我们知道,在多线程的环境中对像Socket这样临界资源的访问必须保证线程安全,从另外一个角度来看,可以这么说,对某个临界资源的访问必须是同步的或者说必须是“仿单线程”的。 ITcpAutoSender就利用了这一“仿单线程”特性使的问题简单化。我们来看看这个组件究竟是如何工作的?
我们的应用程序中的各个线程当有数据需要发送时,就将数据提交ITcpAutoSender,提交时必须指定该数据的优先级。ITcpAutoSender组件会根据指定的优先级将该数据放入到对应的队列中,请注意,将要发送的数据提交给ITcpAutoSender必须是线程安全的,这点已经由ITcpAutoSender组件保证,使用者不用关心。
ITcpAutoSender内部有一个循环线程,每次从高优先级的队列中选取一个数据包进行发送,当高优先级队列为空时,再去发送次高优先级队列中的数据。每当一个数据包发送完毕,就再从高优先级的队列开始检查,如此反复。
从上面的描述已经可以看出,ITcpAutoSender组件仅仅是在一个线程中发送数据,所以ITcpAutoSender组件不需要再借助ISafeNetworkStream组件,而是直接使用NetworkStream就可以了。那么是不是ESFramework就不需要提供ISafeNetworkStream组件了?不是。在ESFramework框架中,ISafeNetworkStream组件主要用于服务端,因为通常情况下,服务端主动发送数据给客户端的几率比较小,而且服务端要管理成千上万的连接,所以使用ISafeNetworkStream组件是非常合适的。而ITcpAutoSender组件最常用于客户端,为客户端应用提供发送的数据的优先级机制。如果对你的应用中的客户端来说,所有的数据的优先级是一样的,那么就没有必要使用ITcpAutoSender组件了,直接使用ISafeNetworkStream就可以了。
在ESFramework框架中,将数据的优先级分为4等,如下枚举定义所示:
2 {
3 High ,//紧急命令
4 Common ,//如普通消息,如聊天消息
5 Low ,//如文件传输
6 CanBeDiscarded //如视频数据、音频数据
7 }
对于每个优先级,ITcpAutoSender组件的实现中都有一个对应的队列,每个队列都设置了初始大小。对于前三个优先级队列,当它们Full的时候,应用中的线程再提交数据就必须阻塞等待。而对于最低优先级CanBeDiscarded的队列,如果Full的时候有数据提交过来,则会删除队列头部的待发送数据。下面给出ITcpAutoSender组件的接口定义:
2 {
3 void Initialize() ;
4 void SendData(byte[] data, DataPriority dataPriority);
5 void ClearQueue(DataPriority queueType) ;
6 event CbDataDiscarded DataDiscarded;
7 event CbDataLacked DataLacked;
8 event CbSimple ConnectionInterrupted ;
9
10 int QueueSizeOfDiscarded { get; set;}
11 int QueueSizeOfNonDiscarded { get; set;}
12 NetworkStream NetworkStream { set;}
13 }
14
15 public delegate void CbDataDiscarded(byte[] data) ;
16 public delegate void CbDataLacked() ;
注意,这个接口中发布DataDiscarded事件和DataLacked事件,当网络繁忙有数据被抛弃时,DataDiscarded事件被触发;当所有的待发送队列都为空时,DataLacked事件被触发。我们的应用可以预定这两个事件来作些适当的调度。比如在AgileIM中,当DataDiscarded事件发生时,就适当减少视频捕获的帧率;当DataLacked事件发生时,就适当增大视频捕获的帧率,以达到更好的视频会话效果。
最后,说一下,很多朋友想得到ESFramework的源代码,这要等到ESFramework开源以后,相信这不是很久远的事情了。这里,我把ESFramework.dll提供给大家下载适用,当然你也可以用反射工具看看其中的构造。如果你想在你的项目中使用ESFramework,我将非常乐意回答你遇到的各种问题,你可以通过AgileSoft@163.com联系我。
谢谢关注!
上一篇文章:ESFramework介绍之(15)-- IRAS
转到 :ESFramework 可复用的通信框架(序)