目录
1、背景
2、设计流程图
1.长连通道的建立与初始化
2.网络长连接通道的稳定性如何保证
(1) NAT超时断开长连通道的规避策略
(2) DHCP 续租断开长连通道的规避策略
(3) 重试
3、pushSDK与pushServer建立连接过程
3.1协议设计
4、通信格式
4.1 监控体系
目前iOS已经有原生APNs推送服务,完全有苹果公司维护。对于我们来说,里面任何的操作就像个黑盒看不见摸不着,很多条件是无法控制的,比如高峰期推送消息不及时,到达率不能给出相关数据等。
思考主要问题:
长连通道的建立见下面。
长连建立成功后,第一次启动需要向服务端注册,获取服务端用于发push 推送的识别码,即push Token。
由于网络运营商都有相应的NAT超时策略,如果客户端和服务端建立的连接在一定时间内没有数据交换,则会被判定为已废弃的连接,并将其关闭。为了避免这种情况影响Push 长连的存活,注册成功后,为了让TCP长连保持连接,需要使用闹钟模块Alarm Service定期向后台发送心跳。但如果发送太频繁,将会导致高额耗电量,所以心跳间隔时间应该由后端控制。
不同的网络运营商对于NAT超时的间隔有不同的设定,后端需要根据不同的网络类型,下发不同的心跳间隔,在最大程度上保证TCP长连存活的情况下,避免频繁心跳触发的高额耗电。
以下是不同运营商对于NAT超时时间的策略。
地区/网络
NAT超时时间
中国移动3G和2G
5分钟
中国联通2G
5分钟
中国电信3G
大于28分钟
美国3G
大于28分钟
台湾3G
大于28分钟
由于安卓系统对DHCP的处理存在漏洞—— DHCP租期到了不会主动续约并且会继续使用过期IP。
这个问题导致的问题表象是,在超过租期的某个时间点(没有规律)会导致IP过期,老的TCP连接不能正常收发数据。并且系统没有网络变化事件,只有等应用判断主动建立新的TCP连接才引起安卓设备重新向DHCP Server申请IP租用。
为了避免这一问题,未到租期的一半时间,安卓设备需要重新向DHCP Server申请IP租用。
网络环境不稳定,长连极易受到影响被断开,在断开后希望能及时建立连接保证通道的稳定性,因此设计一套重试机制是很重要的。有个问题要思考,我们SDK不可能无数次地请求建连,服务器会崩掉,我们会考虑时间次数和时间间隔-斐波拉契数列作为重试时间间隔,这样能有效规避无限建立连接的过程。最后如果还是不行就换个IP进行连接。
参考:https://caofengbin.github.io/2018/03/16/dhcp-and-nat/
连接管理是整个SDK的核心部分
SDK与服务器之间保持长连接,针对于Push业务的特性,我们只需要以单线程,单tcp连接的形式即可满足我们的业务需求。
同时这种设计有助于降低复杂度,提高app稳定性(避免资源竞争,降低服务器承载压力)。
主要包含以下几个模块:
1. TCP连接的选择,建立,维持
2. 通讯协议的定义
3. 通讯安全
4. 消息去重等
① 连接的建立
1、使用预埋或者上次下发的ip池中依次进行连接,直至连接成功
2、连接成功后后服务器会下发新的ip
3. SDK针对IP List中所有ip建立长连通道。每条通道进行 b 步骤(跑马)。
4. 对比所有RTT,保留耗时最小的通道,断开其他通道。
5. SDK发起注册或登录。(鉴权服务)
这样做的意义是有利于服务器的扩容,不依赖于SDK发版
② 重连机制(维持)
受网络环境的不稳定,SDK建立的长连接极易断开,因此我们必须设计一套重连机制。在断开后及时重连,保证通道的可用性。同时,受限于接入点接入能力的限制,SDK不可能不断向接入点申请连接,这样有可能造成服务雪崩。针对这种情况,我们针对连接异常断开的场景,重连时使用斐波那契数列作为时间间隔,这样就有效避免了重连的无线重试。IPV4 因为Address数量有限,所以运营商分配给手机终端的ip一般是内网ip,手机要连接Internet,就需要通过运营商的网关做一个网络地址转换(Network Address Translation,NAT,GGSN 模块)。简单的说运营商的网关需要维护一个外网IP、端口到内网 IP、端口的对应关系,以确保内网的手机可以跟 Internet 的服务器通讯。心跳时间默认2分钟,sdk会随登录接口返回配置项,SDK根据配置调整心跳时长,直至最佳时长。
③ 使用AES对所有通讯内容进行加密处理,保障通讯安全
④ 使用二进制通讯协议
长连下发模块(loadBalance):长连接IP下发模块用于将长连接服务器的外网ip(即负载均衡器的ip)下发给客户端,当有ip删减的时候,用户端需要实时感知到。
连接管理:连接管理模块用于海量连接的管理,维持长连接的稳定性。
保活的实现:1)客户端使用一个定时器,定时发送心跳ping,如果每次都能收到服务端的心跳响应pong,说明当前连接是健康的。2)服务端也使用一个定时器,定期检测是否有数据读写,如果持续有数据读写,说明连接是健康的,如果隔一段时间没有收到客户端的ping,那就说明这条连接已经假死,需要立即释放资源。3)客户端的心跳发送间隔如果是T秒,一般来说,长连服务端的定时器的间隔应该大于2T秒,保证客户端的连续两次心跳能收到一条。
pushServer通信模块:主要就是管理开放给push server对应的tcp服务器端口,收发push server过来的数据,透传给sdk模块。
通信协议的设计
与客户端之间的协议:
其中version:协议版本号
contentLength:数据包总长度(不包括version字段)
encrypt:标识该请求是否加密,1为加密,0为非加密
command:具体命令字,有如下选项
具体的command字段
action
request
response
request 发起端
push
0
connection server
transfer data
1
push sdk
loadBalance
2
12
push sdk
注:LoadBalance请求不需要带任何数据,响应格式为:data字段域为connection-server ip列表, 十六进制表示
Example: 若返回两个ip,26.27.28.29和42.43.44.45则data字段域的值为 1A1B1C1D2A2B2C2D
unEncryptContentLength:非加密部分的长度
unEncryptContent:主要存储加密过程中的密钥相关的信息,比如首次加密的时候客户端需要传public key,通信的时候需要对称密钥
encryptContent:数据包的主体
与pushServer之间的协议
其中version:协议版本号
connectionId:表示本机长的唯一一个用户
length:数据包的长度(command和data字段)
command:命令字
具体的command 字段含义
action
request
response
request 发起端
push
0
push server
transfer data
1
connection server
auth
2
push server
disconnect
9
19
push server
data:与push server之间由于是内网通信,不需要加解密
长连通道通过Connection形式封装,每个Connection表示一个长连通道。每个Connection包含Read,Write流。PSTunnelProtocol类负责对读写流的数据解析。PSPushProtocol类(继承自)负责组装数据。写操作时直将封装好的数据调用父类写方法发送。读操作时将解析后数据抛给Connection。
SDK与服务器之间通信格式为:
在发送push消息后,出现网络异常或者服务器异常等可重试的异常,为了保证push的容错性,可将这部分push提交到重试队列,在指定时间出队,重新发送。
为了及时监控消息的到达率已经SDK的运行情况,我们将SDK的监控体系分为四个部分
1、外部监控(主要由SDK提供接口,当Push触达App时,由App调用HTTP接口进行上报)
主要有Push的展示、点击埋点。
2、内部监控(针对所有长连接到达SDK的消息,SDK会立即以ACK的形式给Push Server一个回复,告知到达,以便Server执行策略)
3、代码级监控 (在函数级埋点,并将日志存储在App本地,当需要时进行回捞)