ESFramework 开发手册(05) -- 联系人
联系人(ESPlus.Application.Contacts 命名空间)是ESFramework 6.0 新增的功能,用于取代之前的 好友与组。
大部分分布式通信系统中,除了客户端与服务器进行通信外,都还会涉及到客户端之间相互通信、以及需要将客户端进行分组的功能,或者是类似这方面的需求。ESFramework对这一常见的任务内置了强大的支持,包括从客户端到服务端、一直到ESPlatform群集。在设计时,我们就考虑到了如何对常见的好友通信与组广播通信进行最大的支持,以期让ESFramework的使用者非常容易的就能够使用这些功能。
在ESFramework中,好友与组成员统称为“联系人”,“联系人”并不仅仅是指用户(某人),而是可以指任何一个运行的客户端实例。只要两个客户端实例之间需要频繁相互通信,那么它们就可以建立好友关系(friend)。如果需要在某些特定的客户端实例间进行广播通信,那么这些实例就可以被划分到同一个组,成为组友(groupmate)。如果是使用ESFramework开发类似IM的系统,那么这两种关系就更明显,它们就类似QQ的好友与群。
一. 非强制性依赖
ESPlus定义了联系人管理器接口(IContactsManager),如果你的应用需要联系人方面的功能,那么只要实现对应的接口,并通过IRapidServerEngine对应的属性注入到框架中即可。如果不需要联系人相关的功能,那么可以完全忽略它的存在。ESFramework 并不会强制性地要求你的应用必须要实现一个与自己的项目需求没有任何关系的接口(比如IContactsManager)。我们将选择的权利交到了你的手中,你可以根据项目的具体需求,决定要实现哪些接口,并注入到ESFramework框架中。
二. 联系人功能服务端详解
1.IContactsManager
ESPlus.Application.Contacts 命名空间用于实现联系人功能,在服务端,定义了ESPlus.Application.Contacts.Server.IContactsManager接口:
/// <summary> /// 联系人管理器接口。 /// </summary> public interface IContactsManager { /// <summary> /// 获取用户的相关联系人。 /// </summary> List<string> GetContacts(string userID); /// <summary> /// 获取目标组的所有成员。 /// </summary> List<string> GetGroupMemberList(string groupID); }
(1)GetContacts 方法用于获取某个用户的所有相关联系人的UserID列表。同很多常见的返回集合的方法设计规则一样,该方法不允许返回null,如果目标用户没有任何联系人,那么请返回元素个数为0的List。
GetContacts 方法在框架内的主要用途:当某个用户上下线时,框架可以自动通知该用户的所有相关联系人其状态变化事件。
(2)GetGroupMemberList 用于获取一个组的所有成员列表。比如,在实现该接口时,我们可以从DB中加载目标组及组成员,然后返回成员列表。
GetGroupMemberList 方法在框架内的主要用途:在发送组内广播消息功能时,框架只有通过GetGroupMembers才知道要将消息转发给哪些用户。
如果要使用联系人功能,请实现IContactsManager接口,并在IRapidServerEngnie初始化之前,注入到其ContactsManager属性。
(3)ESPlus 内置了IContactsManager接口的实现:DefaultContactsManager。DefaultContactsManager假设所有的在线用户都是相关联系人 -- 即其GetContacts方法始终返回所有在线用户列表。DefaultFriendsManager一般用于demo或者测试程序中,使得demo或测试程序的实现更简单。
2. IContactsController
当服务端引擎初始化成功后,还可以通过其暴露的ContactsController属性来对联系人功能进行某些设置和向某个组广播消息:
/// <summary> /// 联系人控制器。 /// </summary> public interface IContactsController { /// <summary> /// 当服务端接收到要转发的广播消息时(BroadcastBlobListened属性决定了是否包括blob广播),触发此事件。 /// 参数:broadcasterID - groupID - broadcastType - broadcastContent - tag /// </summary> event CbGeneric<string, string, int, byte[], string> BroadcastReceived; /// <summary> /// 是否监听来自客户端的Blob广播,默认值为false。如果为true,即使客户端广播的是blob消息,也将触发BroadcastReceived事件(将需要更多的内存)。 /// </summary> bool BroadcastBlobListened { get; set; } /// <summary> /// 用户下线时,是否通知相关联系人。默认值为true。 /// </summary> bool ContactsDisconnectedNotifyEnabled { get; set; } /// <summary> /// 用户上线时,是否通知相关联系人。默认值为true。 /// </summary> bool ContactsConnectedNotifyEnabled { get; set; } /// <summary> /// 联系人上下线通知是否使用单独的线程。默认值为false。 /// </summary> bool UseContactsNotifyThread { get; set; } /// <summary> /// 在组内广播信息。 /// </summary> /// <param name="groupID">接收广播信息的组ID</param> /// <param name="broadcastType">广播信息的类型</param> /// <param name="blobContent">大数据块的内容</param> /// <param name="tag">附带信息</param> /// <param name="action">当通道繁忙时采取的操作</param> void Broadcast(string groupID, int broadcastType, byte[] broadcastContent, string tag, ActionTypeOnChannelIsBusy action); /// <summary> /// 在组内广播大数据块信息。直到数据发送完毕,该方法才会返回。如果担心长时间阻塞调用线程,可考虑异步调用本方法。 /// </summary> /// <param name="groupID">接收广播信息的组ID</param> /// <param name="broadcastType">广播信息的类型</param> /// <param name="blobContent">大数据块的内容</param> /// <param name="tag">附带信息</param> /// <param name="fragmentSize">分片传递时,片段的大小</param> void BroadcastBlob(string groupID, int broadcastType, byte[] blobContent, string tag, int fragmentSize); }
(1)ContactsConnectedNotifyEnabled 用于控制是否开启联系人上线通知;ContactsDisconnectedNotifyEnabled 用于控制是否开启联系人下线通知。
(2)UseContactsNotifyThread用于控制是否开启了联系人通知线程。如果为true,表示所有的上下线通知消息都集中在一个单独的线程中发送。我们建议,在一个需要承载大量用户同时在线的系统中,如果开启了联系人上下线通知功能,请务必将UseContactsNotifyThread也设置为true。
(3)BroadcastReceived 事件可用于在服务端监控所有转发的广播信息。
(4)Broadcast 和 BroadcastBlob 方法使得我们可以在服务端直接向某个组广播信息。
三. 联系人功能客户端详解
1.IContactsOutter
在客户端,定义了ESPlus.Application.Contacts.Passive.IContactsOutter 接口,我们可以通过IRapidPassiveEngine暴露出的ContactsOutter属性,来使用联系人功能。
/// <summary> /// 用于客户端发送与联系人操作相关的信息和广播。 /// </summary> public interface IContactsOutter { /// <summary> /// 当联系人的某设备上线时,触发该事件。参数:UserID - ClientType /// </summary> event CbGeneric<string, ClientType> ContactsDeviceConnected; /// <summary> /// 当联系人的某设备下线时,触发该事件。参数:UserID - ClientType /// </summary> event CbGeneric<string, ClientType> ContactsDeviceDisconnected; /// <summary> /// 当联系人的所有设备都下线时,触发该事件。参数:UserID /// </summary> event CbGeneric<string> ContactsOffline; /// <summary> /// 当接收到某个组内的广播消息(包括大数据块信息)时,触发此事件。 /// 参数:broadcasterID - broadcasterClientType - groupID - broadcastType - broadcastContent - tag。 /// 如果broadcasterID为null,表示是服务端发送的广播。 /// </summary> event CbGeneric<string,ClientType, string, int, byte[], string> BroadcastReceived; /// <summary> /// 获取所有在线的联系人。 /// </summary> List<string> GetAllOnlineContacts(); /// <summary> /// 获取联系人列表。 /// </summary> List<string> GetContacts(); /// <summary> /// 获取组的成员。 /// </summary> Groupmates GetGroupMembers(string groupID); /// <summary> /// 在组内广播信息。 /// </summary> /// <param name="groupID">接收广播信息的组ID</param> /// <param name="broadcastType">广播信息的类型</param> /// <param name="broadcastContent">信息的内容</param> /// <param name="tag">附带信息</param> /// <param name="action">当通道繁忙时采取的操作。</param> void Broadcast(string groupID, int broadcastType, byte[] broadcastContent, string tag, ActionTypeOnChannelIsBusy action); /// <summary> /// 在组内广播大数据块信息。直到数据发送完毕,该方法才会返回。若不想阻塞调用线程,可考虑使用异步广播重载方法。 /// </summary> /// <param name="groupID">接收广播信息的组ID</param> /// <param name="broadcastType">广播信息的类型</param> /// <param name="blobContent">大数据块的内容</param> /// <param name="tag">附带信息</param> /// <param name="fragmentSize">分片传递时,片段的大小</param> void BroadcastBlob(string groupID, int broadcastType, byte[] blobContent, string tag, int fragmentSize); /// <summary> /// 在组内异步广播大数据块信息(当前调用线程立即返回)。 /// </summary> /// <param name="groupID">接收广播信息的组ID</param> /// <param name="broadcastType">广播信息的类型</param> /// <param name="blobContent">大数据块的内容</param> /// <param name="tag">附带信息</param> /// <param name="fragmentSize">分片传递时,片段的大小</param> /// <param name="handler">当发送任务结束时,将回调该处理器</param> /// <param name="handlerTag">将回传给ResultHandler的参数</param> void BroadcastBlob(string groupID, int broadcastType, byte[] blobContent, string tag, int fragmentSize, ResultHandler handler, object handlerTag); }
(1)GetGroupMembers 会返回某个组的所有成员,并将在线成员与不在线成员区分开来。
(2)当用户的某设备上线或下线时,框架会回调IContactsOutter 接口的ContactsDeviceConnected或ContactsDeviceDisconnected事件以通知其所有相关联系人。
(3)当用户所有设备都下线时,将触发ContactsOffline事件以通知其所有相关联系人。
(4)可以通过Broadcast 和 BroadcastBlob 方法向任何一个组发送广播,目标组的每个在线成员都将会通过IContactsOutter 的BroadcastReceived事件来获得广播内容。
2.关注联系人的实时状态
在类似IM的系统中,每个运行的客户端实例,在其运行的整个生命周期中,都需要清楚地知道与其相关每个联系人的实时状态,这个需求可以这样来实现:
(1)当某个客户端登陆成功后,就调用GetContacts方法和GetAllOnlineContacts方法以获取联系人列表和所有的在线联系人列表。这样,就知道了所有联系人的初始状态。
(2)预定IContactsOutter的ContactsDeviceConnected、ContactsDeviceDisconnected和ContactsOffline事件,然后在运行的过程中,当ContactsDeviceConnected、ContactsDeviceDisconnected和ContactsOffline事件触发时,就修改对应联系人或其某设备的状态。
这样就保证我们的客户端可以实时地知道每个相关的联系人或其某设备是否在线。
四. 请注意性能
如果具体的项目中需要频繁地用到联系人特性,那么在实现IContactsManager接口时,要特别注意性能问题。
1. 实现时使用缓存
因为IContactsManager接口的方法会被框架频繁调用,所以,必须想办法提高IContactsManager接口的实现的性能。
如果IContactsManager接口的方法被调用时,每次都需要从外部介质(比如DB、文件等)重新加载相关联系人,那么毫无疑问将严重地降低应用程序的性能。通常的解决方案是,将联系人关系缓存在内存中,以避免重复从外部介质加载。
至于究竟采用何种策略来提升IContactsManager的性能,需要根据你的项目具体情况而作妥当设计。特别是在高性能的分布式通信系统中,这一点是万万不可忽视的。
2. 精简联系人列表
IContactsManager返回的联系人列表,请尽可能的精简,不要包含垃圾数据。
特别是当服务端引擎ContactsController的联系人上下线通知设置为true时,用户的状态变化会自动通知其所有相关联系人。如果用户状态改变频繁,而其相关联系人的数量又很巨大时,这种开销是非常大的。必要时,可以考虑将ContactsConnectedNotifyEnabled或ContactsDisconnectedNotifyEnabled设置为false以关闭状态改变自动通知。
下一篇:ESFramework 开发手册(06) -- Rapid通信引擎
上一篇:ESFramework 开发手册(04) -- 可靠的P2P
-----------------------------------------------------------------------------------------------------------------------------------------------
Q Q:168757008