【软件架构】IM架构设计(安卓版)
1. 架构总览
2. 模块介绍
2.1 协议封装与任务流程
2.1.1 协议与任务的封装
- 协议有协议头(协议头因为格式相同,被抽象出来)和协议体组成,协议有两类:请求协议(request)和回复协议(response);
- 任务(action)由请求协议、回复协议和任务回调(callback)组成;
- callback是针对客户端主动请求协议的相应处理,分别是成功回调、超时回调和失败回调;
2.1.2 消息(任务)流程
- 由UI或SYSTEM触发一个消息的生成,随之将其投递到发送队列中,等待发送;
- 消息发送线程会不停的从发送队列中拉取一个消息并发送出去,同时放入超时监测队列;而当网络断开时会等待若干时间并重新循环,若检测该消息在队列等待时间过长,则丢弃该消息并触发相应的失败回调;
- 对于客户端主动请求,在收到服务端给予回复时,调用该消息的成功回调处理相应回复;
- 对于超时线程监测超时的消息,移除超时监测队列,并调用超时回调。
2.2 定时任务
定时任务的实现主要由TimerHelper类与ITimerProcessor两个类,第一个类主要实现了Timer的功能,ITimerProcessor为一个接口,当要建立一个Timer的时候,需要新建立一个类实现ITimerProcessor中得process接口来处理具体的业务。
TimerHelper类的内部封装了系统的Timer及TimerTask来实现,对外提供startTimer以及stopTimer接口。startTimer需要一个布尔类型的参数标志启动定时任务是需要执行一次,还是永久执行。
新生成TimerHelper实例的时候需要制定定时任务的时间以及定时触发处理接口ITimerProcessor的具体实现。
当然在Timer的设计中,并没有完全采用系统提供的Timer类实现,有些Timer是采用线程模拟实现,这个主要是基于Java 中Timer的一些缺陷来考虑。
下面简单介绍下四种Timer
2.2.1 心跳Timer
心跳Timer 是维持客户端与服务端长连一个强有力的保证。网络中接收发送都是使用socket的recv与send进行发送与接收,如果此套接字已经断开,则发送与接收数据都会出现问题,创建心跳机制,就是为了及时检测该套接字是否有效。
所谓心跳就是给服务端发送一个自定义的包,来告诉服务端,自己在线,以确保长连的有效性。
2.2.2 发送超时Timer
在开发网络应用程序的时候,处理业务和通讯流程之间经常会出现矛盾。这种矛盾主要是由于两者之间的不同步造成的。
比如,网络的延迟较大,而实际业务处理的速度则相对比较快,那么如果处理完某一事务然后等待发送成功再处理下一个事务则会大大降低效率。所以建立了一个发送消息队列。这是一个典型的“生产者消费者”模型,业务逻辑将需要发送的数据放到消息队列中,SendPacketMonitor从消息队列中取出数据,并发送出去。
由于使用了消息队列的模式,发送就变成了一种异步操作,业务逻辑将消息放入消息队列后,就可以进行其他的操作,而无法知道该消息是否真正发送成功。因此,在设计消息队列的时候采用了回调机制,业务方在放入消息队列的时候,必须实现onSuccess接口与onTimeOut接口,分别在发送成功与发送超时调用。
发送超时Timer是采用线程来模拟实现的,在SendPacketMonitor类,作为消费者,会不停的从消息队列中取出数据,取出数据后,会判断该消息产生的时刻与当前时间相比较,如果发现时差已经超过系统定义的最大超时时间,则直接调用“生产者”的onTimeOut接口,通知其发送超时。
2.2.3 重连Timer
重连是另一个保证长连的机制,虽然使用了心跳机制来保证长连,但是由于网络环境的复杂性,无法保证在一个连接开启后,就永远保持连接,因此,重连就成了另外一个保证。
重连主要是为了当连接断开的时候,客户端能够自动快速的连接到服务端。为了系统的稳定性,及相应快速,重连Timer采用的是线程模拟Timer实现。
重连的逻辑中,会去检测服务端的心跳包,如果发现长时间没有收到服务端的任何数据包,则认为该socket已经失效,并进行重连。
在重连Timer中,为了防止雪崩效应的出现,在检测到socket失效,并不是立马进行重连,而是让客户端随机Sleep一段时间再去连接服务端,这样就可以使不同的客户端在服务端重启的时候不会同时去连接,从而造成雪崩效应。
2.2.4 好友状态Timer
虽然在实现的逻辑中,服务端在好友状态变化的时候,会主动推送消息给客户端,但是还是设计了好友状态Timer。因为在网络复杂的环境中,有太多的未知因素。
好友状态Timer的基本实现就是每隔一段时间发送一个数据包请求获得好友的状态信息。当收到响应数据包的时候,就会去更新Cache中的好友状态信息。
2.3状态管理
2.3.1 状态管理StateManager
状态管理就是一个多状态的状态机,其中包括Net状态管理(指硬件网络是否可用),Socket状态管理(指与MsgServer的连接是否可用)。
状态管理主要功能就是采集Net状态与Socket状态,提供两个接口notifyNetState与notifySocketState两个接口供Net状态管理与Socket状态管理调用,当接收到状态变化的时候会调用NetDispach进行网络状态变更分发。
2.3.2 Net状态管理NetStateManager
Net状态管理指的是物理网络状态的管理,主要管理当前物理网络是否可用以及进行变化时进行监听,当监听到网络断开或者连上事件的时候,会调用状态管理notifyNetState接口。
在应用启动的时候我们会注册网络变化广播接收
public class ConnectionChangeReceiver extends BroadcastReceiver
Override他的onReceive函数,在onReceive 函数中获取网络连接服务,然后调用NetStateManager的setState接口,通知状态变化。
2.3.3 Socket状态管理
Socket状态管理指的是客户端与MsgServer之间的连接状态。当检测到连接不可用时,会调用该接口的setState接口设置状态。当socket的channelConnected、exceptionCaught与channelDisconnected函数被调用的时候以及在重连出现异常或失败的时候会通知进行状态变更。
2.3.4 状态变更分发
状态变更分发对外提供三个接口register,unregister与dispachMsg接口。外界如果关心网络状态变化事件,可以注册自己的Handler到该类,当网络状态发生变化的时候,会根据注册的Handler进行事件通知。
register接口为提供注册的接口。
unregister接口为取消注册的忌口。
dispachMsg为事件分发接口,当网络状态发生变化的时候,该接口会被调用,通知各个Handler进行处理。
2.4 断线重连
断线重连机制是当IM与MsgServer断开后能够自动连接。
断线重连为一个单独的线程,进行循环:
当检测到NetState为不可用的时候,会随机睡眠1-9秒然后继续检测。同时会检测心跳包,当发现最后一次收到心跳包超过MAX_HEART_BEAT_TIME时间会认为Socket连接不可用而重置SocketState的状态。
当检测到网络可用,Socket状态不可用的时候,就会启动断线重连机制,在进行断线重连之前,会进行连接次数判断,如果为第一次重连,则随机睡眠1秒多,这个机制主要是为了防止服务端出现异常而重启的时候大量的客户端同时连接上来而发生雪崩现象。如果不为第一次重连,则睡眠指定时间,该时间的计算公式如下:
nSleep =(long)Math.pow(2, mnReconnectCount);
if(nSleep >16){
nSleep =16;
}
该计算公式主要是为了防止大量的客户端不停的进行重连从而对服务端造成大量的压力,另外从节省客户端的能耗考虑。
每次进行重连都会将重连次数累加,这个主要是为了防止以后需要对重连次数进行限制。
重连过程如下图:
2.5 登陆
登陆流程主要分为以下几个流程:
1、认证;2、获取MsgServer地址;3、登陆MsgServer。
下面依次介绍。
2.5.1 认证
认证过程主要是对用户合法身份的验证,包括如下两个方面:
从主客获取AppToken。该过程是一个反射调用,IM程序调用主客的获取Token接口获取到主客的AppToken,Dao等信息。
拿从主客获取到得AppToken及Dao信息到IM的验证服务器去换取一个IMToken,该过程为一个Http调用。服务器会对上传的AppToken及Dao信息进行校验,如果校验成功,则会返回一个IMToken,以及LoginServer地址等信息,后期需要拿该Token到MsgServer进行登陆验证。
认证的过程主要在TokenManager中实现,该类中还对Token时效进行了管理,当获取IMToken的时候会先对判断IMToken是否为空,如果为空则去获取AppToken等信息,再去服务端换取IMToken,否则判断Token的时效是否失效效,如果失效则获取AppToken并换取IMToken信息,如果有效,则直接返回IMToken。
2.5.2 获取MsgServer地址
该过程是客户端通过验证时拿到的LoginServer地址建立Socket连接,并发送获取MsgServer请求,LoginServer会返回一个可用的MsgServer的Ip及Port。
2.5.3 登陆MsgServer
当经过2.5.2获取到MsgServer的IP及Port后,会根据给定的IP与Port与MsgServer建立一个Socket连接,当连接建立成功后。会携带获取到得IMToken,用户名等信息发送一个登陆请求包,如果登陆请求验证通过,客户端启动一个Timer与服务端发送心跳包保持长连接。
以下为整个登陆的流程:
2.6 异步实现
异步的封装有两种实现方式:
- 需要更新界面的异步
- 不需要更新界面的异步
两种实现的方式是不同的。同时异步还有一个异步管理类。
2.6.1 TaskTrigger类
TaskTrigger类主要用来注册以及管理各个Task,每生成一个异步实例,都需要通过trigger接口,如果对于需要更新界面的异步,则直接调用AsyncTask的execute接口执行任务,否则将其放入Task的任务队列中,后台会通过process接口调用dotrigger接口执行具体的任务,执行结束后,调用task的callback接口。
2.6.2 需要更新界面的异步
该方式主要集成Android自有的AsyncTask类,对其进行了一个简单的封装,该异步实现方式主要解决一些需要更新UI界面的异步,解决Android中非UI线程不能更新UI的问题。
2.6.3 不需要更新界面的异步
该异步主要提供不需要更新UI的一些异步操作,该实现方式为新开启一个线程执行任务,当任务执行完成之后调用回调函数。
2.7 本地缓存
存消息时,根据缓存中之前一条消息的ID,获得新消息的唯一自增ID,将该消息存入DB。
存消息时依赖三张表,联系人表,主消息表和附加消息表。首先会根据当前收发用户的ID从联系人表中得到两者的唯一联系ID,并设置消息中的联系ID,将该消息存入主消息表和附加消息表。
读消息时,直接从DB中拉取即可,根据参数的不同拉取的消息不同。
可以根据消息ID,拉取某一天消息,也可以根据起止消息ID,拉取指定偏移量和指定条数的消息列表。
存用户信息时,判断该用户信息是否为空或合法,若为空或不合法,直接返回;若合法,则更新 ,缓存中的用户信息,用户信息只保留在缓存中。
取用户信息时,先判断缓存中是否存在该用户信息,若存在,则直接返回该用户信息;否则返回null,由业务端选择是否发起取用户信息的操作。
收发消息时,得到好友ID,判断缓存中得最近联系人ID列表中是否存在,若不存在则添加,否则 忽略;
同样地,在获取最近联系人列表时也如此。
首先获得最近联系人ID列表,然后从缓存中读取必要地信息,组合成最近联系人列表。
另外通过异步监测有新消息来通知UI主线程来更新界面
【软件架构】IM架构设计(安卓版)的更多相关文章
- Web信息架构——设计大型网站(第3版)(久负盛名经典再现,信息架构设计领域基石之作!)
Web信息架构——设计大型网站(第3版)(久负盛名经典再现,信息架构设计领域基石之作!) [美]]Peter Morville(彼得·莫维尔) Louis Rosenfeld(路易斯·罗森菲尔德) ...
- Web信息架构:设计大型网站(第3版) [美]Peter Morville 中文PDF扫描版
新版Web信息架构设计大型网站针对新技术做了全面更新——搭配新颖范例.全新场景及最佳实践信息——但是,其焦点依然放在基础原理上.其结构严谨,图文并貌,内容涵盖了信息架构基本原理和实践应用的方方面面. ...
- 京东云开发者|软件架构可视化及C4模型:架构设计不仅仅是UML
软件系统架构设计的目标不在于设计本身,而在于架构设计意图的传达.图形化有助于在团队间进行高效的信息同步,但不同的图形化方式需要语义一致性和效率间实现平衡.C4模型通过不同的抽象层级来表达系统的静态结构 ...
- 2017(5)软件架构设计,web系统的架构设计,数据库系统,分布式数据库
试题五(共 25 分) 阅读以下关于 Web 系统架构设计的叙述,在答题纸上回答问题1 至问题 3. [说明] 某公司开发的 B2C 商务平台因业务扩展,导致系统访问量不断增大,现有系统访问速度缓慢, ...
- 安卓架构 视频 Android 插件化架构设计
韩梦飞沙 韩亚飞 313134555@qq.com yue31313 han_meng_fei_sha Android 插件化架构设计-Dream老师 自定义SDK =====
- 微信小程序全面实战,架构设计 && 躲坑攻略(小程序入门捷径教程)
最近集中开发了两款微信小程序,分别是好奇心日历(每天一条辞典+一个小投票)和好奇心日报(轻量版),直接上图: Paste_Image.png 本文将结合具体的实战经验,主要介绍微信小程序的基础知识.开 ...
- Slithice 分布式架构设计
项目原因: 参与过各种 分布式项目,有 Socket,Remoting,WCF,当然还有最常用的可以跨平台的 WebService. 分布式编码的时间浪费: 但是,无一例外的,开发分布式程序的开发遵循 ...
- 架构设计:系统间通信(34)——被神化的ESB(上)
1.概述 从本篇文章开始,我们将花一到两篇的篇幅介绍ESB(企业服务总线)技术的基本概念,为读者们理清多个和ESB技术有关名词.我们还将在其中为读者阐述什么情况下应该使用ESB技术.接下来,为了加深读 ...
- APP界面架构设计
作为PM,信息架构和页面流的设计想必烂熟于心,当确定好产品战略层和范围层即为何种目标用户提供何种服务后,就要着手搭建功能架构,将目标功能通过良好的用户体验传递给用户,目的是高效解决用户痛点,从而实现价 ...
- 架构设计:负载均衡层设计方案(2)——Nginx安装
来源:http://blog.csdn.net/yinwenjie(未经允许严禁用于商业用途!) 目录(?)[-] Nginx重要算法介绍 1一致性Hash算法 2轮询与加权轮询 Nginx的安装 1 ...
随机推荐
- python学习笔记2-functools.wraps 装饰器
wraps其实没有实际的大用处, 就是用来解决装饰器导致的原函数名指向的函数 的属性发生变化的问题: 装饰器装饰过函数func, 此时func不是指向真正的func,而是指向装饰器中的装饰过的函数 i ...
- linux crontab命令参数及用法详解--linux自动化定时任务cron
声明:本文转自Linux 安全网,在此基础上加上自己的体会! crontab 命令 如果发现您的系统里没有这个命令,在ubuntu server 中用的是 sudo apt-get install c ...
- 跨进程(同一app不同进程之间通信)——Android自动化测试学习历程
视频地址:http://study.163.com/course/courseLearn.htm?courseId=712011#/learn/video?lessonId=877122&co ...
- Oracle题目
1. 创建一个函数fun_sal,该函数根据部门号获得该部门下所有员工的平均工资Create or replace function fun_sal(deptnos number)return var ...
- linux开机启动
开机过程指的是从打开计算机电源直到LINUX显示用户登录画面的全过程.分析LINUX开机过程也是深入了解LINUX核心工作原理的一个很好的途径. 启动第一步--加载BIOS 当你打开计算机电源,计算机 ...
- AFNetwork 作用和用法详解
转自:http://www.cnblogs.com/mkai/p/5729685.html AFNetworking是一个轻量级的iOS网络通信类库.它建立在NSURLConnection和NSOpe ...
- Python.resource-for-python-from-internet
1. pyvideo Python related video indexed so you can find it. http://pyvideo.org/ 2. 6 Useful Python L ...
- C#键盘钩子 鼠标钩子
最新对C#模拟键盘按键,鼠标操作产生了兴趣.特从网上收集了一些常用的API用来调用键盘,鼠标操作. class Win32API { #region DLL导入 /// <summary> ...
- php 中文正则
utf8编码中文 preg_match("/^[\x{4e00}-\x{9fa5}]+$/u") 而不是 "/^[\x4e00-\x9fa5]+$/u"
- Spring的定时任务配置(转)
spring的定时任务配置分为三个步骤: 1.定义任务 2.任务执行策略配置 3.启动任务 1.定义任务 <!--要定时执行的方法--> <bean id="testTas ...