AgileEAS.NET SOA 中间件平台.Net Socket通信框架-完整应用例子-在线聊天室系统-代码解析...
一、AgileEAS.NET SOA中間件Socket/Tcp框架介紹
???? 在文章AgileEAS.NET SOA 中間件平臺(tái).Net Socket通信框架-介紹一文之中我們對(duì)AgileEAS.NET SOA中間Socket/Tcp框架進(jìn)行了總體的介紹,我們知道
AgileEAS.NET SOA中間件Socket/Tcp框架是一套Socket通信的消息中間件:
二、多人在線聊天室系統(tǒng)
????? 在文章AgileEAS.NET SOA 中間件平臺(tái).Net Socket通信框架-簡(jiǎn)單例子-實(shí)現(xiàn)簡(jiǎn)單的服務(wù)端客戶端消息應(yīng)答給大家實(shí)例介紹了有關(guān)于AgileEAS.NET SOA 中間件Socket通信框架的簡(jiǎn)單應(yīng)用之后,我們通過(guò)文章AgileEAS.NET SOA 中間件平臺(tái).Net Socket通信框架-完整應(yīng)用例子-在線聊天室系統(tǒng)-下載配置向大家展示了一個(gè)完整成熟的.NET Socket 通信框架的應(yīng)用案例,一個(gè)從在線聊天室系統(tǒng),通過(guò)文章向大家講解了如何下載和編譯安案例源代碼、以及如何配置服務(wù)端和客戶段。
????? 相對(duì)于簡(jiǎn)單的客戶端==》服務(wù)端消息請(qǐng)求與應(yīng)答的例子而言,在線多人聊天室系統(tǒng)的復(fù)雜度都要超過(guò)客戶端==》服務(wù)端消息請(qǐng)求例子N多倍,但是限于文章篇幅的原因,我們沒(méi)有在文章AgileEAS.NET SOA 中間件平臺(tái).Net Socket通信框架-完整應(yīng)用例子-在線聊天室系統(tǒng)-下載配置這中為大家介紹這個(gè)案例的具體代碼。
???? 下面我將為大家介紹這個(gè)案例的關(guān)鍵代碼及閱讀、理解、修改完善所需要注意的地方。
三、關(guān)于代碼編譯環(huán)境及其他的地些設(shè)置
???? 本案例的源代碼在下載壓縮包的Code目錄之中,所有的引用AgileEAS.NET SOA 中間件平臺(tái)的程序集及客戶端、服務(wù)端運(yùn)行所必須的文件都在下載壓縮包的Publish目錄之中,所有項(xiàng)目的編譯輸出路徑也都是在Publish目錄,也就是所有項(xiàng)目不管是在Debug編譯環(huán)境還是在Release編譯環(huán)境都是輸出在Publish目錄之中,有關(guān)具體的設(shè)置請(qǐng)看下圖:
四、解決方案之中的項(xiàng)目說(shuō)明
???? ChatRoom解決方案之是共有ChatRoom.Entities、ChatRoom.BLL.Contracts、ChatRoom.BLL.Host、ChatRoom.Messages、ChatRoom.Socket、ChatingRoom.MainClient、ChatingRoom.UserManage共七個(gè)項(xiàng)目,其中:
??? ChatRoom.Entities:是聊天室注冊(cè)用啟的數(shù)據(jù)存儲(chǔ)實(shí)體,其中只包括一個(gè)實(shí)體User,即注冊(cè)用戶信息。
??? ChatRoom.BLL.Contracts:為用戶管理、登錄驗(yàn)證、密碼找回修改等功能的分布式服務(wù)定義契約,其中僅包括一個(gè)服務(wù)契約定義IUserService(用戶服務(wù))。
??? ChatRoom.BLL.Host:為ChatRoom.BLL.Contracts所定義的服務(wù)契約的功能實(shí)現(xiàn)。
??? ChatRoom.Messages:服務(wù)端與客戶端通信消息的定義,包括聊天消息、用戶登錄請(qǐng)求、登錄結(jié)果、在線用戶清單消息、用戶上下線狀態(tài)通知消息。
??? ChatRoom.Socket:為服務(wù)端的業(yè)務(wù)代碼、包括AgileEAS.NET SOA服務(wù)進(jìn)程的SocketService插件以及服務(wù)端收到客戶端各種消息的消息處理器代碼。
??? ChatingRoom.MainClient:為客戶端代碼、包括客戶段界面以及客戶端收到通信消息的消息處理器代碼。
五、關(guān)于SOA服務(wù)SocketService插件
??? 如果對(duì)比AgileEAS.NET SOA 中間件平臺(tái).Net Socket通信框架-簡(jiǎn)單例子-實(shí)現(xiàn)簡(jiǎn)單的服務(wù)端客戶端消息應(yīng)答,細(xì)心的朋友一定會(huì)發(fā)現(xiàn)本案例中沒(méi)有了類似Socket.Demo.Server功能的項(xiàng)目,而是多了ChatRoom.Socket項(xiàng)目。
??? 關(guān)于這個(gè)問(wèn)題就涉及到了AgileEAS.NET SOA 中間件平臺(tái)的SOA服務(wù)實(shí)例及Socket框架的設(shè)計(jì),在SOA服務(wù)實(shí)例本身被設(shè)計(jì)成為了一個(gè)可以運(yùn)行WCF、WS、Socket等各吃點(diǎn)通信及其他應(yīng)用服務(wù)的運(yùn)行容器,那么我們的Socket服務(wù)端也可以在此服務(wù)實(shí)例之中運(yùn)行,同時(shí)在我們的AgileEAS.NET SOA中間件平臺(tái)的微內(nèi)核程序集EAS.MicroKernel.dll之中定義了SocketService插件的實(shí)現(xiàn)標(biāo)準(zhǔn):
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服務(wù)接口定義。 11: /// </summary> 12: /// <remarks> 13: /// 一個(gè)Socket服務(wù)器可以承載多種/個(gè)Socket服務(wù),一個(gè)Socket服務(wù)處理一種業(yè)務(wù)。 14: /// 如IM SocketService 處理IM相關(guān)的即時(shí)通訊業(yè)務(wù),而WF SocketService 處理工作流相關(guān)的服務(wù),這兩種Socket服務(wù)可以同時(shí)運(yùn)行在一個(gè)Socket服務(wù)器之中。 15: /// </remarks> 16: public interface ISocketService:IAppService 17: { 18: /// <summary> 19: /// 使用ServerEngine初始化SocketService。 20: /// </summary> 21: /// <param name="socketServer">Socket服務(wù)器對(duì)象。</param> 22: void Initialize(ISocketServerBase socketServer); 23: } 24: }??? ISocketService接口中定義了一個(gè)初始化方法:void Initialize(ISocketServerBase socketServer),用于SOA服務(wù)實(shí)例完成對(duì)ISocketService實(shí)例的初始化,其中傳入?yún)?shù)為一個(gè)ISocketServerBase對(duì)象,其本質(zhì)的含義為SOA服務(wù)實(shí)例調(diào)用ISocketService實(shí)例對(duì)象把SOA服務(wù)實(shí)例之中的SocketServer對(duì)象做為參數(shù)傳入,那么我們就可以在ISocketService對(duì)象之中針對(duì)SocketServer做一些初始化工作,其中最重要的工作是,掛載與之相關(guān)的消息對(duì)象器IMessageHandler。
??? ChatRoom.Socket項(xiàng)目之中包括了一個(gè)ISocketService的實(shí)現(xiàn)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: /// 聊天室消息服務(wù),由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函數(shù)之中掛載ChatMessage、LoginMessage兩個(gè)消息的消息處理器代碼:
1: socketServer.AddHander(new ChatMessageHandler()); 2: socketServer.AddHander(new LoginMessageHandler());??? Socket插件服務(wù)的定義除了代碼定義之外,還需要在AgileEAS.NET SOA 中間件有SOA服務(wù)實(shí)例配置文件之中進(jìn)行定義,因?yàn)镾OA服務(wù)實(shí)例程序有32位和64位版本,分別為EAS.SOA.Server.exe和EAS.SOA.Server.x64.exe,所以要根據(jù)自身的機(jī)器條件和自己喜歡的運(yùn)行環(huán)境修改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: <!--數(shù)據(jù)訪問(wèn)提供者對(duì)象--> 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: <!--數(shù)據(jù)訪問(wèn)器--> 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訪問(wèn)器--> 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: <!--本地服務(wù)橋--> 50: <object name="ServiceBridger" assembly="EAS.MicroKernel" type="EAS.Services.DirectServiceBridger" LifestyleType="Singleton" /> 51: <!--Linq查詢語(yǔ)言--> 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: <!--分布式服務(wù)上下文參數(shù)定義。--> 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>配置節(jié)中之中增加了一端:
1: <socketService key="MessageService" type="ChatRoom.Socket.MessageService,ChatRoom.Socket"/>???? 用于告訴SOA服務(wù)實(shí)例在啟動(dòng)的時(shí)候加載并初始化類型為“ChatRoom.Socket.MessageService,ChatRoom.Socket”的SocketService。
六、注冊(cè)用戶數(shù)據(jù)庫(kù)及Sqlite介紹
??? 在線多人聊到室系統(tǒng)之中有登錄、用戶,那么也就必須有數(shù)據(jù)庫(kù),要存儲(chǔ)這些注冊(cè)用戶的信息,為了方便這案例的使用和部署,我們選擇了輕量級(jí)的Sqlite文件數(shù)據(jù)庫(kù),其特別是簡(jiǎn)單方便,對(duì)于小數(shù)據(jù)量存儲(chǔ)非常好用,有關(guān)于Sqlite的知識(shí)請(qǐng)自己從網(wǎng)上學(xué)習(xí),本人使用的sqlite管理工具為SQLite Expert。
??? 注冊(cè)用戶表結(jié)構(gòu)如下:
? CHAT_USER(聊天室用戶表)
| 表名 | CHAT_USER | ||
| 所有者 | dbo | ||
| 列名 | 數(shù)據(jù)類型 | 空 | 說(shuō)明 |
| LOGINID | NVARCHAR(64) | N | 登錄ID |
| Name | NVARCHAR(64) | Y | 用戶名 |
| PASSWORD | NVARCHAR(64) | Y | 密碼 |
| | VARCHAR(128) | Y | 郵件 |
| SafeKey | NVARCHAR(64) | Y | 密碼找回問(wèn)題 |
| SafeResult | NVARCHAR(64) | Y | 密碼找回答案 |
| STATE | BIT | Y | 狀態(tài) |
| REGTIME | DATETIME | Y | 注冊(cè)時(shí)間 |
??? 有關(guān)針對(duì)CHAT_USER表的數(shù)據(jù)訪問(wèn)使用了AgileEAS.NET SOA中間件平臺(tái)的ORM及與之配套的Linq進(jìn)行訪問(wèn),其對(duì)應(yīng)的ORM實(shí)體對(duì)象為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: /// 實(shí)體對(duì)象 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: /// 密碼找回問(wèn)題 。 78: /// </summary> 79: [Column("SafeKey","密碼找回問(wèn)題"),DataSize(64)] 80: [DisplayName("密碼找回問(wèn)題")] 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: /// 狀態(tài) 。 100: /// </summary> 101: [Column("STATE","狀態(tài)")] 102: [DisplayName("狀態(tài)")] 103: public int State 104: { 105: get; 106: set; 107: } 108:? 109: /// <summary> 110: /// 注冊(cè)時(shí)間 。 111: /// </summary> 112: [Column("REGTIME","注冊(cè)時(shí)間")] 113: [DisplayName("注冊(cè)時(shí)間")] 114: public DateTime RegTime 115: { 116: get; 117: set; 118: } 119: 120: #endregion 121: } 122: }??? 針對(duì)CHAT_USER表的用戶登錄、注冊(cè)驗(yàn)證、找回密碼等代碼的實(shí)現(xiàn)在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: /// 賬號(hào)服務(wù)。 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("已經(jīng)存在賬號(hào)為{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("不存在登賬號(hào)稱為{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: }七、關(guān)于在線用戶清單的管理
??? 系統(tǒng)中如何知道目有那些用戶在線,參考以上六節(jié)的內(nèi)容我們可以知道,用戶的主鍵是賬號(hào)ID,與SocketServer之中在線清單NetSession沒(méi)有特定的關(guān)系,那么如何建立這種關(guān)系,多而得到目前有那些用戶在線呢,在ChatRoom.Socket項(xiàng)目之中我們定義了LoginInfo對(duì)象:
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: /// 登錄賬號(hào)。 17: /// </summary> 18: public string LoginID 19: { 20: get; 21: set; 22: } 23:? 24: /// <summary> 25: /// 用戶對(duì)象。 26: /// </summary> 27: public User User 28: { 29: get; 30: set; 31: } 32:? 33: /// <summary> 34: /// 會(huì)話。 35: /// </summary> 36: public NetSession Session { get; set; } 37: } 38:? 39: }??? 我們看源代碼就可以明確的知道,他是一個(gè)用于建立NetSession與登錄用戶LoginID/User對(duì)象的影射類,即某個(gè)登錄用戶是在那個(gè)網(wǎng)絡(luò)會(huì)話這上,我們?cè)贑hatRoom.Socket項(xiàng)目之中定義了一個(gè)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服務(wù)器。 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: /// 會(huì)話集合。 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: /// 根據(jù)Socket會(huì)話求上下文信息。 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: /// 根據(jù)賬號(hào)求上下文信息。 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: /// 登錄注冊(cè)上下文。 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: /// 鏈接關(guān)機(jī)上下文。 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: /// 客戶段連接斷開(kāi),用戶下線。 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: //向其他用戶發(fā)生下線通稿。 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屬性即為當(dāng)前在線的用戶與網(wǎng)絡(luò)會(huì)話(NetSession)的映射清單。
八、服務(wù)端處理登錄/上線、下線流程
??? 客戶端在處理用戶登錄時(shí)執(zhí)行以下流程:
??? 執(zhí)行本流程的具體代碼在ChatRoom.Socket項(xiàng)目之中的登錄消息處理器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.向其發(fā)送登錄成功消息 45:? 46: context.Reply(result); 47:? 48: #endregion 49:? 50: #region //2.向其他用戶發(fā)送上線通告 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.注冊(cè)到上下文環(huán)境 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.向客戶段發(fā)送在線清單 80:? 81: context.Reply(ChatRoomContext.Instance.OnLineMessage); 82:? 83: #endregion 84: } 85: } 86: }??? 當(dāng)客戶端下線/斷開(kāi)鏈接之后服務(wù)端會(huì)向其他在線的客戶段發(fā)送一個(gè)UserStateMessage狀態(tài)通告消息,告訴其他在線客戶端,某人已經(jīng)下線。
九、服務(wù)端聊天消息轉(zhuǎn)發(fā)流程
???? 當(dāng)服務(wù)端接收到客戶端發(fā)來(lái)的聊天消息之后,如何轉(zhuǎn)發(fā)呢,請(qǐng)參見(jiàn)下圖:
???? 關(guān)于這一部分的代碼請(qǐng)參考ChatRoom.Socket項(xiàng)目之中的聊天消息處理器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: /// 服務(wù)器收到聊天消息處理程序。 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: }???? 關(guān)于這一部分的代碼請(qǐng)參考:
十、客戶端界面的異步處理
???? 因?yàn)锳gileEAS.NET SOA 中間件平臺(tái)Socket 通信框架何用的是異步消息處理模式,所以當(dāng)客戶端收到服務(wù)器發(fā)回的消息的時(shí)候其工作線程與界面UI線呢不一致,那么UI界面處理的時(shí)候我們就需要異步處理,比如在顯示收到的ChatMessage的時(shí)候:
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}對(duì)【{3}】說(shuō):{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: }???? 我們定義了一個(gè)名稱為action的匿名方法,使用this.Invoke(action)進(jìn)行界面的消息顯示。
十一、聯(lián)系我們
???? 為了完善、改進(jìn)和推廣AgileEAS.NET而成立了敏捷軟件工程實(shí)驗(yàn)室,是一家研究、推廣和發(fā)展新技術(shù),并致力于提供具有自主知識(shí)產(chǎn)權(quán)的業(yè)務(wù)基礎(chǔ)平臺(tái)軟件,以及基于業(yè)務(wù)基礎(chǔ)平臺(tái)了開(kāi)發(fā)的管理軟件的專業(yè)軟件提供商。主要業(yè)務(wù)是為客戶提供軟件企業(yè)研發(fā)管理解決方案、企業(yè)管理軟件開(kāi)發(fā),以及相關(guān)的技術(shù)支持,管理及技術(shù)咨詢與培訓(xùn)業(yè)務(wù)。
???? AgileEAS.NET平臺(tái)自2004年秋呱呱落地一來(lái),我就一直在逐步完善和改進(jìn),也被應(yīng)用于保險(xiǎn)、醫(yī)療、電子商務(wù)、房地產(chǎn)、鐵路、教育等多個(gè)應(yīng)用,但一直都是以我個(gè)人在推廣,2010年因?yàn)槲肄o職休息,我就想到把AgileEAS.NET推向市場(chǎng),讓更多的人使用。
???? 我的技術(shù)團(tuán)隊(duì)成員都是合作多年的老朋友,因?yàn)檫@個(gè)平臺(tái)是免費(fèi)的,所以也沒(méi)有什么收入,都是由程序員的那種理想與信念堅(jiān)持,在此我感謝一起奮斗的朋友。
團(tuán)隊(duì)網(wǎng)站:http://www.agilelab.cn,
AgileEAS.NET網(wǎng)站:http://www.smarteas.net
官方博客:http://eastjade.cnblogs.com
QQ:47920381,AgileEAS.NET
QQ群:113723486(AgileEAS SOA 平臺(tái))/上限1000人
199463175(AgileEAS SOA 交流)/上限1000人
120661978(AgileEAS.NET 平臺(tái)交流)/上限1000人
212867943(AgileEAS.NET研究)/上限500人
147168308(AgileEAS.NET應(yīng)用)/上限500人
172060626(深度AgileEAS.NET平臺(tái))/上限500人
116773358(AgileEAS.NET 平臺(tái))/上限500人
125643764(AgileEAS.NET探討)/上限500人
193486983(AgileEAS.NET 平臺(tái))/上限500人
郵件:james@agilelab.cn,mail.james@qq.com,
電話:18629261335。
總結(jié)
以上是生活随笔為你收集整理的AgileEAS.NET SOA 中间件平台.Net Socket通信框架-完整应用例子-在线聊天室系统-代码解析...的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 使用bash编写Linux shell脚
- 下一篇: [转]Install Windows S