AgileEAS.NET SOA 中间件平台.Net Socket通信框架-完整应用例子-在线聊天室系统-代码解析...
一、AgileEAS.NET SOA中間件Socket/Tcp框架介紹
???? 在文章AgileEAS.NET SOA 中間件平臺.Net Socket通信框架-介紹一文之中我們對AgileEAS.NET SOA中間Socket/Tcp框架進行了總體的介紹,我們知道
AgileEAS.NET SOA中間件Socket/Tcp框架是一套Socket通信的消息中間件:
二、多人在線聊天室系統
????? 在文章AgileEAS.NET SOA 中間件平臺.Net Socket通信框架-簡單例子-實現簡單的服務端客戶端消息應答給大家實例介紹了有關于AgileEAS.NET SOA 中間件Socket通信框架的簡單應用之后,我們通過文章AgileEAS.NET SOA 中間件平臺.Net Socket通信框架-完整應用例子-在線聊天室系統-下載配置向大家展示了一個完整成熟的.NET Socket 通信框架的應用案例,一個從在線聊天室系統,通過文章向大家講解了如何下載和編譯安案例源代碼、以及如何配置服務端和客戶段。
????? 相對于簡單的客戶端==》服務端消息請求與應答的例子而言,在線多人聊天室系統的復雜度都要超過客戶端==》服務端消息請求例子N多倍,但是限于文章篇幅的原因,我們沒有在文章AgileEAS.NET SOA 中間件平臺.Net Socket通信框架-完整應用例子-在線聊天室系統-下載配置這中為大家介紹這個案例的具體代碼。
???? 下面我將為大家介紹這個案例的關鍵代碼及閱讀、理解、修改完善所需要注意的地方。
三、關于代碼編譯環境及其他的地些設置
???? 本案例的源代碼在下載壓縮包的Code目錄之中,所有的引用AgileEAS.NET SOA 中間件平臺的程序集及客戶端、服務端運行所必須的文件都在下載壓縮包的Publish目錄之中,所有項目的編譯輸出路徑也都是在Publish目錄,也就是所有項目不管是在Debug編譯環境還是在Release編譯環境都是輸出在Publish目錄之中,有關具體的設置請看下圖:
四、解決方案之中的項目說明
???? ChatRoom解決方案之是共有ChatRoom.Entities、ChatRoom.BLL.Contracts、ChatRoom.BLL.Host、ChatRoom.Messages、ChatRoom.Socket、ChatingRoom.MainClient、ChatingRoom.UserManage共七個項目,其中:
??? ChatRoom.Entities:是聊天室注冊用啟的數據存儲實體,其中只包括一個實體User,即注冊用戶信息。
??? ChatRoom.BLL.Contracts:為用戶管理、登錄驗證、密碼找回修改等功能的分布式服務定義契約,其中僅包括一個服務契約定義IUserService(用戶服務)。
??? ChatRoom.BLL.Host:為ChatRoom.BLL.Contracts所定義的服務契約的功能實現。
??? ChatRoom.Messages:服務端與客戶端通信消息的定義,包括聊天消息、用戶登錄請求、登錄結果、在線用戶清單消息、用戶上下線狀態通知消息。
??? ChatRoom.Socket:為服務端的業務代碼、包括AgileEAS.NET SOA服務進程的SocketService插件以及服務端收到客戶端各種消息的消息處理器代碼。
??? ChatingRoom.MainClient:為客戶端代碼、包括客戶段界面以及客戶端收到通信消息的消息處理器代碼。
五、關于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。
六、注冊用戶數據庫及Sqlite介紹
??? 在線多人聊到室系統之中有登錄、用戶,那么也就必須有數據庫,要存儲這些注冊用戶的信息,為了方便這案例的使用和部署,我們選擇了輕量級的Sqlite文件數據庫,其特別是簡單方便,對于小數據量存儲非常好用,有關于Sqlite的知識請自己從網上學習,本人使用的sqlite管理工具為SQLite Expert。
??? 注冊用戶表結構如下:
? CHAT_USER(聊天室用戶表)
| 表名 | CHAT_USER | ||
| 所有者 | dbo | ||
| 列名 | 數據類型 | 空 | 說明 |
| LOGINID | NVARCHAR(64) | N | 登錄ID |
| Name | NVARCHAR(64) | Y | 用戶名 |
| PASSWORD | NVARCHAR(64) | Y | 密碼 |
| | 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: }七、關于在線用戶清單的管理
??? 系統中如何知道目有那些用戶在線,參考以上六節的內容我們可以知道,用戶的主鍵是賬號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)的映射清單。
八、服務端處理登錄/上線、下線流程
??? 客戶端在處理用戶登錄時執行以下流程:
??? 執行本流程的具體代碼在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狀態通告消息,告訴其他在線客戶端,某人已經下線。
九、服務端聊天消息轉發流程
???? 當服務端接收到客戶端發來的聊天消息之后,如何轉發呢,請參見下圖:
???? 關于這一部分的代碼請參考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: }???? 關于這一部分的代碼請參考:
十、客戶端界面的異步處理
???? 因為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)進行界面的消息顯示。
十一、聯系我們
???? 為了完善、改進和推廣AgileEAS.NET而成立了敏捷軟件工程實驗室,是一家研究、推廣和發展新技術,并致力于提供具有自主知識產權的業務基礎平臺軟件,以及基于業務基礎平臺了開發的管理軟件的專業軟件提供商。主要業務是為客戶提供軟件企業研發管理解決方案、企業管理軟件開發,以及相關的技術支持,管理及技術咨詢與培訓業務。
???? AgileEAS.NET平臺自2004年秋呱呱落地一來,我就一直在逐步完善和改進,也被應用于保險、醫療、電子商務、房地產、鐵路、教育等多個應用,但一直都是以我個人在推廣,2010年因為我辭職休息,我就想到把AgileEAS.NET推向市場,讓更多的人使用。
???? 我的技術團隊成員都是合作多年的老朋友,因為這個平臺是免費的,所以也沒有什么收入,都是由程序員的那種理想與信念堅持,在此我感謝一起奮斗的朋友。
團隊網站:http://www.agilelab.cn,
AgileEAS.NET網站:http://www.smarteas.net
官方博客:http://eastjade.cnblogs.com
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。
總結
以上是生活随笔為你收集整理的AgileEAS.NET SOA 中间件平台.Net Socket通信框架-完整应用例子-在线聊天室系统-代码解析...的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 使用bash编写Linux shell脚
- 下一篇: char与varchar区别(MYISA