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。

  • 相关阅读:
    Java 简单算法--打印乘法口诀(只使用一次循环)
    Java简单算法--求100以内素数
    ubuntu 16.04 chrome flash player 过期
    java 网络API访问 web 站点
    java scoket (UDP通信模型)简易聊天室
    leetcode1105 Filling Bookcase Shelves
    leetcode1140 Stone Game II
    leetcode1186 Maximum Subarray Sum with One Deletion
    leetcode31 Next Permutation
    leetcode834 Sum of Distances in Tree
  • 原文地址:https://www.cnblogs.com/eastjade/p/3385699.html
Copyright © 2011-2022 走看看