个人开发历程知识库

关注C++/Java/C#技术, 致力于安防监控/移动应用/WEB方面开发
------------------------------------ 业精于勤,荒于嬉;行成于思,毁于随
  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

[转载]可扩展多线程异步Socket服务器框架EMTASS 2.0

Posted on 2009-05-29 10:35  peterzb  阅读(7044)  评论(7编辑  收藏  举报
(原创文章,转载请注明来源:http://blog.csdn.net/hulihui/archive/2008/10/27/3158613.aspx)

 

0 前言

>>[前言][第1节][第2节][第3节][第4节][第5节][第6节]

 

在程序设计与实际应用中,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. 总体思路与架构
  2. 关键实现技术
  3. 框架使用简介
  4. 一般测试结果
  5. 总结与展望
  6. 版本与源码

1 总体思路与架构

>>[前言][第1节][第2节][第3节][第4节][第5节][第6节]

1.1 总体思路

 

总体构思上,主要考虑多线程、异步Socket和可扩展性三个方面。

 

1) 三个核心线程

 

在Internet环境下的Socket应用中,客户端和网络容易出现异常,此时必须释放异常退出的Socket资源。考虑到服务器的高并发能力,一般采取包接收和处理分开的策略:将接收到的包添加到包队列,然后处理队列中的数据包。当然,侦听远程客户端的连接请求可以用Socket的AcceptAsync()异步方法(IOCP,I/O完成端口由此开始)。考虑到暂停、关闭同步操作,仍然用一个线程。这样,清理资源、处理数据包、侦停客户连接请求就是组成了EMTASS架构的三个核心线程,它们由.NET线程池统一管理:
  1. 客户端连接侦听线程 StartServerListen():循环侦听远程客户端的Socket连接请求。如果存在,通过适当规范性判断后创建该Socket的客户端会话TSessionBase对象(实际上是该类的派生类对象),同时调用该会话对象的Socket异步数据接收方法BeginReceive(),接收到的数据包存放在会话对象的包队列中。当然,新增的TSessionBase对象将添加到会话队列m_sessionTable(一个Dictionary<>泛型对象)中,该队列表就是清理和处理线程的遍历对象;
  2. 数据包处理线程 CheckDatagramQueue():循环检测TSessonBase队列中的会话对象,调用该对象的相关方法完成数据包解析、判断类型、数据存储等任务;
  3. 会话表检测线程 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、数据接收缓冲区、数据包缓冲区、数据包队列等数据结构,包括了与客户端通信相关的全部方法:数据接收与发送、数据包处理等。
  • 数据库类
    • 抽象数据库基类 TDatabaseBase:封装了数据库打开与关闭、异常事件处理等方法,其中数据连接属性DbConnection是虚属性、数据库打开方法Open()是抽象方法,需要在派生类中重写;
    • 基类TSqlServerBase:派生自TDatabaseBase,应用System.Data.SqlClient名称空间中SqlServer相关类型重定义了数据库连接属性DbConnection、重写了Open()方法;
    • 基类TOleDbDatabaseBase:派生自TDatabaseBase,应用System.Data.OleDb名称空间中OleDb数据访问相关类型重定义了基类的连接属性DbConnection、重写了Open()方法
  • 枚举类型
    • 会话状态类型 TSessioinState:取4个值:Valid、Invalid、Shutdown和Closed。为Valid时表示会话是有效的,为Invalid时表示会话将被清理,为Shutdown时表示会话Socket正在卸载,为Closed时表示会话已经关闭、资源已经清理;
    • 会话断开类型 TDisconnectType:取3个值:Normal、Timeout和Exception,分别表示正常连接、超时断开、异常断开。其中,超时表示最近两次会话接收数据的时间超过约定的时限,防止某些会话长时间占有资源。

2)事件参数类型结构图


(图2 事件参数类层次关系)

 

EMTASS框架的事件包括三类:第一,普通事件,如:服务器启动与停止;第二,异常事件,接收与发送数据异常、数据库连接或数据存储异常等;第三,与会话相关事件,如:增加会话对象、接收到一个合法数据包等。异常与会话结合即是会话异常事件。通过泛型委托EventHandler可以定义类事件,其中的事件参数类型如下:
  • 异常事件参数类 TExceptionEventArgs:封装了异常Exception对象的Message值;
  • 会话事件参数类 TSessionEventArgs:封装了一个TSessionCoreInfo对象;
  • 会话异常参数类 TSessionExceptionEventArgs:派生自TSessionEventArgs,包括异常消息字段Message。

2 关键实现技术

>>[前言][第1节][第2节][第3节][第4节][第5节][第6节]

 

下面介绍主要类TSocketServerBase和辅助类TSessionBase、TDatabaseBase中的主要实现方法。

2.1 TSocketServerBase类

 

该类包括了全部的对外接口和事件,主要实现前面介绍过的三个线程。

 

1) 线程池与同步信号

 

.NET 提供了线程池方法 ThreadPool.QueueUserWorkItem() 自动将委托对象添加到系统线程池,见如下的实现代码:
    if (!ThreadPool.QueueUserWorkItem(this.StartServerListen)) return false;
if (!ThreadPool.QueueUserWorkItem(this.CheckDatagramQueue)) return false;
if (!ThreadPool.QueueUserWorkItem(this.CheckSessionTable)) return false;
其中, 客户端连接请求侦听方法StartServerListen()、数据包队列检查方法CheckDatagramQueue()和会话表检查方法CheckSessionTable()均使用循环处理方式,循环条件是m_serverClosed为false。只有该类的Close()方法可以中断这三个线程。在Close()方法中设置m_serverClosed为true终止线程的同时,还需要考虑线程退出的同步问题,此时使用手工事件信号对象ManualResetEvent。参考如下数据包队列检查线程方法的代码:
private void CheckDatagramQueue(object state)
{
m_checkDatagramQueueResetEvent.Reset();

while (!m_serverClosed)
{
lock (m_sessionDictionary)
{
// ...其它代码
}
}

m_checkDatagramQueueResetEvent.Set();
}

 

上述代码是不安全的,一般要需要try{}finally{}保证事件信号对象Reset()与Set()匹配。但EMTASS中的三个线程方法均有自己的异常处理方式,不会抛出异常。下面是关闭服务器方法Close()的主要代码。在设置变量了m_serverCloed为true后,使用了三个事件信号等待,同步三个线程的正常终止。
private void Close()
{
if (m_serverClosed)
{
return;
}

m_serverClosed = true;
m_serverListenPaused = true;

m_checkServerListenResetEvent.WaitOne(); // 等待3个线程
m_checkSessionTableResetEvent.WaitOne();
m_checkDatagramQueueResetEvent.WaitOne();

// ...其它代码
}

 

2) 分步骤的清理线程

 

建立会话对象后,三种情况需要终止会话:1)关闭服务器;2)会话异常;3)会话超时。第1种情况将强制终止会话,第2、3种情况需要清理线程终止会话并释放其资源。为防止立即关闭Socket引发的异常,系统分3个步骤完成:1)标记该会话为Invalid,此时停止一切与该会话的处理操作;2)调用Shutdown()方法:Shutdown会话Socket,标记会话状态为Shutdown;3)调用Close()方法:清除会话缓冲区和数据包队列,释放Socket资源,从会话表中删除该对象。具体操作可以参考TSocketServerBase类中的CheckSessionTable()方法。

 

3) 事件传递与发布

 

EMTASS框架的所有事件,包括TSessionBase类和TDatabaseBase类的事件,都通过服务器类TSocketServerBase对外发布。在创建会话对象或数据库对象时,直接传递其事件给TSocketServerBase的相同委托事件,见如下代码举例:
    session.DatagramAccepted += new EventHandler(this.OnDatagramAccepted);
session.DatagramHandled += new EventHandler(this.OnDatagramHandled);
上述代码中,将TSessionBase派生类对象session的两个事件直接绑定(使用+=方法)到当前TSocketServerBase对象上。具体实现代码可以参考TSocketServerBase的初始化方法Initiate()和添加会话对象方法AddSession()。

2.2 TSessionBase类

 

该抽象类包括客户端Socket、数据接收缓冲区和数据包队列等成员,封装了所有与Socket通信的方法。该类还包括数据包处理方法:数据包解析保护方法ResolveSessionBuffer()和数据包分析虚拟方法AnalyzeDatagram()。

 

1) 会话缓冲区和数据包队列

 

TSessionBase包括两个数据接收缓冲区和一个数据包队列:
  • m_receiveBuffer缓冲区:接收客户端Socket数据的缓冲区,如果数据包比该缓冲区长,Socket将自动(异步)读取几次,每次用方法CopyToDatagramBuffer暂到数据包缓冲区m_datagramBuffer中;
  • m_datagramBuffer缓冲区:如果m_receiveBuffer接收了非完整的数据包,则使用该缓冲区暂存,直到获得一个完整数据包。一般情况下,设置m_receiveBuffer大于数据包长度,则一次可以接收一个完整包,此时该缓冲区为空。此外,该缓冲区大小根据数据包的长度动态增长;
  • m_datagramQueue包队列:字节数组的队列(Queue<>泛型),保存了当前会话的数据包(字节数组),等待处理线程分析与处理。在EMTASS 1.0与1.1中,该队列结构封装在TSocketServerBase内。这种设计增加了TSessionBase类与TSocketServerBase类的偶合程度。

 

2) 数据包解析方法ResolveSessionBuffer()

 

该方法是protected的,可以根据数据包结构与具体业务逻辑重写代码。在TSessionBase类中实现的包组成规则是:开始字符是<结束字符是>。特别指出,Socket通信中有两个必须考虑的著名问题:
  • 数据包界限问题:数据包字符串(字节数组)如何界限?在交通部的Socket通信协议中,用<>分别作为一个包的开始与结束字符;
  • 数据包间断与重叠问题:由于网络或设备故障,一个数据包可能分两次接收。另一种情况就是连续接收到多个数据包。第一种情况,需要先缓存接收到的包直到一个完整的数据包。第二种情况,则需要根据包界限符号分解一个个的包。

 

3) 数据包分析方法AnalyzeDatagram()

 

该抽象方法是TSessionBase类必须重写的方法,也是EMTASS框架扩展的主要接口,应该完成如下基本任务:
  • 判断数据包的有效性与包类型;
  • 分解包中的各字段数据;
  • 校验包及其数据有效性;
  • 发送确认消息给客户端(调用方法 SendDatagram());
  • 存储包数据到数据库中;
  • 如果数据包中存在客户端名称或编号,则填写m_name字段。

2.3 TDatabaseBase类

 

该抽象类定义了3个数据库异常处理事件:DatabaseOpenException、DatabaseCloseExeption和DatabaseException,以及4个public方法:Open()、Close()、Clear()和Store()。其中,Open()是抽象方法,在派生类中可以增加自己的代码(见demo的实现部分),Close()方法关闭数据库连接,Clear()方法在Close()中被调用——关闭数据库前清理相关资源,虚方法Store()用于数据存储。EMTASS框架给出了该基类的两个派生类:TSqlServerBase和TOleDatabaseBase,可以满足一般的数据库应用需求。

 

3 架构使用简介

>>[前言][第1节][第2节][第3节][第4节][第5节][第6节]

3.1 一般步骤

 

EMTASS框架的使用包括如下步骤:
  • 定制满足需求的TSqlServerBase或TOleDatabaseBase派生类
    • 增加成员字段,如:DbCommand、DbDataAdapter等;
    • 重写TDatabaseBase类的虚拟方法Store(),编写保存数据包到数据库中的实现代码;
    • 在TSessionBase的AnalyzeDatagram()方法中直接或间接调用Store()方法。
  • 定制满足业务处理逻辑的TSessionBase派生类
    • 重写ResolveSessionBuffer()方法,按照数据包规则提取缓冲区中的数据包文,并存储到包队列中;
    • 重写AnalyzeDatagram()方法,按前面的要求增加功能。
  • 定制满足需求的TSocketServerBase派生类
    • 定义TSocketSErverBase泛型类的派生类(该步可省略);
    • 创建泛型类的派生类对象,在构造函数中给出数据库连接字符串和TCP通信端口;
    • 设置泛型类的派生类对象的最大数据包长度等参数;
    • 实现泛型类的派生类对象的相关事件处理方法。

 

3.2 TSocketServerBase的构造、属性、方法和事件

 

泛型类TSocketServerBase提供了EMTASS框架的所有对外接口(属性、方法和事件),包括内联TSessionBase对象和TDatabaseBase对象的对外属性和事件。

 

1) TSocketServerBase构造函数

 

有两个重载版本,默认端口3130。考虑到可扩展性,必须给出数据库连接串,见如下代码:
public TSocketServerBase(string dbConnectionString)
{
this.Initiate(dbConnectionString);
}

public TSocketServerBase(int tcpPort, string dbConnectionString)
{
m_servertPort = tcpPort;
this.Initiate(dbConnectionString);
}
构造函数中的方法Initiate()完成具体的初始化任务。

 

2) TSocketServerBase公共属性

  • ServerPort:服务器端口号,默认值为3130
  • Closed:服务器已经关闭
  • ListenPaused:服务器暂时停止客户端连接请求
  • LoopWaitTime:Socket.Listen方法中的等待时间(ms),默认值为25ms
  • MaxDatagramSize:允许数据包的最大长度,默认值为1024K
  • MaxListenQueueLength:最大侦听队列长度,默认值为16
  • MaxReceiveBufferSize:允许数据包接收缓冲区的最大长度,默认值为16K
  • MaxSameIPCount:允许同地址IP的会话Socket个数,默认值为64
  • MaxSessionTableLength:允许最大会话表长度,默认值为1024
  • MaxSessionTimeout:允许最大的会话超时间隔(s),默认值为120s
  • ErrorDatagramCount:错误数据包个数
  • ReceivedDatagramCount:接收数据包个数
  • ServerExceptionCount:服务器异常次数
  • SessionCount:当前会话个数
  • SessionExeptionCount:会话异常个数
  • SessionCoreInfoList:当前会话表信息清单

3) TSocketServerBase公共方法

  • Start():启动服务器
  • Stop():关闭服务器
  • PauseListen():暂停侦听连接请求
  • ResumeListen():恢复侦听连接请求
  • Dispose():关闭服务器并释放系统资源
  • CloseSession():关闭一个会话
  • CloseAllSessions():关闭全部会话
  • SendToSession():给一个会话发送消息
  • SendToAllSessions():给所有会话发送消息

4) TSocketServerBase事件

  • DatabaseCloseException:数据库关闭异常
  • DatabaseException:数据库异常
  • DatabaseOpenException:数据库打开异常
  • DatagramAccepted:接受了一个完整数据包
  • DatagramDelimiterError:数据包界限符错误
  • DatagramError:数据包错误
  • DatagramHandled:处理了一个数据包
  • DatagramOversizeError:数据包超长错误
  • ServerStarted:服务器启动后
  • ServerClosed:服务器关闭后
  • ServerListenPaused:服务器暂停连接请求后
  • ServerListenResumed:服务器恢复连接请求后
  • ServerException:服务器异常
  • SessionRejected:连接请求被拒绝
  • SessionConnected:建立一个会话连接
  • SessionDisConnected:断开一个会话连接
  • SessionReceiveException:会话接收数据异常
  • SessionSendException:会话发送数据异常
  • SessionTimeout:会话超时

 

3.3 下载包Demo介绍

 

下载包中包括EMTASS框源代码和Demo。其中,VS2005的Demo解决方案文件为EMTASS.sln,包含两个项目:服务器项目和客户端项目。\bin\文件夹下的编译文件可直接运行:先启动服务器,然后运行客户端。

 

1) 服务器端Demo

 

服务器端包括两个部分:第一,接收服务器窗体程序;第二,Access数据库。服务器端窗体程序包含如下实现:
  • TSessionBase派生类TTestSession:重写了OnDatagramDelimiterError()和OnDatagramOversizeError()事件处理方法,重写了数据包分析方法AnalyzeDatagram(),增加了一个自定义方法Store();
  • TDatabaseBase派生类TAccessDatabase:增加了一个OleDbCommmand字段m_command,重写了Open()方法和Store()方法。在重写的Open()方法中,创建了m_command对象及其参数对象,给出了数据库的Insert语句SQL代码。重写的Store()方法中,将TSessionBase的IP、SessionName和数据包长度保存到Access数据库中。方法Store()将被TTestSession()的AnalyzeDatagram()方法调用;
  • TSocketServerBase<>对象:给出数据库连接字符串后,应用前面定义的两个派生类创建泛型类对象,然后注册该对象的事件实现方法。注册的事件方法功能包括:显示服务器的如果计数情况,显示服务器运行状态。
服务器端的主要代码如下:
public partial class SocketServerDemo : Form
{
TSocketServerBase m_socketServer;

public SocketServerDemo()
{
InitializeComponent();
}

private void SocketServerDemo_Load(object sender, EventArgs e)
{
cb_maxDatagramSize.SelectedIndex = 1;

// 数据库连接字符串
string connStr = "Provider=Microsoft.Jet.OLEDB.4.0; Data Source = DemoAccessDatabase.mdb;";

m_socketServer = new TSocketServerBase(connStr); // 服务器对象
m_socketServer.MaxDatagramSize = 1024 * int.Parse(cb_maxDatagramSize.Text); // 包最大长度

this.AttachServerEvent(); // 附加服务器全部事件
}

private void SocketServerDemo_FormClosing(object sender, FormClosingEventArgs e)
{
m_socketServer.Dispose(); // 关闭服务器进程
}

private void AttachServerEvent()
{
m_socketServer.ServerStarted += this.SocketServer_Started;
m_socketServer.ServerClosed += this.SocketServer_Stoped;
m_socketServer.ServerListenPaused += this.SocketServer_Paused;
m_socketServer.ServerListenResumed += this.SocketServer_Resumed;
m_socketServer.ServerException += this.SocketServer_Exception;

m_socketServer.SessionRejected += this.SocketServer_SessionRejected;
m_socketServer.SessionConnected += this.SocketServer_SessionConnected;
m_socketServer.SessionDisconnected += this.SocketServer_SessionDisconnected;
m_socketServer.SessionReceiveException += this.SocketServer_SessionReceiveException;
m_socketServer.SessionSendException += this.SocketServer_SessionSendException;

m_socketServer.DatagramDelimiterError += this.SocketServer_DatagramDelimiterError;
m_socketServer.DatagramOversizeError += this.SocketServer_DatagramOversizeError;
m_socketServer.DatagramAccepted += this.SocketServer_DatagramReceived;
m_socketServer.DatagramError += this.SocketServer_DatagramrError;
m_socketServer.DatagramHandled += this.SocketServer_DatagramHandled;

m_socketServer.DatabaseOpenException += this.SocketServer_DatabaseOpenException;
m_socketServer.DatabaseCloseExcpetion += this.SocketServer_DatabaseCloseException;
m_socketServer.DatabaseExcpetion += this.SocketServer_DatabaseException;

m_socketServer.ShowDebugMessage += this.SocketServer_ShowDebugMessage;
}
//...其它代码
}

 

下面是服务器端Demo运行图片

 

 

2) 客户端Demo

 

创建一个TcpClient对象,模拟远程客户端与服务器通信。下面是客户端Demo运行图片:

 

 

 

4 一般测试结果

>>[前言][第1节][第2节][第3节][第4节][第5节][第6节]
  • 机器配置:双核CPU E2140,主频1.6G,RAM是1G(含显卡内存)。
  • 正确性测试
    • 测试方法:客户端的数据包含有长度串,在服务器端检测比较,以此判断数据包传输的正确性;
    • 测试情况:单机启动服务器,运行7个客户端程序,其中3个是干扰源——连续做连接/断开操作,另4个连续发送数据包。运行约80分钟检查,服务器平均每秒接收15个包,没有发生异常,也没有错误包,数据包队列稳定在3以下。
  • 速度测试
    • 测试方法:单机运行服务器,然后运行20个客户端程序,用10-50ms的速度发送数据包;
    • 测试情况:运行30分钟检查,服务器平均每秒接收60个包,没有出现数据包队列显著增长等情况。
  • 稳定性测试:客户端Demo中包含一个连接后马上断开的连续操作,在这种情况下服务器端没有发现异常。此外,笔者在30个客户端会话情况下运行服务器1个小时,没有发生服务器端异常,但有个别客户端异常退出。
  • 并发性测试:同时运行了30个客户端Demo,没有发生服务器异常等现象,数据包队列上限也不超过5。
  • 测试结论: 在一台机器上做测试, EMTASS 2.0接收与处理正确,每秒可以处理60以上的数据包,运行稳定可靠,有较好的并发处理能力。测试中发现的主要问题如下:
    • CPU占用率很大:显然是三个线程的循环操作所引起的,在EMTASS1.0、1.1中,使用Thread.Sleep(m_waitTime)等待一段时间。本框架的设计目的是专用Socket服务器,为提高吞吐能力省略了这个操作。如果必要,在以后版本中添加该功能;
    • 服务器启动后拒绝连接请求:特别是关闭后再启动容易发生这种现象,多关闭启动几次后却又恢复正常。笔者估计是服务器m_serverSocket对象释放资源不同步的原因;
    • 客户端连接请求被拒绝:有时客户端发生错误后,再连接时被拒绝。有两种可能,第一种是如前所讲的服务器对象的问题;第二种是客户端Socket接收、发送和断开时被阻塞,具体原因待分析。出现这种情况后,做多次连接/断开操作还是可以连接服务器。

 

显然,这种测试环境和结果有待进一步验证,但存在较大的改进空间。特别,数据包队列最大值一般不超过5,表明服务器接收到包后立即处理,并发性能比较好。

 

5 总结与展望

>>[前言][第1节][第2节][第3节][第4节][第5节][第6节]

 

本文介绍的EMTASS 2.0 是笔者一段工作的总结,也是学习基于.NET的类设计、组件设计、模式设计等的一个小结。笔者的目标就是不断修改和完善,设计与实现一个可靠与稳定的、有良好可扩展性的和易于使用的Socket数据包接收服务器框架。由于最初的代码和思路均来自他人的开源架构和设计构思,EMTASS也仿效一般开源做法:公布源码和设计思路。

 

基于EMTASS 1.0的服务器有连续运行30天完全正常的记录,而框架EMTASS 2.0 虽然具有可扩展性,也进行了一般的测试,但没有投入实际运行,需要时间检验和实践验证。当前,.NET 3.0及3.5 Framwwork提供的IOCP(完成端口)具有更好的异步并发处理能力,笔者将结合新的运行平台,完善与升级EMTASS,并公布完善与升级计划。如果有读者使用EMTASS 2.0时发现问题,或有更好的建议或想法,请不吝指正。

 

6 版本与源码

>>[前言][第1节][第2节][第3节][第4节][第5节][第6节]
  • EMTASS 1.0, 2005年12月
  • EMTASS 1.1, 2008年09月
  • EMTASS 2.0, 2008年10月27日
    • 重设计了类名及类关系,增加了框架的可扩展性,重构了大部分代码
    • TSessionBase的数据包队列取代TSocketServerBase的数据包队列
    • TSessionBase中封装了全部通信方法
  • EMTASS 2.1, 2008年11月9日
    • 增加了缓冲区管理类BufferManager,管理两个可重复使用的发送/接收缓冲区。
    • TSocketServerBase增加了如下属性:
      • ReceiveBufferSize:接收缓冲区大小(默认16K)
      • SendBufferSize:发送缓冲区大小(默认16K)
      • CheckDatagramQueueTimeInterval:数据包处理线程Sleep时间间隔(默认:100ms)
      • CheckSessionTableTimeInterval:会话资源清理线程Sleep时间间隔(默认:100ms)
    • TSocketServerBase增加了若干构造函数,包括最大任务数和缓冲区大小
    • TSocketServerBase使用了Mutex互斥类,防止同机器创建两个服务器
    • TSessionBase在异步接收/发送完成后,调用IAsyncResult.AsyncWaitHandle.Close()方法
    • TSessionBase接收数据时,使用BufferManger公共缓冲区ReceivevBuffer
    • TSessionBase发送数据时,如果据长度小于发送缓冲区,则使用SendBuffer,否则申请字节数组发送
    • 测试结果:10个干扰客户端(100ms连续连开)与15个数据客户端(3个100K/100ms、3个100K/50ms、4个100K/20ms、2个1M/1s、2个1M/500ms、1个1M/10s),CPU占用率为70-90%,速度为40/s,无错误包,直接显示的连接数为30-50之间