AgileEAS.NET SOA 中間件平臺.Net Socket通訊框架-完整應用例子-在線聊天室系統-代碼解析

1、AgileEAS.NET SOA中間件Socket/Tcp框架介紹

     在文章AgileEAS.NET SOA 中間件平臺.Net Socket通訊框架-介紹一文之中咱們對AgileEAS.NET SOA中間Socket/Tcp框架進行了整體的介紹,咱們知道git

AgileEAS.NET SOA中間件Socket/Tcp框架是一套Socket通訊的消息中間件:程序員

image_thumb2_thumb3_thumb_thumb

2、多人在線聊天室系統

      在文章AgileEAS.NET SOA 中間件平臺.Net Socket通訊框架-簡單例子-實現簡單的服務端客戶端消息應答給你們實例介紹了有關於AgileEAS.NET SOA 中間件Socket通訊框架的簡單應用以後,咱們經過文章AgileEAS.NET SOA 中間件平臺.Net Socket通訊框架-完整應用例子-在線聊天室系統-下載配置向你們展現了一個完整成熟的.NET Socket 通訊框架的應用案例,一個從在線聊天室系統,經過文章向你們講解了如何下載和編譯安案例源代碼、以及如何配置服務端和客戶段。github

      相對於簡單的客戶端==》服務端消息請求與應答的例子而言,在線多人聊天室系統的複雜度都要超過客戶端==》服務端消息請求例子N多倍,可是限於文章篇幅的緣由,咱們沒有在文章AgileEAS.NET SOA 中間件平臺.Net Socket通訊框架-完整應用例子-在線聊天室系統-下載配置這中爲你們介紹這個案例的具體代碼。sql

     下面我將爲你們介紹這個案例的關鍵代碼及閱讀、理解、修改完善所須要注意的地方。數據庫

3、關於代碼編譯環境及其餘的地些設置

     本案例的源代碼在下載壓縮包的Code目錄之中,全部的引用AgileEAS.NET SOA 中間件平臺的程序集及客戶端、服務端運行所必須的文件都在下載壓縮包的Publish目錄之中,全部項目的編譯輸出路徑也都是在Publish目錄,也就是全部項目不論是在Debug編譯環境仍是在Release編譯環境都是輸出在Publish目錄之中,有關具體的設置請看下圖:服務器

image_thumb3

4、解決方案之中的項目說明

     ChatRoom解決方案之是共有ChatRoom.Entities、ChatRoom.BLL.Contracts、ChatRoom.BLL.Host、ChatRoom.Messages、ChatRoom.Socket、ChatingRoom.MainClient、ChatingRoom.UserManage共七個項目,其中:網絡

    ChatRoom.Entities:是聊天室註冊用啓的數據存儲實體,其中只包括一個實體User,即註冊用戶信息。session

    ChatRoom.BLL.Contracts:爲用戶管理、登陸驗證、密碼找回修改等功能的分佈式服務定義契約,其中僅包括一個服務契約定義IUserService(用戶服務)。app

    ChatRoom.BLL.Host:爲ChatRoom.BLL.Contracts所定義的服務契約的功能實現。框架

    ChatRoom.Messages:服務端與客戶端通訊消息的定義,包括聊天消息、用戶登陸請求、登陸結果、在線用戶清單消息、用戶上下線狀態通知消息。

    ChatRoom.Socket:爲服務端的業務代碼、包括AgileEAS.NET SOA服務進程的SocketService插件以及服務端收到客戶端各類消息的消息處理器代碼。

    ChatingRoom.MainClient:爲客戶端代碼、包括客戶段界面以及客戶端收到通訊消息的消息處理器代碼。

5、關於SOA服務SocketService插件

    若是對比AgileEAS.NET SOA 中間件平臺.Net Socket通訊框架-簡單例子-實現簡單的服務端客戶端消息應答,細心的朋友必定會發現本案例中沒有了相似Socket.Demo.Server功能的項目,而是多了ChatRoom.Socket項目。

    關於這個問題就涉及到了AgileEAS.NET SOA 中間件平臺的SOA服務實例及Socket框架的設計,在SOA服務實例自己被設計成爲了一個能夠運行WCF、WS、Socket等各吃點通訊及其餘應用服務的運行容器,那麼咱們的Socket服務端也能夠在此服務實例之中運行,同時在咱們的AgileEAS.NET SOA中間件平臺的微內核程序集EAS.MicroKernel.dll之中定義了SocketService插件的實現標準:

   1: using System;
   2: using System.Collections.Generic;
   3: using System.Linq;
   4: using System.Text;
   5: using EAS.Distributed;
   6:  
   7: namespace EAS.Sockets
   8: {
   9:     /// <summary>
  10:     /// SocketService服務接口定義。
  11:     /// </summary>
  12:     /// <remarks>
  13:     /// 一個Socket服務器能夠承載多種/個Socket服務,一個Socket服務處理一種業務。
  14:     /// 如IM SocketService 處理IM相關的即時通信業務,而WF SocketService 處理工做流相關的服務,這兩種Socket服務能夠同時運行在一個Socket服務器之中。
  15:     /// </remarks>
  16:     public interface ISocketService:IAppService
  17:     {
  18:         /// <summary>
  19:         /// 使用ServerEngine初始化SocketService。
  20:         /// </summary>
  21:         /// <param name="socketServer">Socket服務器對象。</param>
  22:         void Initialize(ISocketServerBase socketServer);
  23:     }
  24: }

    ISocketService接口中定義了一個初始化方法:void Initialize(ISocketServerBase socketServer),用於SOA服務實例完成對ISocketService實例的初始化,其中傳入參數爲一個ISocketServerBase對象,其本質的含義爲SOA服務實例調用ISocketService實例對象把SOA服務實例之中的SocketServer對象作爲參數傳入,那麼咱們就能夠在ISocketService對象之中針對SocketServer作一些初始化工做,其中最重要的工做是,掛載與之相關的消息對象器IMessageHandler。

    ChatRoom.Socket項目之中包括了一個ISocketService的實現ChatRoom.Socket.MessageService

   1: using EAS.Loggers;
   2: using EAS.Sockets;
   3: using System;
   4: using System.Collections.Generic;
   5: using System.Linq;
   6: using System.Text;
   7:  
   8: namespace ChatRoom.Socket
   9: {
  10:     /// <summary>
  11:     /// 聊天室消息服務,由EAS.SOA.Server.Exe引擎的Socket初始化程序。
  12:     /// </summary>
  13:     public class MessageService : ISocketService
  14:     {
  15:         #region ISocketService 成員
  16:  
  17:         public void Initialize(EAS.Sockets.ISocketServerBase socketServer)
  18:         {
  19:             try
  20:             {
  21:                 socketServer.AddHander(new ChatMessageHandler());
  22:                 socketServer.AddHander(new LoginMessageHandler());
  23:                 ChatRoomContext.Instance.SocketServer = socketServer;
  24:             }
  25:             catch (System.Exception exc)
  26:             {
  27:                 Logger.Error(exc);
  28:             }
  29:  
  30:             socketServer.SessionStarted += socketServer_SessionStarted;
  31:             socketServer.SessionAbandoned += socketServer_SessionAbandoned;
  32:         }
  33:  
  34:         void socketServer_SessionStarted(object sender, NetSessionEventArgs e)
  35:         {
  36:             Logger.Info(string.Format("Session:{0}  Started", e.Session.SessionID));
  37:         }
  38:  
  39:         void socketServer_SessionAbandoned(object sender, NetSessionEventArgs e)
  40:         {
  41:             Logger.Info(string.Format("Session:{0}  Abandoned", e.Session.SessionID));
  42:         }
  43:  
  44:         //void socketServer_MessagerReceived(object sender, EAS.Sockets.MessageEventArgs e)
  45:         //{
  46:         //    Logger.Info(string.Format("MessagerReceived:{0}", e.Message.ToString()));
  47:         //}
  48:  
  49:  
  50:         //void socketServer_MessageSend(object sender, EAS.Sockets.MessageEventArgs e)
  51:         //{
  52:         //    Logger.Info(string.Format("MessageSend:{0}", e.Message.ToString()));
  53:         //}
  54:  
  55:         public void Start()
  56:         {
  57:  
  58:         }
  59:  
  60:         public void Stop()
  61:         {
  62:  
  63:         }
  64:  
  65:         #endregion
  66:     }
  67: }

    其中最重要的代碼是Initialize函數之中掛載ChatMessage、LoginMessage兩個消息的消息處理器代碼:

   1: socketServer.AddHander(new ChatMessageHandler());
   2: socketServer.AddHander(new LoginMessageHandler());

    Socket插件服務的定義除了代碼定義以外,還須要在AgileEAS.NET SOA 中間件有SOA服務實例配置文件之中進行定義,由於SOA服務實例程序有32位和64位版本,分別爲EAS.SOA.Server.exe和EAS.SOA.Server.x64.exe,因此要根據自身的機器條件和本身喜歡的運行環境修改EAS.SOA.Server.exe.config或EAS.SOA.Server.x64.exe.config:

   1: <?xml version="1.0"?>
   2: <configuration>
   3:   <configSections>
   4:     <section name="eas" type="EAS.ConfigHandler,EAS.MicroKernel"/>
   5:   </configSections>
   6:   <!--支持混合程序集-->
   7:   <startup useLegacyV2RuntimeActivationPolicy="true">
   8:     <supportedRuntime version="v4.0"/>
   9:   </startup>
  10:   <eas>
  11:     <configurations>
  12:       <item name="Key"  value="Value"/>
  13:     </configurations>
  14:     <appserver>
  15:       <channel>
  16:         <wcf enable="true">
  17:           <config tcpPort="6907" httpPort="6908"/>
  18:           <serviceThrottling maxConcurrentCalls="128" maxConcurrentInstances="128" maxConcurrentSessions="256"/>
  19:           <wcfServices>
  20:             <wcfService key="Key" type="Value"/>
  21:           </wcfServices>
  22:         </wcf>
  23:         <socket enable ="true">
  24:           <config tcpPort="6906"/>
  25:           <serviceThrottling maxConcurrence="8196"/>
  26:           <socketServices>
  27:             <socketService key="MessageService" type="ChatRoom.Socket.MessageService,ChatRoom.Socket"/>
  28:           </socketServices>
  29:         </socket>
  30:       </channel>
  31:       <appServices>
  32:         <service key="Key" type="Value"/>
  33:       </appServices>
  34:     </appserver>
  35:     <objects>
  36:       <!--數據訪問提供者對象-->
  37:       <object name="DbProvider" assembly="EAS.Data.Provider" type="EAS.Data.Access.SqliteProvider" LifestyleType="Thread">
  38:         <property name="ConnectionString" type="string" value="Data Source=..\db\Chat.db;" />
  39:       </object>
  40:       <!--數據訪問器-->
  41:       <object name="DataAccessor" assembly="EAS.Data" type="EAS.Data.Access.DataAccessor" LifestyleType="Thread">
  42:         <property name="DbProvider" type="object" value="DbProvider"/>
  43:         <property name="Language" type="object" value="SqliteLanguage"/>
  44:       </object>
  45:       <!--ORM訪問器-->
  46:       <object name="OrmAccessor" assembly="EAS.Data" type="EAS.Data.ORM.OrmAccessor" LifestyleType="Thread">
  47:         <property name="DataAccessor" type="object" value="DataAccessor"/>
  48:       </object>
  49:       <!--本地服務橋-->
  50:       <object name="ServiceBridger" assembly="EAS.MicroKernel" type="EAS.Services.DirectServiceBridger" LifestyleType="Singleton" />
  51:       <!--Linq查詢語言-->
  52:       <object name="SqliteLanguage" assembly="EAS.Data.Provider" type="EAS.Data.Linq.SqliteLanguage" LifestyleType="Thread"/>
  53:       <!--日誌記錄-->
  54:       <object name="Logger" assembly="EAS.MicroKernel" type="EAS.Loggers.TextLogger" LifestyleType="Singleton">
  55:         <property name="Path" type="string" value="..\logs" />
  56:       </object>
  57:       <!--分佈式服務上下文參數定義。-->
  58:       <object name="EAS.Distributed.ServiceContext" type="EAS.Distributed.ServiceContext,EAS.SOA.Server" LifestyleType="Singleton">
  59:         <property name="EnableLogging" type="bool" value="false" />
  60:       </object>
  61:     </objects>
  62:   </eas>
  63:   <startup>
  64:     <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0"/>
  65:   </startup>
  66: </configuration>

     須要在  <eas/appserver/channel/socket/socketServices>配置節中之中增長了一端:

   1: <socketService key="MessageService" type="ChatRoom.Socket.MessageService,ChatRoom.Socket"/>

     用於告訴SOA服務實例在啓動的時候加載並初始化類型爲「ChatRoom.Socket.MessageService,ChatRoom.Socket」的SocketService。

6、註冊用戶數據庫及Sqlite介紹

    在線多人聊到室系統之中有登陸、用戶,那麼也就必須有數據庫,要存儲這些註冊用戶的信息,爲了方便這案例的使用和部署,咱們選擇了輕量級的Sqlite文件數據庫,其特別是簡單方便,對於小數據量存儲很是好用,有關於Sqlite的知識請本身從網上學習,本人使用的sqlite管理工具爲SQLite Expert。

    註冊用戶表結構以下:

Ø CHAT_USER(聊天室用戶表)

表名

CHAT_USER

全部者

dbo

列名

數據類型

說明

LOGINID

NVARCHAR(64)

N

登陸ID

Name

NVARCHAR(64)

Y

用戶名

PASSWORD

NVARCHAR(64)

Y

密碼

MAIL

VARCHAR(128)

Y

郵件

SafeKey

NVARCHAR(64)

Y

密碼找回問題

SafeResult

NVARCHAR(64)

Y

密碼找回答案

STATE

BIT

Y

狀態

REGTIME

DATETIME

Y

註冊時間

    有關針對CHAT_USER表的數據訪問使用了AgileEAS.NET SOA中間件平臺的ORM及與之配套的Linq進行訪問,其對應的ORM實體對象爲ChatRoom.Entities.User:

   1: using System;
   2: using System.Linq;
   3: using System.ComponentModel;
   4: using System.Data;
   5: using EAS.Data;
   6: using EAS.Data.Access;
   7: using EAS.Data.ORM;
   8: using EAS.Data.Linq;
   9: using System.Runtime.Serialization;
  10:  
  11: namespace ChatRoom.Entities
  12: {
  13:    /// <summary>
  14:    /// 實體對象 User(聊天室用戶表)。
  15:    /// </summary>
  16:    [Serializable()]
  17:    [Table("CHAT_USER","聊天室用戶表")]
  18:    partial class User: DataEntity<User>, IDataEntity<User>
  19:    {
  20:        public User()
  21:        {
  22:            this.RegTime = DateTime.Now;
  23:        }
  24:        
  25:        protected User(SerializationInfo info, StreamingContext context)
  26:            : base(info, context)
  27:        {
  28:        }
  29:        
  30:        #region O/R映射成員
  31:  
  32:        /// <summary>
  33:        /// 登陸ID 。
  34:        /// </summary>
  35:        [Column("LOGINID","登陸ID"),DataSize(64),PrimaryKey]
  36:        [DisplayName("登陸ID")]
  37:        public string LoginID
  38:        {
  39:            get;
  40:            set;
  41:        }
  42:  
  43:        /// <summary>
  44:        /// 用戶名 。
  45:        /// </summary>
  46:        [Column("Name","用戶名"),DataSize(64)]
  47:        [DisplayName("用戶名")]
  48:        public string Name
  49:        {
  50:            get;
  51:            set;
  52:        }
  53:  
  54:        /// <summary>
  55:        /// 密碼 。
  56:        /// </summary>
  57:        [Column("PASSWORD","密碼"),DataSize(64)]
  58:        [DisplayName("密碼")]
  59:        public string Password
  60:        {
  61:            get;
  62:            set;
  63:        }
  64:  
  65:        /// <summary>
  66:        /// 郵件 。
  67:        /// </summary>
  68:        [Column("MAIL","郵件"),DataSize(128)]
  69:        [DisplayName("郵件")]
  70:        public string Mail
  71:        {
  72:            get;
  73:            set;
  74:        }
  75:  
  76:        /// <summary>
  77:        /// 密碼找回問題 。
  78:        /// </summary>
  79:        [Column("SafeKey","密碼找回問題"),DataSize(64)]
  80:        [DisplayName("密碼找回問題")]
  81:        public string SafeKey
  82:        {
  83:            get;
  84:            set;
  85:        }
  86:  
  87:        /// <summary>
  88:        /// 密碼找回答案 。
  89:        /// </summary>
  90:        [Column("SafeResult","密碼找回答案"),DataSize(64)]
  91:        [DisplayName("密碼找回答案")]
  92:        public string SafeResult
  93:        {
  94:            get;
  95:            set;
  96:        }
  97:  
  98:        /// <summary>
  99:        /// 狀態 。
 100:        /// </summary>
 101:        [Column("STATE","狀態")]
 102:        [DisplayName("狀態")]
 103:        public int State
 104:        {
 105:            get;
 106:            set;
 107:        }
 108:  
 109:        /// <summary>
 110:        /// 註冊時間 。
 111:        /// </summary>
 112:        [Column("REGTIME","註冊時間")]
 113:        [DisplayName("註冊時間")]
 114:        public DateTime RegTime
 115:        {
 116:            get;
 117:            set;
 118:        }
 119:        
 120:        #endregion
 121:    }
 122: }

    針對CHAT_USER表的用戶登陸、註冊驗證、找回密碼等代碼的實如今ChatRoom.BLL.Host.UserService之中:

   1: using System;
   2: using System.Collections.Generic;
   3: using System.Linq;
   4: using System.Text;
   5: using EAS.Services;
   6: using ChatRoom.Entities;
   7: using EAS.Data.ORM;
   8: using EAS.Data.Linq;
   9:  
  10: namespace ChatRoom.BLL
  11: {
  12:     /// <summary>
  13:     /// 帳號服務。
  14:     /// </summary>
  15:     [ServiceBind(typeof(IUserService))]
  16:     public class UserService : IUserService
  17:     {
  18:         public void AddUser(User user)
  19:         {
  20:             using (DbEntities db = new DbEntities())
  21:             {
  22:                 user.RegTime = DateTime.Now;
  23:                 int count  = db.Users.Where(p => p.LoginID == user.LoginID).Count();
  24:                 if (count>0)
  25:                 {
  26:                     throw new System.Exception(string.Format("已經存在帳號爲{0}的用戶", user.LoginID));
  27:                 }
  28:  
  29:                 db.Users.Insert(user);
  30:             }
  31:         }
  32:  
  33:         public User UserLogin(string loginID, string password)
  34:         {
  35:             using (DbEntities db = new DbEntities())
  36:             {
  37:                 var v = db.Users.Where(p => p.LoginID == loginID).FirstOrDefault();
  38:                 if (v == null)
  39:                 {
  40:                     throw new System.Exception(string.Format("不存在登帳號稱爲{0}的用戶", loginID));
  41:                 }
  42:  
  43:                 if (v.Password != password)
  44:                 {
  45:                     throw new System.Exception("密碼不正肯定");
  46:                 }
  47:  
  48:                 return v;
  49:             }
  50:         }
  51:  
  52:         public bool UserExists(string loginID)
  53:         {
  54:             using (DbEntities db = new DbEntities())
  55:             {
  56:                 int count = db.Users.Where(p => p.LoginID == loginID).Count();
  57:                 return count > 0;
  58:             }
  59:         }
  60:  
  61:         public string GetSafeKey(string loginID)
  62:         {
  63:             using (DbEntities db = new DbEntities())
  64:             {
  65:                 var v = db.Users.Where(p => p.LoginID == loginID).FirstOrDefault();
  66:                 if (v != null)
  67:                 {
  68:                     return v.SafeKey;
  69:                 }
  70:                 else
  71:                 {
  72:                     return string.Empty;
  73:                 }
  74:             }
  75:         }
  76:  
  77:         public string GetSafeResult(string loginID)
  78:         {
  79:             using (DbEntities db = new DbEntities())
  80:             {
  81:                 var v = db.Users.Where(p => p.LoginID == loginID).FirstOrDefault();
  82:                 if (v != null)
  83:                 {
  84:                     return v.SafeResult;
  85:                 }
  86:                 else
  87:                 {
  88:                     return string.Empty;
  89:                 }
  90:             }
  91:         }
  92:  
  93:  
  94:         public void ChangePassword(string loginID, string password)
  95:         {
  96:             using (DbEntities db = new DbEntities())
  97:             {
  98:                 db.Users.Update(p => new User { Password = password }, p => p.LoginID == loginID);
  99:             }
 100:         }
 101:     }
 102: }

7、關於在線用戶清單的管理

    系統中如何知道目有那些用戶在線,參考以上六節的內容咱們能夠知道,用戶的主鍵是帳號ID,與SocketServer之中在線清單NetSession沒有特定的關係,那麼如何創建這種關係,多而獲得目前有那些用戶在線呢,在ChatRoom.Socket項目之中咱們定義了LoginInfo對象:

   1: using EAS.Sockets;
   2: using System;
   3: using System.Collections.Generic;
   4: using System.Linq;
   5: using System.Text;
   6: using ChatRoom.Entities;
   7:  
   8: namespace ChatRoom.Socket
   9: {
  10:     /// <summary>
  11:     /// 消息信息類。
  12:     /// </summary>
  13:     public class LoginInfo
  14:     {
  15:         /// <summary>
  16:         /// 登陸帳號。
  17:         /// </summary>
  18:         public string LoginID
  19:         {
  20:             get;
  21:             set;
  22:         }
  23:  
  24:         /// <summary>
  25:         /// 用戶對象。
  26:         /// </summary>
  27:         public User User
  28:         {
  29:             get;
  30:             set;
  31:         }
  32:  
  33:         /// <summary>
  34:         /// 會話。
  35:         /// </summary>
  36:         public NetSession Session { get; set; }
  37:     }
  38:  
  39: }

    咱們看源代碼就能夠明確的知道,他是一個用於創建NetSession與登陸用戶LoginID/User對象的影射類,即某個登陸用戶是在那個網絡會話這上,咱們在ChatRoom.Socket項目之中定義了一個ChatRoomContext上下文輔助類:

   1: using EAS.Sockets;
   2: using ChatRoom.Messages;
   3: using System;
   4: using System.Collections.Generic;
   5: using System.Linq;
   6: using System.Text;
   7: using System.Runtime.CompilerServices;
   8:  
   9: namespace ChatRoom.Socket
  10: {
  11:     class ChatRoomContext
  12:     {
  13:         #region 單例模式
  14:  
  15:         private static object m_Lock = new object();
  16:         private static ChatRoomContext m_Instance = null;
  17:  
  18:         public static ChatRoomContext Instance
  19:         {
  20:             get
  21:             {
  22:                 if (m_Instance == null)
  23:                 {
  24:                     lock (m_Lock)
  25:                     {
  26:                         if (m_Instance == null)
  27:                         {
  28:                             m_Instance = new ChatRoomContext();
  29:                         }
  30:                     }
  31:                 }
  32:  
  33:                 return m_Instance;
  34:  
  35:             }
  36:         }
  37:  
  38:         ChatRoomContext()
  39:         {
  40:             this.m_LoginInfos = new List<LoginInfo>();
  41:             this.m_OnLineMessage = new OnLineMessage();
  42:         }
  43:  
  44:         #endregion
  45:  
  46:         ISocketServerBase m_SocketServer = null;
  47:         List<LoginInfo> m_LoginInfos = null;
  48:         OnLineMessage m_OnLineMessage = null;
  49:  
  50:         /// <summary>
  51:         /// Socket服務器。
  52:         /// </summary>
  53:         public ISocketServerBase SocketServer
  54:         {
  55:             get
  56:             {
  57:                 return this.m_SocketServer;
  58:             }
  59:             set
  60:             {
  61:                 this.m_SocketServer = value;
  62:             }
  63:         }
  64:  
  65:         /// <summary>
  66:         /// 會話集合。
  67:         /// </summary>
  68:         public List<LoginInfo> LoginInfos
  69:         {
  70:             get
  71:             {
  72:                 return this.m_LoginInfos;
  73:             }
  74:         }
  75:  
  76:         /// <summary>
  77:         /// 在線清單信息。
  78:         /// </summary>
  79:         public OnLineMessage OnLineMessage
  80:         {
  81:             get
  82:             {
  83:                 return this.m_OnLineMessage;
  84:             }
  85:         }
  86:         
  87:         /// <summary>
  88:         /// 根據Socket會話求上下文信息。
  89:         /// </summary>
  90:         /// <param name="sessionID"></param>
  91:         /// <returns></returns>
  92:         public LoginInfo GetLoginInfo(Guid sessionID)
  93:         {
  94:             LoginInfo v = null;
  95:             lock (this.m_LoginInfos)
  96:             {
  97:                 v = this.m_LoginInfos.Where(p => p.Session.SessionID == sessionID).FirstOrDefault();
  98:             }
  99:  
 100:             return v;
 101:         }
 102:  
 103:         /// <summary>
 104:         /// 根據帳號求上下文信息。
 105:         /// </summary>
 106:         /// <param name="LoginID"></param>
 107:         /// <returns></returns>
 108:         public LoginInfo GetLoginInfo(string LoginID)
 109:         {
 110:             LoginInfo v = null;
 111:             lock (this.m_LoginInfos)
 112:             {
 113:                 v = this.m_LoginInfos.Where(p => p.LoginID == LoginID).FirstOrDefault();
 114:             }
 115:  
 116:             return v;
 117:         }        
 118:        
 119:         /// <summary>
 120:         /// 登陸註冊上下文。
 121:         /// </summary>
 122:         /// <param name="info"></param>
 123:         public void Add(LoginInfo info)
 124:         {
 125:             lock (this.m_LoginInfos)
 126:             {
 127:                 int count = this.m_LoginInfos.Where
 128:                     (p => p.Session.SessionID == info.Session.SessionID
 129:                         && p.LoginID == info.LoginID
 130:                     ).Count();
 131:  
 132:                 if (count == 0)
 133:                 {
 134:                     this.m_LoginInfos.Add(info);
 135:                     info.Session.ClientClosed += Session_ClientClosed;
 136:                 }
 137:             }
 138:  
 139:             this.CreateOnLineMesssage();
 140:         }
 141:  
 142:         /// <summary>
 143:         /// 連接關機上下文。
 144:         /// </summary>
 145:         /// <param name="session"></param>
 146:         public void Remove(Guid session)
 147:         {
 148:             lock (this.m_LoginInfos)
 149:             {
 150:                 LoginInfo info = this.m_LoginInfos.Where(p => p.Session.SessionID == session).FirstOrDefault();
 151:  
 152:                 if (info != null)
 153:                 {
 154:                     this.m_LoginInfos.Remove(info);
 155:                     info.Session.ClientClosed -= new EventHandler(Session_ClientClosed);
 156:                 }
 157:             }
 158:         }
 159:  
 160:         /// <summary>
 161:         /// 生成在線清單信息。
 162:         /// </summary>
 163:         [MethodImpl(MethodImplOptions.Synchronized)]
 164:         void CreateOnLineMesssage()
 165:         {
 166:             this.m_OnLineMessage = new OnLineMessage();
 167:             lock (this.m_LoginInfos)
 168:             {
 169:                 foreach (var item in this.m_LoginInfos)
 170:                 {
 171:                     OnLine onLine = new OnLine();
 172:                     onLine.LoginID = item.LoginID;
 173:                     onLine.Name = item.User.Name;
 174:                     this.m_OnLineMessage.OnLines.Add(onLine);
 175:                 }
 176:             }
 177:         }
 178:  
 179:         /// <summary>
 180:         /// 客戶段鏈接斷開,用戶下線。
 181:         /// </summary>
 182:         /// <param name="sender"></param>
 183:         /// <param name="e"></param>
 184:         private void Session_ClientClosed(object sender, EventArgs e)
 185:         {
 186:             NetSession session = sender as NetSession;
 187:             LoginInfo loginInfo = this.GetLoginInfo(session.SessionID);
 188:             this.Remove(session.SessionID);
 189:             this.CreateOnLineMesssage();
 190:  
 191:             //向其餘用戶發生下線通稿。
 192:             UserStateMessage userState = new UserStateMessage();
 193:             userState.OnLine = false;
 194:             userState.User = loginInfo.User;
 195:  
 196:             lock (this.m_LoginInfos)
 197:             {
 198:                 foreach (var item in this.m_LoginInfos)
 199:                 {
 200:                     ChatRoomContext.Instance.SocketServer.Send(item.Session.SessionID, userState);
 201:                 }
 202:             }
 203:         }
 204:     }
 205: }

    其中public List<LoginInfo> LoginInfos屬性即爲當前在線的用戶與網絡會話(NetSession)的映射清單。

8、服務端處理登陸/上線、下線流程

    客戶端在處理用戶登陸時執行如下流程:

image_thumb6

    執行本流程的具體代碼在ChatRoom.Socket項目之中的登陸消息處理器LoginMessageHandler之中:

   1: using EAS.Sockets;
   2: using EAS.Sockets.Messages;
   3: using System;
   4: using System.Collections.Generic;
   5: using System.Linq;
   6: using System.Text;
   7: using System.Data;
   8: using EAS.Data.Access;
   9: using ChatRoom.Messages;
  10: using EAS.Loggers;
  11: using ChatRoom.BLL;
  12: using ChatRoom.Entities;
  13: using EAS.Services;
  14:  
  15: namespace ChatRoom.Socket
  16: {
  17:     /// <summary>
  18:     /// 用戶登陸消息處理程序。
  19:     /// </summary>
  20:     public class LoginMessageHandler : AbstractMessageHandler<LoginMessage>
  21:     {
  22:         public override void Process(NetSession context, uint instanceId, LoginMessage message)
  23:         {
  24:             LoginResultMessage result = new LoginResultMessage();
  25:             IUserService services = ServiceContainer.GetService<IUserService>();
  26:             try
  27:             {
  28:                 result.User = services.UserLogin(message.LoginID, message.PassWord);
  29:             }
  30:             catch (System.Exception exc)
  31:             {
  32:                 result.Error = exc.Message;
  33:             }
  34:  
  35:             //X.登陸失敗。
  36:             if (!string.IsNullOrEmpty(result.Error))
  37:             {
  38:                 context.Reply(result);
  39:                 return;
  40:             }
  41:  
  42:             //A.登陸成功,作以下處理
  43:             
  44:             #region //1.向其發送登陸成功消息
  45:  
  46:             context.Reply(result);
  47:  
  48:             #endregion
  49:  
  50:             #region //2.向其餘用戶發送上線通告
  51:  
  52:             UserStateMessage userState = new UserStateMessage();
  53:             userState.OnLine = true;
  54:             userState.User = result.User;
  55:  
  56:             var vList = ChatRoomContext.Instance.LoginInfos;
  57:             if (vList.Count > 0)
  58:             {
  59:                 lock (vList)
  60:                 {
  61:                     foreach (var item in vList)
  62:                     {
  63:                         ChatRoomContext.Instance.SocketServer.Send(item.Session.SessionID, userState);
  64:                     }
  65:                 }
  66:             }
  67:  
  68:             #endregion
  69:  
  70:             #region //3.註冊到上下文環境
  71:  
  72:             LoginInfo loginInfo = new LoginInfo();
  73:             loginInfo.LoginID = message.LoginID;
  74:             loginInfo.User = result.User;
  75:             loginInfo.Session = context;
  76:             ChatRoomContext.Instance.Add(loginInfo);
  77:             #endregion
  78:  
  79:             #region //4.向客戶段發送在線清單
  80:  
  81:             context.Reply(ChatRoomContext.Instance.OnLineMessage);
  82:  
  83:             #endregion
  84:         }
  85:     }
  86: }

    當客戶端下線/斷開連接以後服務端會向其餘在線的客戶段發送一個UserStateMessage狀態通告消息,告訴其餘在線客戶端,某人已經下線。

9、服務端聊天消息轉發流程

     當服務端接收到客戶端發來的聊天消息以後,如何轉發呢,請參見下圖:

image_thumb9

     關於這一部分的代碼請參考ChatRoom.Socket項目之中的聊天消息處理器ChatMessageHandler之中::

   1: using EAS.Sockets;
   2: using EAS.Sockets.Messages;
   3: using ChatRoom.Messages;
   4: using System;
   5: using System.Collections.Generic;
   6: using System.Linq;
   7: using System.Text;
   8:  
   9: namespace ChatRoom.Socket
  10: {
  11:     /// <summary>
  12:     /// 服務器收到聊天消息處理程序。
  13:     /// </summary>
  14:     public class ChatMessageHandler : AbstractMessageHandler<ChatMessage>
  15:     {
  16:         public override void Process(NetSession context, uint instanceId, ChatMessage message)
  17:         {
  18:             if (!message.Secret)  //廣播消息。
  19:             {
  20:                 lock (ChatRoomContext.Instance.LoginInfos)
  21:                 {
  22:                     foreach (var p in ChatRoomContext.Instance.LoginInfos)
  23:                     {
  24:                         context.Server.Send(p.Session.SessionID, message);
  25:                     }
  26:                 }
  27:             }
  28:             else
  29:             {
  30:                 LoginInfo loginInfo = ChatRoomContext.Instance.GetLoginInfo(message.To);
  31:                 if (loginInfo != null)
  32:                 {
  33:                     context.Server.Send(loginInfo.Session.SessionID, message);
  34:                 }
  35:             }
  36:         }
  37:     }
  38: }

     關於這一部分的代碼請參考:

10、客戶端界面的異步處理

     由於AgileEAS.NET SOA 中間件平臺Socket 通訊框架何用的是異步消息處理模式,因此當客戶端收到服務器發回的消息的時候其工做線程與界面UI線呢不一致,那麼UI界面處理的時候咱們就須要異步處理,好比在顯示收到的ChatMessage的時候:

   1: /// <summary>
   2: /// 顯示聊天消息。
   3: /// </summary>
   4: /// <param name="chat"></param>
   5: internal void ShowMessage(ChatMessage chat)
   6: {
   7:     Action action = () =>
   8:     {
   9:         string form = "你";
  10:         string to = "你";
  11:  
  12:         //其餘人。
  13:         if (chat.From != AppContext.User.LoginID)
  14:         {
  15:             var v = this.m_OnLines.Where(p => p.LoginID == chat.From).FirstOrDefault();
  16:             if (v != null)
  17:                 form = v.Name;
  18:             else
  19:                 form = chat.From;
  20:         }
  21:  
  22:         //全部人
  23:         if (string.IsNullOrEmpty(chat.To))
  24:         {
  25:             to = DEFAULT_ALL_USER;
  26:         }
  27:         else  //
  28:         {
  29:             var v = this.m_OnLines.Where(p => p.LoginID == chat.To).FirstOrDefault();
  30:             if (v != null)
  31:                 to = v.Name;
  32:             else
  33:                 to = chat.From;
  34:         }
  35:  
  36:         string face = string.IsNullOrEmpty(chat.Face) ? string.Empty : string.Format("{0}地", chat.Face);
  37:         string Text = string.Format("【{0}】{1}{2}對【{3}】說:{4}", form, chat.Action, face, to, chat.Content);
  38:  
  39:         ListViewItem item = new ListViewItem(new string[] { string.Empty, Text });
  40:         item.ForeColor = Color.FromArgb(chat.Color);
  41:         item.Tag = chat.From;
  42:  
  43:         if (chat.Secret)  //密聊
  44:         {
  45:             this.lvSecret.Items.Add(item);
  46:             this.lvSecret.EnsureVisible(item.Index);
  47:         }
  48:         else
  49:         {
  50:             this.lvAll.Items.Add(item);
  51:             this.lvAll.EnsureVisible(item.Index);
  52:         }
  53:     };
  54:  
  55:     this.Invoke(action);
  56: }

     咱們定義了一個名稱爲action的匿名方法,使用this.Invoke(action)進行界面的消息顯示。

11、聯繫咱們

     爲了完善、改進和推廣AgileEAS.NET而成立了敏捷軟件工程實驗室,是一家研究、推廣和發展新技術,並致力於提供具備自主知識產權的業務基礎平臺軟件,以及基於業務基礎平臺了開發的管理軟件的專業軟件提供商。主要業務是爲客戶提供軟件企業研發管理解決方案、企業管理軟件開發,以及相關的技術支持,管理及技術諮詢與培訓業務。

     AgileEAS.NET平臺自2004年秋呱呱落地一來,我就一直在逐步完善和改進,也被應用於保險、醫療、電子商務、房地產、鐵路、教育等多個應用,但一直都是以我我的在推廣,2010年由於我辭職休息,我就想到把AgileEAS.NET推向市場,讓更多的人使用。

     個人技術團隊成員都是合做多年的老朋友,由於這個平臺是免費的,因此也沒有什麼收入,都是由程序員的那種理想與信念堅持,在此我感謝一塊兒奮鬥的朋友。

團隊網站:http://www.agilelab.cn

AgileEAS.NET網站:http://www.agileeas.net

官方博客:http://eastjade.cnblogs.com

github:https://github.com/agilelab/eas

QQ:47920381

AgileEAS.NET QQ羣:

113723486(AgileEAS SOA 平臺)/上限1000人

199463175(AgileEAS SOA 交流)/上限1000人

120661978(AgileEAS.NET 平臺交流)/上限1000人

212867943(AgileEAS.NET研究)/上限500人

147168308(AgileEAS.NET應用)/上限500人

172060626(深度AgileEAS.NET平臺)/上限500人

116773358(AgileEAS.NET 平臺)/上限500人

125643764(AgileEAS.NET探討)/上限500人

193486983(AgileEAS.NET 平臺)/上限500人

郵件:james@agilelab.cn,mail.james@qq.com,

電話:18629261335。

相關文章
相關標籤/搜索