一、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。