赢博体育

赢博体育

你的当前位置: 赢博体育 > 赢博动态

即时IM电竞APP通讯IM技术领域基础篇

发布日期:2023-02-14 08:53:58 点击次数:

  IM电竞APPudp协议虽然实时性更好,但是如何处理安全可靠的传输并且处理不同客户端之间的消息交互是个难题,实现起来过于复杂. 目前大部分IM架构都不采用UDP来实现.

  核心的TCP长连接,用来实时收发消息,其他资源请求不占用此连接,保证实时性

  IM进行图片/语言/大涂鸦聊天的时候: http能够很方便的处理 断点续传和分片上传等功能.

  IM协议选择原则一般是:易于拓展,方便覆盖各种业务逻辑,同时又比较节约流量。节约流量这一点的需求在移动端IM上尤其重要 !!!

  xmpp: 协议开源,可拓展性强,在各个端(包括服务器)有各种语言的实现,开发者接入方便。但是缺点也是不少:XML表现力弱,有太多冗余信息,流量大,实际使用时有大量天坑。

  MQTT: 协议简单,流量少,但是它并不是一个专门为IM设计的协议,多使用于推送. 需要自己在业务上实现群,好友相关等等(目前公司有用MQTT实现通用IM框架).

  SIP: 多用于VOIP相关的模块,是一种文本协议. sip信令控制比较复杂

  私有协议: 自己实现协议.大部分主流IM APP都是是使用私有协议,一个被良好设计的私有协议一般有如下优点:高效,节约流量(一般使用二进制协议),安全性高,难以破解。xxx项目基本属于私有定制协议参考了蘑菇街开源的TeamTalk, 后期通用IM架构使用MQTT

  网络数据大小 —— 占用带宽,传输效率:虽然对单个用户来说,数据量传输很小,但是对于服务器端要承受众多的高并发数据传输,必须要考虑到数据占用带宽,尽量不要有冗余数据,这样才能够少占用带宽,少占用资源,少网络IO,提高传输效率;

  网络数据安全性 —— 敏感数据的网络安全:对于相关业务的部分数据传输都是敏感数据,所以必须考虑对部分传输数据进行加密(xxx项目目前提供C++的加密库给客户端使用)

  提供序列化和反序列化库的开源协议: pb,Thrift. 扩展相当方便,序列化和反序列化方便(xxx项目目前使用pb)

  文本化协议: xml,json. 序列化,反序列化容易,但是占用体积大(一般http接口采用json格式).

  oracle里面都是模块化,有点类似MVC模式, 代码解耦, 功能解耦.

  Access server和用户紧密连接,维持长连接的同时,还有部分业务

  每条消息到服务端后,都会生成一个全局唯一的msgid, 这个msgid一定都是递增增长的(msgid的生成会有同步机制保证并发时的唯一性)

  这种情况下,就可能会需要有重发机制. 客户端和服务端都可能需要有这种机制.

  消息meta结构里面增加一个字段isResend. 客户端重复发送的时候置位此字段,标识这个是重复的,服务端用来后续判断

  服务端为每个用户缓存一批最近的msgids(所谓的localMsgId),如缓存50条

  因为仅仅靠isResend不能够准备判断,因为可能客户端确实resend,但是服务端确实就是没有收到......

  服务端返回给客户端的数据,有可能客户端没有收到,或者客户端收到了没有回应.

  根据服务器和手机端之间sequence的差异IM电竞APP,可以很轻松的实现增量下发手机端未收取下去的消息

  对于在弱网络环境差的情况,丢包情况发生概率是比较高的,此时经常会出现服务器的回包不能到达手机端的现象。由于手机端只会在确切的收取到消息后才会更新本地的sequence,所以即使服务器的回包丢了,手机端等待超时后重新拿旧的sequence上服务器收取消息,同样是可以正确的收取未下发的消息。

  由于手机端存储的sequence是确认收到消息的最大sequence,所以对于手机端每次到服务器来收取消息也可以认为是对上一次收取消息的确认。一个帐号在多个手机端轮流登录的情况下,只要服务器存储手机端已确认的sequence,那就可以简单的实现已确认下发的消息不会重复下发,不同手机端之间轮流登录不会收到其他手机端已经收取到的消息。

  假如原手机A用户换到手机B登录,并使用Seq_cli = 120上服务器收取消息,由于服务器已经确认sequence = 150的消息已经被手机收取下去了,故不会再返回sequence为[121 - 150]的消息给手机B,而是将sequence为[151 - 200]的消息下发给手机B。

  方式一中,确认机制是等到下一次拉取数据的时候进行确定, 不额外增加请求, 但是淘汰数据不及时.

  心跳功能: 维护TCP长连接,保证长连接稳定性, 对于移动网络, 仅仅只有这个功能吗?

  运营商通过NAT(network adddress translation)来转换移动内网ip和外网ip,从而最终实现连上Internet,其中GGSN(gateway GPRS support Node)模块就是来实现NAT的过程,但是大部分运营商为了减少网关NAT的映射表的负荷,若一个链路有一段时间没有通信就会删除其对应表,造成链路中断,因此运营商采取的是刻意缩短空闲连接的释放超时,来节省信道资源,但是这种刻意释放的行为就可能会导致我们的连接被动断开(xxx项目之前心跳有被运营商断开连接的情况,后面改进了心跳策略,后续还将继续改进心跳策略)

  NAT方案说白了就是将过去每个宽带用户独立分配公网IP的方式改为分配内网IP给每个用户,运营商再对接入的用户统一部署NAT设备,NAT的作用就是将用户网络连接发起的内网IP,以端口连接的形式翻译成公网IP,再对外网资源进行连接。

  从mobile 到GGSN都是一个内网,然后在GGSN上做地址转换NAT/PAT,转换成GGSN公网地址池的地址,所以你的手机在Internet 上呈现的地址就是这个地址池的公网地址

  心跳时间太长,可能会被因为运营商的策略淘汰NAT表中的对应项而被动断开连接

  app进入后台(或者前台关屏)时,先用几次最小心跳维持长链接。然后进入后台自适应心跳计算。这样做的目的是尽量选择用户不活跃的时间段,来减少心跳计算可能产生的消息不及时收取影响。

  精简心跳包,保证一个心跳包大小在10字节之内, 根据APP前后台状态调整心跳包间隔 (主要是安卓)

  掉线后,根据不同的状态需要选择不同的重连间隔。如果是本地网络出错,并不需要定时去重连IM电竞APP,这时只需要监听网络状态,等到网络恢复后重连即可。如果网络变化非常频繁,特别是 App 处在后台运行时,对于重连也可以加上一定的频率控制,在保证一定消息实时性的同时,避免造成过多的电量消耗。

  断线重连的最短间隔时间按单位秒(s)以4、8、16...(最大不超过30)数列执行,以避免频繁的断线重连,从而减轻服务器负担。当服务端收到正确的包时,此策略重置

  有网络但连接失败的情况下IM电竞APP,按单位秒(s)以间隔时间为2、2、4、4、8、8、16、16...(最大不超过120)的数列不断重试

  未读消息索引存在的意义在于保证消息的可靠性以及作为离线用户获取未读消息列表的一个索引结构。

  消息上行以及队列更新未读消息索引是指,hash结构对应的field加1,然后将消息id追加到相应好友的zset结构中。

  接收ack维护未读消息索引则相反,hash结构对应的field减1,然后将消息id从相应好友中的zset结构中删除。

  和在线的流程相同,离线客户端读取了未读消息后也要发送接收ack到业务端,告诉它未读消息已经下发成功,业务端负责维护该用户的未读消息索引。

  和在线流程不同的是,这个接收ack是通过调用messages/lastAccessedId接口来实现的。客户端需要传一个hash结构到服务端,key为通过sessions/recent接口下发的好友id,value为sessions/recent接口的未读消息列表中对应好友的最大一条消息id。

  如果消息标记为offline,则将消息入库,写缓存(只有离线消息才写缓存),更新未读消息索引,然后调用apns进行推送。

  如果消息标记为online,则直接将消息入库即可,因为B已经收到这条消息。

  如果消息标记为redeliver,则将消息写入缓存,然后调用apns进行推送。

  连接层更稳定 - - - 需要有硬性指标来判断才能确定更稳定,因为Access的服务不重,目前也不是瓶颈点.

  真要拆分, 那也不是这么拆分, 是在Oracle上做拆分, 类似微服务的那种概念

  稳定性不是这么体现,原来 connd 的设计,更薄不承担业务,而现在的 access 还是有一些业务逻辑,那么它升级的可能性就比较高。

  access 拆分,目的就是让保持连接的那一层足够薄,薄到怎么改业务,它都不用升级代码(tcp 不会断)。

  减少重启,方便Access服务升级 - - - 不能通过增加一层服务来实现重启升级,需要有其他机制来确保服务端进行升级而不影响TCP长连接上的用户

  拆分出来的connd server 还是有可能会需要重启的, 这时候怎么办呢 ?关键性问题还是没有解决

  加一层服务,是打算通过共享内存的方式,connd 只管理连接。access 更新升级的时候,用户不会掉线。

  增加一个服务,就多了一条链路, 就可能会导致服务链路过长,请求经过更多的服务,会导致服务更加不可用. 因为要保证每个服务的可用性都到99.999%(5个9)是很难的,增加一个服务,就会降低整个服务的可用性.

  架构改进一定要有数据支撑, 要确实起到效果, 要有数据输出才能证明这个改进是有效果的,要不然花了二个月时间做改进,结果没有用,浪费人力和时间,还降低开发效率

  方案: 增加一条信令交互,服务端如果要重启/缩容, 告知连接在此Access上的所有客户端,服务端要升级了,客户端需要重连其他节点

  等确定当前Access节点上的所有客户端都连接到其他节点后, 当前Access节点再进行重启/下线/缩容.

  怎么扩容? 如果需要扩容,则增加新的节点后,通过etcd进行服务发现注册.客户端通过router server请求数据后,拉取到相关节点.

  如果当前3个节点扛不住了,增加2个节点, 这个时候,要能够马上缓解当前3个节点压力,需要怎么做?

  按照之前的方式,客户端重新登录请求router server,然后再进行连接的话,这是不能够马上缓解压力的,因为新增的节点后, 当前压力还是在之前几个节点

  软件防火墙: 软件层面上的如iptable, 设置iptable的防火墙策略

  socket建连速度的频率控制, 不能让别人一直建立socket连接,要不然socket很容易就爆满了,撑不住了

  还有一点就是消息不可靠,它的请求及应答机制也是主要为稳定长连网络环境所设计,对于带宽偏窄及长连不稳定的移动网络并不是特别优化

  mqtt 适合推送,不适合IM, 需要业务层面上额外多做处理, 目前已经开始再用

  xxx项目不用mqtt是历史遗留问题,因为刚开始要迅速开展,迅速搭建架构实现,因此用来蘑菇街的teamtalk.

  除了数据量大, 还要考虑协议的复杂度, 客户端和服务端处理协议的复杂度?

  等待接收消息回应(这个之前没有说到,就是把消息发送给接收方后还需要接收方回应)