(原创文章,转载请注明来源:http://blog.csdn.net/hulihui)
在程序设计与实际应用中,Socket数据包接收服务器够得上一个经典问题了:需要计算机与网络编程知识(主要是Socket),与业务处理逻辑密切(如:包组成规则),同时还要兼顾系统运行的稳定、效率、安全与管理等。具体应用时,在满足业务处理逻辑要求的基础上,存在侧重点:有些需要考虑并发与效率,有些需要强调稳定与可靠等等。虽然.NET 2.0 Framework上的IOCP(I/O完成端口)异步技术可以有效解决并发等问题,但完全的异步模式也缺乏一些控制上的灵活性,例如:Socket暂停操作等。
本文介绍的是一个传统Socket数据包服务器解决方案,该方案改自笔者2005年底的一个交通部省级公路交通流量数据服务器中心(DSC)项目。当时.NET Framework 2.0 与 Visual Studio 2005 发布没多久,笔者接触C#的时间不长。于是Google了国内国外网,希望找点应用C#解决Socket通信问题的思路和代码。最后,找到了两篇帮助最大的文章:一篇是国人2005年3月写的Socket接收器框架——
在C#中使用异步Socket编程实现TCP网络服务的C/S的通讯构架(一)(分(一)、(二)两篇),该文应用了客户端Socket会话(Session)概念;另一篇是美国人写的,提出了多线程、分段接收数据包的技术方案,描述了多线程、异步Socket的许多实现细节,该文坚定了笔者采用多线程和异步方式处理Socket接收器的技术路线。第一个版本EMTASS 1.0(EMTASS,Extensible Multi-Thread Asynchronous Socket Server)于2006年初完成并投入使用。
今年暑假,笔者修改了原Socket接收服务器代码,即EMTASS 1.1。最近,又按框架的可扩展性、可重用性等要求重新构思和设计了EMTASS,即EMTASS 2.0。下面的介绍共分六个部分:
- 总体思路与架构
- 关键实现技术
- 框架使用简介
- 一般测试结果
- 总结与展望
- 版本与源码
1.1 总体思路
总体构思上,主要考虑多线程、异步Socket和可扩展性三个方面。
1) 三个核心线程
在Internet环境下的Socket应用中,客户端和网络容易出现异常,此时必须释放异常退出的Socket资源。考虑到服务器的高并发能力,一般采取包接收和处理分开的策略:将接收到的包添加到包队列,然后处理队列中的数据包。当然,侦听远程客户端的连接请求可以用Socket的AcceptAsync()异步方法(IOCP,I/O完成端口由此开始)。考虑到暂停、关闭同步操作,仍然用一个线程。这样,清理资源、处理数据包、侦停客户连接请求就是组成了EMTASS架构的三个核心线程,它们由.NET线程池统一管理:
- 客户端连接侦听线程 StartServerListen():循环侦听远程客户端的Socket连接请求。如果存在,通过适当规范性判断后创建该Socket的客户端会话TSessionBase对象(实际上是该类的派生类对象),同时调用该会话对象的Socket异步数据接收方法BeginReceive(),接收到的数据包存放在会话对象的包队列中。当然,新增的TSessionBase对象将添加到会话队列m_sessionTable(一个Dictionary<>泛型对象)中,该队列表就是清理和处理线程的遍历对象;
- 数据包处理线程 CheckDatagramQueue():循环检测TSessonBase队列中的会话对象,调用该对象的相关方法完成数据包解析、判断类型、数据存储等任务;
- 会话表检测线程 CheckSessionTable():循环检查会话表m_sessionTable中的各个会话对象,分步骤清理已经超时、无效或异常的会话对象,清理会话对象的缓冲区,释放其Socket资源。
2) 异步处理模式
.NET Framework中的Socket具有完整的异步处理能力:侦听后异步接收(AcceptAsync())、数据异步接收(BeginReceive())、数据异步发送(BeginSend())等。EMTASS框架采取了异步接收和发送方式,并封装在TSessionBase类中。在EMTASS的版本1.0、1.1中,这些方法在主类TSocketServerBase中实现,显然不符合类封装原则。
3) 系统可扩展性
可扩展性主要考虑不同的业务处理逻辑和应用场景,即:数据包格式、数据存储方法、数据库服务器等。框架EMTASS的可扩展性体现在类的泛型与抽象设计、方法虚拟和保护等方面:
- 抽象类:会话基类TSessionBase、数据库基类TDatabaseBase均是抽象类,分别提供了数据包分析与判断、数据存储的虚拟方法;
- 泛型类:主要基类TSocketServerBase有TSessionBase、TDatabaseBase两个泛型约束参数,可以根据这两个抽象类的派生类产生具体的服务器类型;
- 方法抽象(abstract):TSessionBase的数据包分析方法AnalyzeDatagram()、TDatabaseBase的数据库打开方法Open()均是抽象的,必须在派生类中根据业务处理逻辑和数据库类型重写;
- 方法保护(protected):与事件处理有关的方法、与业务处理逻辑相关的方法全部是protected方法,可以根据实际情况重写。
1.2 类架构
1)主要类层次结构
(图1 主要类层次关系)
按应用类别分,EMTASS主要有四组类:Socket服务器类、Session会话类、Database数据库类和枚举类型。
- Socket服务器类
- 服务器泛型类 TSocketServerBase:该类包括了一个服务器Socket对象、一个TDatabaseBase派生类对象、一个会话TSessionBase类派生对象的列表(Dictionary<>泛型对象),封装了TDatabaseBase、TSessionBase的全部事件,提供统一的对外公开接口和事件;
- 泛型参数:服务器基类TSocketServerBase有两个泛型参数:TSessionBase类和TDatabaseBase类,定制它们的派生类后可以确定泛型类的具体版本。
- 客户端会话类
- 会话核心成员类 TSessionCoreInfo:是TSessionBase的基类,包括会话的核心字段:登录时间、最近会话时间、IP地址、客户端名、对象状态等,是会话列表清单和事件参数的基类之一。该类的成员字段全部是protected的,需要在派生类中赋予具体值;
- 抽象会话类 TSessionBase:封装了客户端Socket、数据接收缓冲区、数据包缓冲区、数据包队列等数据结构,包括了与客户端通信相关的全部方法:数据接收与发送、数据包处理等。