ESFramework的初体验

  第一次接触ESFramework,心中难免会有一丝喜悦与激动,想第一时间和大家分享这一个星期以来我学习ESFramework的体会...当然还有很多疑问有待在后面的学习中去解决。

  暂不说技术,在这里谈谈我自己初步学习ESFramework的一些体会,当然也会对一个基于ESFramework的即时通信的Demo进行简单的阐述,希望对于大家学习ESFramework会有少许帮助!

  前几天从官网上下载了ESFramework的学习文档与Demo。下载地址:http://www.oraycn.com/ESFramework_detail.aspx,在这里我们可以下载到ESFramework的开发手册和一些使用技巧,还有Demo。  

  

  在这里给初学者一个小的建议,不要先去看Demo,因为这样你会不知所云。我开始也是看了开发手册的第一篇概述,然后就迫不及待的去研究代码,最后发现越看越糊涂,根本就不知道讲的什么,因为里面都是一些陌生的类、接口等,也不知道这些类具体能为我们做什么,如:RapidPassiveEngine、ICustomizeOutters、IManagedForm,哈哈,模糊了吧,因为你根本就不认识。我的建议是,先通读开发手册的前十篇,大体了解ESFramework是什么,核心组件是哪些,基本体系是怎样的,一些方法内部是如何实现了。如果你大体了解了这些你就可以开始去学习一些Demo了。当然还有一个前提是你必须要对委托、事件、接口都能理解,因为Demo中大量使用了这些。

  接触任何新事物,都会有一个循序渐进的过程,从了解到认知,从陌生到熟悉。对于一门全新的技术也是如此,你必须先得了解它是什么(what),然后再问为什么(why),最后才是怎么用(do)。当然对于初学者的我们,我个人觉得我们可以先去了解它是什么,然后学着别人怎么用,最后在不断学习的过程中去理解它是为什么,内部是如何实现的。来吧,开始我们的学习!

  

  一:ESFramework是什么?

  首先我们必须明白,ESFramework 是一套性能卓越、稳定可靠、可高度伸缩、灵活易用而又功能强大的.NET通信框架。既然是一个通讯框架,那么它的应用当然也会非常的广泛了,即时通讯系统(IM),大型多人在线游戏(MMORPG)、在线网页游戏(WebGame)、文件传送系统(FTS)、数据采集系统(DGS)、分布式OA系统、远程教育系统等等,凡是需要分布式通信的系统都可以使用ESFramework框架。当然ESFramework的问世,当然会有它独特之处,也就说它相对其它的通信框架有它的优点:高性能,可靠、稳定,可靠的P2P,高伸缩性的群集平台,跨平台,可扩展性强。(开发手册有详细的介绍)。

      ESFramework体系直接构建在.NET Framework 2.0上,它由通信核心ESFramework、应用增强ESPlus、以及群集平台ESPlatform构成。 

  为了快速而高效地构建应用程序,我们可以基于ESPlus进行开发。ESPlus内置众多组件供我们直接使用,像消息头、解析器、消息处理器、序列化器、自定义信息、文件传送、P2P通道、好友/组友状态改变通知、等等。

  欲掌握ESPlus快速开发,需要抓住三个方面:Rapid引擎、四大武器、两翼。

  1.Rapid引擎

  在了解ESPlus的开发之前我们必须得了解Rapid引擎,引擎又分为服务端引擎(RapdiServerEngine)和客户端引擎RapidPassiveEngine。这里的引擎我们大可理解为我们日常生活中的发动机引擎,不管是摩托车、小汽车还是大货车,都需要这个发动机引擎。暂时不必关心它的内部实现,直接拿来用就好了,在使用之前必须对其初始化,初始化成功后,我们就可以使用引擎对象暴露出的四大武器了。正如发动机引擎启动后,我们就可以控制其加速、减速、刹车等;

     2.四大武器都都位于ESPlus.Application空间下:

  • ESPlus.Application.CustomizeInfo 命名空间:用于发送和处理自定义信息。
  • ESPlus.Application.Basic             命名空间:用于完成基础功能(如获取在线好友列表等)、和接收用户状态改变通知(如好友上下线等)。
  • ESPlus.Application.FileTransfering 命名空间:用于完成与文件传送相关的所有功能。
  • ESPlus.Application.P2PSession     命名空间:用于完成与P2P打洞、P2P通信相关的所有功能。 

  3.  两翼

   “两翼”指的就是IFriendManager与IGroupManager,后面服务器模块会有用到,主要是对好友和组的一个管理。

 

  二:怎么用?(Demo的学习)

   从官网上下载的Demo名为:“1.ESFramework.Demos.Simplest”,这个Demo主要是实现 客户端用户上下线时,通知其他在线用户;当客户端与服务端网络断开时,进行自动重连;当网络恢复后,重连成功;所有在线用户之间可以进行文字聊天;重登陆模式当同名的用户登陆时,会把前面的用户挤掉等。   

      1.先看看整个Demo的框架

   整个解决方法下有三个项目,一个客户端、一个服务端,还有个一个公共的类库Demos.Core,在客户端和服务端都可以引用它。

      2.先说说公共的类库,公共的类库中有2个自定义类,一个是InformationTypes类   

/// <summary>
/// 自定义信息的类型
/// </summary>
public static class InformationTypes
{
/// <summary>
/// 聊天信息
/// </summary>
public const int Chat = 0;

/// <summary>
/// 客户端同步调用服务端
/// </summary>
public const int ClientCallServer = 100;
}

       自定义信息类中定义了2个常量一个表示聊天消息,一个表示客户端同步调用服务器。再看TextChatContract类

 /// <summary>
/// TextChatContract 文字交谈协议。
/// </summary>
[Serializable]
public class TextChatContract : BaseSerializeContract
{
#region Ctor
public TextChatContract()
{
}
public TextChatContract(string _text)
{
this.text = _text;
}
public TextChatContract(string _text, bool _containsForeignObject, SortedArray<int, uint> _ary,Font _font, Color _foreColor)
{
this.text = _text;
this.containsForeignObject = _containsForeignObject;
this.localEmotionArray = _ary;
this.font = _font;
this.foreColor = _foreColor;
}
#endregion
...
//其它字段和属性的定义
}

   这就是定义的关于聊天消息的类,其中包括了文字消息,还有可以有截图,表情,字体及其它颜色。

      3.再看看Client项目下有几个窗体,一个登录窗体LoginForm,一个客户端管理主窗体MainForm,一个聊天窗体ChatForm,一个加载表情的窗体EmotionForm,还有一个用户控件TextChatControl。顾名思义,我们可以想象几个窗体分别的用途。

          1)Program.cs

    static class Program
{
[STAThread]
static void Main()
{
try
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);


RapidPassiveEngine rapidPassiveEngine = new RapidPassiveEngine();
MainForm mainForm = new MainForm();
LoginForm loginForm = new LoginForm(rapidPassiveEngine, mainForm); //在LoginForm中初始化客户端引擎RapidPassiveEngine
if (loginForm.ShowDialog() != DialogResult.OK)
{
return;
}

mainForm.Initialize(rapidPassiveEngine);

Application.Run(mainForm);
}
catch (Exception ee)
{
MessageBox.Show(ee.Message);
}
}
}

          2) 先实例化一个服务器端的引擎RapidPassiveEngine对象,然后在LoginForm中初始化客户端引擎

public LoginForm( RapidPassiveEngine engine ,MainForm _mainForm)
{
InitializeComponent();
this.rapidPassiveEngine = engine;
this.mainForm = _mainForm;
}

    3)登陆界面

View Code
 private void button_login_Click(object sender, EventArgs e)
{
string userID = this.textBox_id.Text.Trim();
//判断用户名的长度
if (userID.Length > 10)
{
MessageBox.Show("ID长度必须小于10.");
return;
}
//默认登录为成功状态
LogonResult logonResult = LogonResult.Succeed;
try
{
this.rapidPassiveEngine.SystemToken = "" ; //系统标志

//初始化引擎并登录,返回登录结果。如果登陆成功,引擎将与当前用户绑定。
logonResult = this.rapidPassiveEngine.Initialize(userID, this.textBox_pwd.Text, "127.0.0.1" ,4530, this.mainForm).LogonResult;
}
catch (Exception ee)
{
MessageBox.Show(string.Format("连接服务器失败。{0}" ,ee.Message));
return;
}

//登录失败的几种原因
if (logonResult == LogonResult.Failed)
{
MessageBox.Show("用户名或密码错误!");
return;
}

if (logonResult == LogonResult.HadLoggedOn)
{
MessageBox.Show("已经在其它地方登陆!");
return;
}
this.DialogResult = DialogResult.OK;
}

  Demo默认任何用户登录都成功,LogonResponse是在ESPlus.Application.Basic命名空间下的一个枚举类型。引擎在初始化方法中分别要传入几个参数(用户名,密码,服务器IP,服务器端口,自定义信息),如果登录成功,服务器引擎将与当前用户绑定。

   4)登录成功后运行,运行主窗体,主窗体就是一个管理聊天的中心,里面包括对消息的处理,还包括断线、重连、好友的上线与下线、重复登录、心跳超时、自己被踢或被挤下线等处理;这就需要用的我们的四大武器之一:ESPlus.Application.CustomizeInfo 命名空间:用于发送和处理自定义信息。

   因为我们这是客户端程序所有必须得引用using ESPlus.Application.CustomizeInfo.Passive;这有在这个命名空间下我们才能实现ICustomizeHandler这个接口,而这个接口中正是定义了(消息处理、断线等问题)处理问题的对应方法;

  所以在主窗体初始化时,我们就预定了这些事件:

View Code
public void Initialize(RapidPassiveEngine engine)
{
this.userID = engine.CurrentUserID;
this.rapidPassiveEngine = engine;

//预订断线事件
this.rapidPassiveEngine.TcpPassiveEngine.ConnectionInterrupted += new CbGeneric(tcpPassiveEngine_ConnectionInterrupted);
//预订重连失败事件
this.rapidPassiveEngine.TcpPassiveEngine.ConnectionRebuildFailure += new CbGeneric(tcpPassiveEngine_ConnectionRebuildFailure);
//预订重连开始事件
this.rapidPassiveEngine.TcpPassiveEngine.ConnectionRebuildStart += new CbGeneric(tcpPassiveEngine_ConnectionRebuildStart);
//预订重连成功事件
this.rapidPassiveEngine.TcpPassiveEngine.ConnectionRebuildSucceed += new CbGeneric(tcpPassiveEngine_ConnectionRebuildSucceed);
//预订好友上线的事件
this.rapidPassiveEngine.BasicOutter.FriendConnected += new CbGeneric<string>(BasicOutter_FriendConnected);
//预订好友下线的事件
this.rapidPassiveEngine.BasicOutter.FriendOffline += new CbGeneric<string>(BasicOutter_FriendOffline);
//预定重复登陆时的事件
this.rapidPassiveEngine.BasicOutter.HadLogon += new CbGeneric(BasicOutter_HadLogon);
//预订自己心跳超时的事件
this.rapidPassiveEngine.BasicOutter.TimeoutOffline += new CbGeneric(BasicOutter_TimeoutOffline);
//预订自己被踢出掉线的事件
this.rapidPassiveEngine.BasicOutter.BeingKickedOut += new CbGeneric(BasicOutter_BeingKickedOut);
//预订自己被挤掉线的事件
this.rapidPassiveEngine.BasicOutter.BeingPushedOut += new CbGeneric(BasicOutter_BeingPushedOut);

this.toolStripLabel_loginfo.Text = string.Format("当前登录:{0}", this.userID);
this.toolStripLabel_state.Text = "连接状态:正常";

this.InitializeFriends();
}

  在主窗体中还定义了处理来自其它用户的聊天消息的方法,这个方法是实现了ICustomizeHandler接口,也是我们提到过的四大武器中的一个用途,ICustomizeHandler

在ESPlus.Application.CustomizeInfo.Passive命名空间下。

View Code
/// <summary>
/// 处理来自其它用户的聊天消息
/// </summary>
public void HandleInformation(string sourceUserID, int informationType, byte[] info)
{
if (this.InvokeRequired)
{
this.Invoke(new CbGeneric<string,int, byte[]>(this.HandleInformation), sourceUserID,informationType, info);
}
else
{
if (informationType == InformationTypes.Chat)
{

ChatForm form = this.chatFormManager.GetForm(sourceUserID);
if (form == null)
{
form = new ChatForm(this.userID, sourceUserID, this.rapidPassiveEngine.CustomizeOutter);
this.chatFormManager.Add(form);
form.Show();
}

form.Focus();

TextChatContract contract = (TextChatContract)ESBasic.Helpers.SerializeHelper.DeserializeBytes(info, 0, info.Length);
form.ShowOtherTextChat(sourceUserID, contract);
}
}
}

  我们还可以看到在主窗体初始化方法中调用了this.InitializeFriends();//这就是加载所有的好友列表将其显示在主窗体界面中。方法如下:

View Code
        private void InitializeFriends()
{
List<string> list = this.rapidPassiveEngine.BasicOutter.GetAllOnlineUsers();
foreach (string friendID in list)
{
if (friendID != this.userID && !this.ListViewContains(friendID))
{
this.listView1.Items.Add(friendID, 0);
}
}
}

#region ListViewContains
private bool ListViewContains(string userID)
{
for (int i = 0; i < this.listView1.Items.Count; i++)
{
if (this.listView1.Items[i].Text == userID)
{
return true;
}
}
return false;
}

  在主窗体中还定义了一个全局的private FormManager<string, ChatForm> chatFormManager = new FormManager<string, ChatForm>();我们可以简单的将其理解为一个字典dit,主要是用来管理我们所有的聊天窗口ChatForm,这样我们的聊天窗口就必须得实现IManagedForm<string>这一泛型接口;

  ChatForm form = this.chatFormManager.GetForm(friendID);//通过ID获取聊天窗体


  5)当所有默认的好友都加载成功后,我们就可以双击主窗体中的好友头像,这时就会弹出一个聊天窗口ChatForm;

  整个界面分为消息的显示,表情面板,以及消息的发送框和”发送”,Demo将这些功能都定义在了用户控件TextChatControl中了;

  点击发送按钮,将用户信息发送给其它用户或者服务端:

View Code
        private void button_send_Click(object sender, EventArgs e)
{
bool containsForeignObject = false;
SortedArray<int, uint> ary = this.agileRichTextBox_send.GetAllImage(out containsForeignObject);

if (this.agileRichTextBox_send.Text.Trim() == "" && ary.Count == 0 && (!containsForeignObject))
{
return;
}
string text = "";

this.agileRichTextBox_history.AppendRichText(string.Format("{0} {1}\n ",this.userID, DateTime.Now) ,null,null,Color.DarkGreen);
if (containsForeignObject)
{
this.agileRichTextBox_history.AppendRtf(this.agileRichTextBox_send.Rtf);// AppendRichText(this.agileRichTextBox_send.Text, ary, this.agileRichTextBox_send.Font, this.agileRichTextBox_send.ForeColor);
text = this.agileRichTextBox_send.Rtf;

}
else
{
this.agileRichTextBox_history.AppendRichText(this.agileRichTextBox_send.Text, ary, this.agileRichTextBox_send.Font, this.agileRichTextBox_send.ForeColor);
text = this.agileRichTextBox_send.Text;
}

TextChatContract contract = new TextChatContract(text, containsForeignObject, ary, this.agileRichTextBox_send.Font, this.agileRichTextBox_send.ForeColor);
this.agileRichTextBox_history.AppendText("\n");
this.agileRichTextBox_history.ScrollToCaret();
this.agileRichTextBox_send.Clear();

//发送消息给好友
this.customizeInfoOutter.Send(this.destUserID, InformationTypes.Chat, ESBasic.Helpers.SerializeHelper.SerializeObject(contract));

}

   在发送消息之前必须准备好要发送的消息内容contract;(公共类库中定义文字交谈协议)

  然后调用ICustomizeInfoOutter类型的对象的Send()方法(对方ID,消息类型,序列化后的消息);

  发送消息的方式有多种(开发手册(一)有介绍),我们这里的Send()方法,是只向在线用户targetUserID发送消息;还有SendByP2PChannel(),是只通P2P通道向targetUserID发送消息。

  对话框中显示对方消息的方法:

View Code
        public void ShowOtherTextChat(string userID, TextChatContract contract)
{
if (this.InvokeRequired)
{
this.Invoke(new CbGeneric<string, TextChatContract>(this.ShowOtherTextChat), userID, contract);
}
else
{
if (userID != this.userID)
{

this.agileRichTextBox_history.AppendRichText(string.Format("{0} {1}\n ", userID, DateTime.Now), null, null, Color.DarkGreen);
if (contract.ContainsForeignObject)
{
this.agileRichTextBox_history.AppendRtf(contract.Text);// AppendRichText(this.agileRichTextBox_send.Text, ary, this.agileRichTextBox_send.Font, this.agileRichTextBox_send.ForeColor);

}
else
{
this.agileRichTextBox_history.AppendRichText(contract.Text, contract.LocalEmotionArray, contract.Font, contract.ForeColor);
}

this.agileRichTextBox_history.AppendText("\n");
this.agileRichTextBox_history.ScrollToCaret();
this.agileRichTextBox_send.Clear();
}
}
}

        

  4.服务端

  1)program.cs  (前面提到过“两翼”的使用)

View Code
        private static ESPlus.Rapid.RapidServerEngine RapidServerEngine = new ESPlus.Rapid.RapidServerEngine();

[STAThread]
static void Main()
{
try
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);

//如果是其它类型的授权用户,请使用下面的语句设定正确的授权用户ID和密码。
ESFramework.AuthorizationVerifier.SetAuthorizedUser(AuthorizationVerifier.FreeUser, "");

//使用简单的好友管理器,假设所有在线用户都是好友。(仅仅用于demo)
ESPlus.Core.Server.DefaultFriendManager friendManager = new ESPlus.Core.Server.DefaultFriendManager();

//初始化服务端引擎
RapidServerEngine.Initialize(4530, new CustomizeHandler(), new BasicHandler(), friendManager, null);
friendManager.UserManager = RapidServerEngine.UserManager; //RapidServerEngine初始化成功后,其UserManager属性才可用。

//设置重登陆模式
RapidServerEngine.UserManager.RelogonMode = RelogonMode.ReplaceOld;

//如果不需要默认的UI显示,可以替换下面这句为自己的Form
ESPlus.Widgets.MainServerForm mainForm = new ESPlus.Widgets.MainServerForm(RapidServerEngine);
Application.Run(mainForm);
}
catch (Exception ee)
{
MessageBox.Show(ee.Message);
}
}

  在Program.cs中实例化一个服务端引擎,可以设定正确的授权用户ID(Demo中是免费用户)和密码;可以设定好友组(默认所有登录到服务器的都是好友),还可以设定用户的登录模式,以及设定是否使用默认的服务器管理窗体:

  2)BasicBusinessHandler.cs和CustomizeHandler.cs分别定义对客户端用户名、密码的验证和对客户端消息的处理(实现ESPlus.Application.CustomizeInfo.Server.ICustomizeHandler接口)。

  ICustomizeHandler接口定义了2个方法:

  

      //处理来自客户端的自定义信息

void HandleInformation(string sourceUserID, int informationType, byte[] info);

  //处理来自客户端的请求并返回应答信息]

  byte[] HandleQuery(string sourceUserID, int informationType, byte[] info);

  

  这大致就是我学习这个Demo的一个思路与少许感触,或许你仍然不知道ESFramework是什么,不知道ESFramework在这个Demo中是如何体现的。但我们必须清楚这里面的核心是什么:两个引擎(RapdiServerEngine和RapidPassiveEngine)、四大武器、两翼,在这个Demo中都有体现。比如说我们发送和处理文字消息时都用到了四大武器之一:ESPlus.Application.CustomizeInfo。在这里ESFramework通信框架的优势就凸现出来了,因为发送消息不必像socket编程那样繁琐,我们只需要调用Send()方法就可以了,我们也不必深究其什么通信协议之类的东西,因为ESFramework的内部引擎全部替我们做好了这些事情。在我们发送消息的时候还可以选择不同的方式(如:发送同步消息,选择P2P通道发送等)去发送。还有发送文件我们有用到另一大武器:ESPlus.Application.FileTransfering ;还比如在服务器端我们也可以直接去调用一些接口(好友上下线,自己被挤下线,不同的登录方式,好友分组等)快速的去实现一些功能。

  就此搁笔,如果此时的您也和一样编程经验不足,也是初步了解ESFramework,那您就有必要和我一样自己重复的将Demo写上一遍或几遍,通过反复的读文档,看Demo,相信您会有不一样的收获!

      

posted @ 2011-12-29 10:41  fly_kw  阅读(3886)  评论(5编辑  收藏  举报