MARS做为优秀的跨平台网络层通讯方案开源1年多了,github上收获过万的star,期间较为稳定更新并不频繁。基于内核socket MARS针对弱网络环境下的移动应用作了不少比较实用的优化,详细的优化点和原理在其开源项目的wiki里有不少文档说的比较清楚了Mars wiki。本人恰好参与了多款具备IM功能的应用开发,底层网络通讯集成了MARS,该底层通信模块已经稳定服务于Android/Ios/windows平台上多款产品。网上有关MARS使用的实践经验还比较少见,这里总结一下供你们参考。android
MARS支持长链接的同时也支持短连接,短连接主要映射成有限制的http链接。短链接不是MARS的长处,不在本文涉猎,后面提到的全部链接如无特指均为长链接。git
读完文档就能把MARS用起来仍是得靠运气的,索性把代码走读了一下,恰好能够梳理梳理长链接的数据流。github
上面数据流展现了client端要发送数据的整个过程和涉及到主要API,以Android API为例,MARS提供了涉及数据输出的如下重要APIwindows
//初始化 public static void init(Context _context, Handler _handler) //设置长链接server public static void setLonglinkSvrAddr(final String host, final int[] ports) //client发送任务接口 public static native void startTask(final Task task); //server主动推送回调 void onPush(final int cmdid, final byte[] data); //client发送数据的回调 boolean req2Buf(final int taskID, Object userContext, ByteArrayOutputStream reqBuffer, int[] errCode, int channelSelect); //client收到回应数据的回调 int buf2Resp(final int taskID, Object userContext, final byte[] respBuffer, int[] errCode, int channelSelect); //client发送任务结束的回调 int onTaskEnd(final int taskID, Object userContext, final int errType, final int errCode);
实际过程当中MARS提供的接口就比较复杂了,这边也放一张总结图感觉一下。安全
Mars对外提供的消息收发接口是基于task的,要先理解task的概念。Mars经过任务来描述一次数据的发送、应答和最终结束。服务器
数据传输过程有许多控制参数,任务的定义就是这些控制参数的集合。网络
public int taskID; // 任务惟一标识,会自动生成。 public int channelSelect; // 任务走长连仍是短连,或者两个均可以,可选值见 EShort。ELong EBoth public int cmdID; // 长连的 cgi 命令号,用于标识长连请求的 cgi。长连必填项,至关于短连的 cgi。 public String cgi; // 短连的 URI,短连必填项。 public ArrayList<String> shortLinkHostList; //短连所用 host 或者 ip,若是是走短连的任务,必填项。 //optional public boolean sendOnly; // true 为不须要等待回包,false 为须要等待回包。默认值为 false public boolean needAuthed; // true 为须要登录态才能发送的任务,false 为任何状态下均可以发送的任务,默认值为 true。 public boolean limitFlow; // true 在手机网络状况下会走流量限制,false 不会。默认值为 true。大数据包请置为 false。 public boolean limitFrequency; // true 会走频率限制,false 不会。默认值为 true。 频繁发送相同包内容的 Task 请置为 false。 public int channelStrategy; // channelSelect 为 EBoth 状况下,该值为 ENORMAL 长连存在则走长连,该值为 EFAST,即便长连存在,可是长链接队列里有别的任务的时候,会优先走短链接。默认值为 ENORMAL public boolean networkStatusSensitive; // true 没网络的状况下任务会直接返回失败,不会尝试去走网络,false 即便没网络,也会尝试创建链接。默认为 false。 public int priority; // 任务的优先级,可选值见 ETASK_PRIORITY_XX。 public int retryCount = -1; // 任务重试次数,设为-1,若是任务失败,会走 Mars 的重试逻。辑,设置大于等于0的数,会以此为准,默认值-1。 public int serverProcessCost; //该 Task 等待SVR处理的最长时间,也即预计的SVR处理耗时。 public int totalTimeout; // 该 Task 总的超时时间,设置小于等于零的值,会走 Mars 的超时逻辑,不然以此值为准,默认值为0。 public Object userContext; // 用户变量,可填任何值,Mars 不会更改该变量。 public String reportArg; // 统计上报所用,可忽略。
server端配置多个IP,MARS同时发起多个链接并取其中最快创建的链接使用,其余释放掉。该策略确实能提升client创建链接的成功率和速度,同时也给server端带来了并发的压力,须要根据自身的用户规模和server资源状况谨慎使用。咱们开启了多IP的功能,有几点值得注意。
MARS提供的接口上定义了几种不一样的ip,必定要当心应用。并发
IP | 使用 |
---|---|
Debug IP | 调试IP,线上勿用。 |
NewDns IP | 自开发DNS解析IP。 |
DNS IP | MARS解析出的DNS IP。 |
Backup IP | 保底IP。 |
安全是永恒的话题,长链接创建后的第一件事情就是用户鉴权认证。过程就是client发送一些server端认识的信息来证实本身是合法用户,能够继续通讯。MARS提供了 makesureAuthed/getLongLinkIdentifyCheckBuffer/onLongLinkIdentifyResp 等接口给APP,但该接口是经过回调的方式被动触发发送鉴权信息的。APP主动发起鉴权信息,也一样能够走通用 startTask 接口。app
MARS一直致力于维持链接常在,链接断开会自动重连。惋惜没有提供给APP主动断开链接和重连的API,APP会有场景须要主动断开当前链接,好比上面提到的认证信息更新时或者用户业务登出时。MARS的 redoTasks会有断开链接的效果,咱们开发APP时就比较讨巧的用了这个API来作主动重连的操做。异步
心跳是保持长链接的必需手段,MARS也提供了智能心跳的方案。很遗憾咱们的产品是server端主动发心跳包的方案,恰好跟MARS相反的方向。稍稍改造禁用掉MARS的客户端心跳,走 onPush和 startTask接口一样能够实现心跳。
MARS要求实现longlink_packer.cc.rewriteme中定义的函数来达到自定义APP协议的目的。实际产品中server端和client的通讯协议确定须要开发定制的,这部分的实现几乎是必需的。
能够根据产品本身的特性定制私有的通信协议,这里本人给出一个通信协议的例子
struct MessageFormat { uint32_t magicNum; // magically defined num for error message checking uint32_t messageId; // unqiue message identification uint32_t len; // body length char data[]; // body start byte };
这几乎是最精简的一个通信协议了,尤为比较重要的是messageId。messageId对应于MARS的taskId,用于串联起来IM消息的发送和应答消息对。好比A发送了messageId=1(taskId=1)的“How are you?”到B,B收到后一样以messageId=1(taskId=1)回应“I'm fine"。这样在对A端MARS taskId=1的任务管理全靠这个messageId来标记了。同时有几点注意事项以下:
我这边有一个MARS的二次封装,提供了上面简单的通信协议同时封装了Mars task的管理,有兴趣的同窗能够参考一下,文末有连接地址。
MARS xlog经过磁盘文件内存映射的方式得到高效可靠的日志方案,详细原理见高性能日志模块xlog。实际线上产品使用推荐
MARS有单独的网络监控模块SDT,目前还不能独立使用。网络通讯模块STN里面也有不少网络状况和任务统计的实现,能够稍微改造一下把这些统计项暴漏给APP层。APP就能够搜集统计这些信息汇总到server端,而后运营人员能够比较轻松的了解当前全部客户端的网络表现啦。
顺带提一下MARS的上报长链接状态的接口 reportConnectInfo 一个小小的提示。该回调函数上报的状态存在必定的迷惑性。底层网络长链接状态发生变化时会触发该状态上报接口调用,但真正调用到该接口时上报的网络状态反应的是当时的链接状态。举个例子,链接断开触发上报,上报接口 reportConnectInfo 是在另一个线程里被调用的,真正调用时状态可能已经变为已链接了,这样APP就缺失一个感知链接断开的机会。因此APP不能直接依赖该接口作严格的逻辑处理或状态维护。
总的来讲,MARS是一款出色的移动通讯产品网络层解决方案,若是你须要移动端实时通讯能够尝试在产品中集成MARS。若是你以为接口使用有些复杂,我这边有一个MARS的二次封装,你能够作一个参考或者直接用一下,至少看起来简单了不少。好比这个C++的例子:
//推送监听类 class PushHandler :PushListener { virtual void onPush(const std::string &message) { } }; //应答监听类 class ResponseHandler :ResponseListener { virtual void onResponse(const std::string &message) { printf("response received:%s \n",message.c_str()); } virtual void onError(const int err, const std::string &errMsg) { printf("message send failed:%d \n",err); } virtual void onSuccess() { printf("message send ok \n"); } }; int main(int argc, char* argv[]) { MarsConfig config("39.106.56.27",9001); init(config); PushHandler pushHandler; registerPushListener((PushListener*)&pushHandler); _sleep(2000); ResponseHandler responseHandler; std::string message = "hello"; sendMessage(message.c_str(), message.size(), (ResponseListener*)&responseHandler); _sleep(200000); return 0; }
这个MARS的二次封装我放在了github上,你们能够做为一个了解怎样使用MARS的入口
MarsWrapper