zoukankan      html  css  js  c++  java
  • 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通信的消息中间件:

    image_thumb2_thumb3_thumb_thumb

    二、多人在线聊天室系统

          在文章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目录之中,有关具体的设置请看下图:

    image_thumb3

    四、解决方案之中的项目说明

         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=..dbChat.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

    密码

    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: }

    七、关于在线用户清单的管理

        系统中如何知道目有那些用户在线,参考以上六节的内容我们可以知道,用户的主键是账号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)的映射清单。

    八、服务端处理登录/上线、下线流程

        客户端在处理用户登录时执行以下流程:

    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状态通告消息,告诉其他在线客户端,某人已经下线。

    九、服务端聊天消息转发流程

         当服务端接收到客户端发来的聊天消息之后,如何转发呢,请参见下图:

    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: }

         关于这一部分的代码请参考:

    十、客户端界面的异步处理

         因为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.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。

  • 相关阅读:
    深度学习 框架比较
    深度学习 Fine-tune 技巧总结
    基于Spark环境对比Python和Scala语言利弊
    【Python系列】HDF5文件介绍
    【Git】CentOS7 通过源码安装Git
    【Git】Git工具常用命令
    登录自动跳转
    offset,scroll,client系列
    谷歌浏览器input中的text 和 button 水平对齐的问题
    git 的基本使用
  • 原文地址:https://www.cnblogs.com/eastjade/p/3385699.html
Copyright © 2011-2022 走看看