Android IM SDK,基于Netty+Okhttp设计思路实现的一款高可定制化的开源库思路实现的一款可定制化的开源库

一、前言

相信现在很多App都会有通讯功能,可能它要求是tcp、udp或者websocket等,每次开发者需要自己再去找个轮子,这样繁琐且耗时,所以本文旨意在打造一个通用的可配置化的IM SDK。文笔有限,如有不妥或者错误之处,恳请在评论、私信或者邮箱里指出,万分感谢。

先上图

image.png
image.png

这里直接模拟两个用户通讯,详情使用读者可以直接移步Github查看NettyIM

二、功能介绍

  1. 支持TCP协议
  2. 支持WebSocket的ws、wss协议
  3. 支持UDP协议
  4. 内置一套默认私有协议实现
  5. 支持断线重连、连接重试
  6. 地址自动切换
  7. 支持消息重发、消息确认机制
  8. 支持心跳机制
  9. tcp协议、udp协议、websocket都支持握手鉴权
  10. 提供Netty消息处理器注册
  11. 支持自定义编解码器
  12. 连接状态、消息状态监听
  13. 支持单个消息设置是否需要确认包、是否失败重发
  14. 支持各种参数配置

三、Netty

什么是Netty?

Netty 是一个利用 Java 的高级网络的能力,隐藏其背后的复杂性而提供一个易于使用的 API 的客户端/服务器框架。
Netty 是一个广泛使用的 Java 网络编程框架(Netty 在 2011 年获得了Duke's Choice Award,见https://www.java.net/dukeschoice/2011)。它活跃和成长于用户社区,像大型公司 Facebook 和 Instagram 以及流行 开源项目如 Infinispan, HornetQ, Vert.x, Apache Cassandra 和 Elasticsearch 等,都利用其强大的对于网络抽象的核心代码。

以上是摘自《Essential Netty In Action》这本书

为什么选择Netty?
Netty是业界最流行的NIO框架之一,它的健壮性、功能、性能、可定制性和可扩展性在同类框架中都是首屈一指的,它已经得到成百上千的商用项目验证,例如Hadoop的RPC框架avro使用Netty作为底层通信框架。很多其它业界主流的RPC框架,也使用Netty来构建高性能的异步通信能力。
通过对Netty的分析,我们将它的优点总结如下:

  • API使用简单,开发门槛低;
  • 功能强大,预置了多种编解码功能,支持多种主流协议;
  • 定制能力强,可以通过ChannelHandler对通信框架进行灵活的扩展;
  • 性能高,通过与其它业界主流的NIO框架对比,Netty的综合性能最优;
  • 成熟、稳定,Netty修复了已经发现的所有JDK NIO BUG,业务开发人员不需要再为NIO的BUG而烦恼;
  • 社区活跃,版本迭代周期短,发现的BUG可以被及时修复,同时,更多的新功能会被加入;
  • 经历了大规模的商业应用考验,质量已经得到验证。在互联网、大数据、网络游戏、企业应用、电信软件等众多行业得到成功商用,证明了它可以完全满足不同行业的商业应用。

正是因为这些优点,Netty逐渐成为Java NIO编程的首选框架。
以上是摘自[《Netty 权威指南》—— 选择Netty的理由
](http://ifeve.com/netty-2-6/)

四、多种协议支持

1、 TCP

简介:

TCP协议是一种在计算机网络中常用的传输层协议,它负责提供可靠的、面向连接的数据传输服务。TCP保证数据的可靠传输,提供流量控制、拥塞控制和错误恢复等功能,以保证网络的可靠性和稳定性。TCP常用于许多应用层协议,如HTTP、FTP、Telnet等。

优点:
  • 可靠性
  1. 应答机制:在TCP协议中,每个数据包都有一个序号和确认号,接收端会对每个数据包进行确认应答。如果发送端在一定时间内没有收到确认应答,就会认为数据包丢失,需要重新发送数据包。
  2. 重传机制:如果某个数据包没有按序到达或者丢失,接收端会要求发送端重新发送该数据包。发送端会定期重传未收到确认应答的数据包,直到接收端确认收到为止。
  3. 滑动窗口机制:TCP协议使用滑动窗口机制进行流量控制。发送端和接收端都有一个窗口大小,用于控制数据包的发送和接收。发送端根据接收端的窗口大小来控制发送速率,接收端根据自己的窗口大小来控制接收速率,以避免网络拥塞和数据丢失。
  4. 拥塞控制机制:TCP协议使用拥塞控制机制来避免网络拥塞。如果网络出现拥塞,TCP会降低发送速率,以避免数据丢失和网络崩溃。
  • 有序性:
  1. 序号机制:在TCP协议中,每个数据包都有一个序号,用于标识数据包在数据流中的位置。发送端会按照序号将数据包进行排序,并将序号添加到数据包的首部。接收端会按照序号将数据包进行排序,以保证数据的有序传输。
  2. 应答机制:在TCP协议中,接收端会对每个数据包进行确认应答。如果发送端在一定时间内没有收到确认应答,就会认为数据包丢失,需要重新发送数据包。这样可以保证数据包按序到达。
  3. 滑动窗口机制:TCP协议使用滑动窗口机制进行流量控制。发送端和接收端都有一个窗口大小,用于控制数据包的发送和接收。发送端根据接收端的窗口大小来控制发送速率,接收端根据自己的窗口大小来控制接收速率,以避免网络拥塞和数据丢失。
  • 面向连接:

TCP协议在传输数据之前需要建立连接,传输完成后需要释放连接,这样可以保证数据的有序传输

缺点:
  1. 传输效率低: TCP协议提供可靠的数据传输,但是为了保证数据的可靠性和完整性,会进行确认和重传等操作,这会增加网络传输的延迟和开销,降低传输效率。
  2. 面向连接: TCP协议需要在传输数据之前建立连接,传输完成后释放连接,这样会增加网络开销和复杂度。
  3. 不适合实时应用: 由于TCP协议对数据传输进行确认和重传等操作,这会增加网络延迟,不适合实时应用,如视频会议、在线游戏等。
  4. 安全性差: TCP协议没有提供加密和身份验证等安全机制,容易受到网络攻击和窃听。
总结:

如果我们通讯对实时要求不高、但对数据可靠性、完整性、有序性有要求,tcp是个不错的选择,但注意这里的可靠、有序性仅代表在传输层是可靠的,并不能保证我们应用层通讯的可靠性,所以在很多采用TCP协议的通讯上都会在应用层上加上确认机制和重传机制或者使用UDP协议加上TCP的一些可靠机制去实现。

2、UDP

简介:

UDP协议是一种用户数据报协议,它是一种简单的、无连接的传输层协议,不提供可靠的数据传输、数据有序性和错误恢复机制。UDP协议直接将应用层的数据报发送到网络层,不需要建立连接和维护状态,因此传输效率高。UDP协议常用于实时应用,如音视频传输、在线游戏等。

优点:
  1. 传输效率高: UDP协议不需要建立连接和维护状态,直接将应用层的数据报发送到网络层,因此传输效率高。
  2. 实时性好: 由于UDP协议不提供可靠性和错误恢复机制,因此能够快速传输数据。
  3. 传输数据较小: UDP协议的数据报头较小,只有8个字节,相比TCP协议的20个字节要小很多,因此在传输数据量较小的情况下,UDP协议的开销相对较小。
  4. 简单: UDP协议是一种简单的协议,实现起来比较容易,适合于一些简单的应用场景
缺点:
  1. 不可靠性: UDP是无连接的,因此它不提供可靠的数据传输。它不会跟踪数据包是否已到达目标,也不会重新发送丢失的数据包。这意味着,如果数据包在传输过程中丢失或损坏,接收方将无法知道,并且无法要求发送方重新发送。
  2. 无序性: UDP不保证数据包的顺序。如果发送方发送的数据包按照A、B、C的顺序发送,但接收方却按照C、A、B的顺序接收,那么接收方将无法正确地重构原始数据。
  3. 低效性: 由于UDP不提供数据包的可靠性和有序性,因此它可能需要发送更多的数据包来确保数据的正确性。这会导致网络拥塞和低效率的数据传输。
  4. 难以控制流量: UDP不提供拥塞控制机制,因此发送方可能会发送过多的数据包,导致网络拥塞。这可能会对网络中的其他流量产生负面影响。
总结:

UDP是一种无连接的传输协议,不保证数据包的可靠性、完整性和顺序。因此,在使用UDP进行数据传输时,需要在应用层自行实现相关机制来检测和纠正错误,例如在每个数据包中添加序列号和校验和等信息,来检测数据包是否有丢失和损坏、添加seq/ack机制,确保数据发送到对端、添加超时重传等机制来实现可靠性,还有个点是数据报大小对传输效率的影响,当IP数据报大于MTU,这个时候发送方IP层就需要分片。把数据报分成若干片,使每一片都小于MTU,而接收方IP层则需要进行数据报的重组。这样就会多做许多事情,可能会导致数据包的丢失或延迟,因为每个分片都是独立传输的,可能会按照不同的路径到达目的地,也可能会在传输过程中丢失一些分片。因此,应该尽量避免 UDP 分片。鉴于 Internet 上的标准 MTU 值为 576 字节,所以最好将 UDP 的数据长度控制在 548 字节(MTU(576) - IPHeader(20) - UDPHeader(8)),但是考虑到IP头部选项和一些没有预料到的其他头部信息,UDP 数据包的最大安全负载应该是 508 字节(MTU(576) - IPHeader(60) - UDPHeader(8))

3、WebSocket

简介:

WebSocket是一种应用层协议,它必须依赖 HTTP 协议进行一次握手 ,握手成功后,数据就直接从 TCP 通道传输。 协议支持文本和二进制数据。客户端和服务器都可以发送和接收这两种类型的数据。此外,WebSocket 还支持 ping 和 pong 消息,用于检测连接是否仍然处于活动状态。

优点:
  1. 兼容性: 更好的支持 Web,并支持 HTTP 代理和中介
  2. 实时性: WebSocket是基于TCP传输层协议 可以在客户端和服务器之间实现实时的双向通信,使得客户端可以即时地接收到服务器端的数据,从而实现实时更新
  3. 安全性:支持使用 SSL/TLS(wss协议) 加密传输数据,这可以确保数据在传输过程中不被窃听或篡改。
  4. 支持扩展: WebSocket 定义了扩展,用户可以扩展协议、实现部分自定义的子协议,例如压缩扩展、加密扩展、认证扩展...等。
缺点:
  1. 不支持所有浏览器: 尽管 WebSockets 已经成为现代浏览器的标准功能,但某些旧版本的浏览器可能不支持它。这可能会导致应用程序无法在所有浏览器中正常工作
  2. 安全性问题: WebSocket 技术需要在客户端和服务器之间建立持久连接,这可能会导致一些安全问题。例如,攻击者可以利用 WebSocket 连接来进行跨站点脚本攻击(XSS)和跨站点请求伪造(CSRF)等攻击
  3. 容易受到网络波动的影响: WebSocket 本身是基于 TCP 协议实现的,因此在网络波动较大的情况下,可能会出现连接断开、传输延迟等问题
总结:

如果是考虑到兼容web,且需要tcp协议的一些特性,且不想自己做一些应用层的事情,例如握手、认证、加密等。websocket是个不错的选择。对于数据格式来说,websocket支持了文本和二进制两种,使用者也可以直接简单的使用。

五、框架设计

1、因为IM和OKhttp具有一定的共性, 所以本库借鉴OKhttp设计思想,来让我们看一下构造一个IMClient可以有多精简。

  • 通用配置
IMClient.Builder builder = new IMClient.Builder()
            .setConnectTimeout(10, TimeUnit.SECONDS) //设置连接超时
            .setResendCount(3)//设置失败重试数
            .setConnectRetryInterval(1000,TimeUnit.MILLISECONDS)//连接尝试间隔
            .setConnectionRetryEnabled(true)//是否连接重试
            .setSendTimeout(6,TimeUnit.SECONDS)//设置发送超时
            .setHeartIntervalBackground(30,TimeUnit.SECONDS)//后台心跳间隔
            .setEventListener(eventListener!=null?eventListener:new DefaultEventListener(userId)) //事件监听,可选
            .setMsgTriggerReconnectEnabled(true)  //如果连接已经断开,消息发送是否触发重连
            .setProtocol(protocol) //哪种协议 IMProtocol.PRIVATE、IMProtocol.WEB_SOCKET、IMProtocol.UDP
            .setOpenLog(true);//是否开启日志
  • TCP协议配置
        //以下支持两种数据传输格式,一种protobuf,一种string格式
 builder.setCodec(codecType == 0?new DefaultTcpProtobufCodec():new DefaultTcpStringCodec())//默认的编解码,开发者可以使用自己的protobuf或者其他格式的编解码
           .setShakeHands(codecType == 0? new DefaultProtobufMessageShakeHandsHandler(getDefaultTcpHands()):new DefaultStringMessageShakeHandsHandler(getDefaultStringHands())) //设置握手认证,可选
           .setHeartBeatMsg(codecType == 0? getDefaultProtobufHeart(): getDefaultStringHeart()) //设置心跳,可选
           .setAckConsumer(codecType == 0?new DefaultProtobufAckConsumer():new DefaultStringAckConsumer()) //设置消息确认机制,如果需要消息回执,必选
           .registerMessageHandler(codecType == 0?new DefaultProtobufMessageReceiveHandler(onMessageArriveListener):new DefaultStringMessageReceiveHandler(onMessageArriveListener)) //消息接收处理器
           .registerMessageHandler(codecType == 0?new DefaultReplyReceiveHandler(onReplyListener):new DefaultStringMessageReplyHandler(onReplyListener)) //消息状态接收处理器
           .registerMessageHandler(codecType == 0?new DefaultProtobufHeartbeatRespHandler():new DefaultStringHeartbeatRespHandler()) //心跳接收处理器
           .setTCPLengthFieldLength(2)//本库拆包采用消息头包含消息长度的协议,装包拆包的长度字段的占用字节数,默认值为2
           .addAddress(new Address(ip,9081,Address.Type.TCP))
           .setMaxFrameLength(65535*100); //设置最大帧长 //私有tcp和websocket生效
  • WebSocket协议配置
builder.setHeartBeatMsg(getDefaultWsHeart())
           .setAckConsumer(new DefaultWSAckConsumer())
           .registerMessageHandler(new DefaultWSMessageReceiveHandler(onMessageArriveListener))
           .registerMessageHandler(new DefaultWSMessageReplyHandler(onReplyListener))
           .registerMessageHandler(new DefaultWsHeartbeatRespHandler())
           .addAddress(new Address(ip,8804,Address.Type.WS))
           .setMaxFrameLength(65535*100)
       //  .addAddress(new Address(ip,8804,Address.Type.WSS))//支持WSS协议,请在scheme带上wss标识
           .addWsHeader("user",userId); //webSocket特有的,可以用来鉴权使用
  • UDP协议配置
 builder.setCodec(new DefaultUdpStringCodec(new InetSocketAddress(ip,8804), CharsetUtil.UTF_8)) //String的编解码,开发者可以设定为自己的格式
            .setShakeHands(new DefaultStringMessageShakeHandsHandler(getDefaultStringHands())) //设置握手认证,可选
            .setHeartBeatMsg(getDefaultStringHeart()) //设置心跳,可选
            .setAckConsumer(new DefaultStringAckConsumer()) //设置确认机制
            .registerMessageHandler(new DefaultStringMessageReceiveHandler(onMessageArriveListener)) //消息接收处理器
            .registerMessageHandler(new DefaultStringMessageReplyHandler(onReplyListener)) //消息状态接收处理器
            .registerMessageHandler(new DefaultStringHeartbeatRespHandler()) //心跳接收处理器
            .addAddress(new Address(ip, 8804, Address.Type.UDP));   
                    

上述很多配置项都是可选项,例如你没有握手的要求、没有心跳设计、没有消息回执,setShakeHands、setHeartBeatMsg、setAckConsumer都可以是不设置的。所有的Default开头的实现,开发者都可以替换成自己的实现类。

整个框架的核心实现在几个内置拦截器中:

   Response getResponseWithInterceptorChain(SubsequentCallback callback) throws IOException, InterruptedException, AuthException, SendTimeoutException {
       // Build a full stack of interceptors.
       List<Interceptor> interceptors = new ArrayList<>();
       if (client.interceptors()!=null&&client.interceptors().size()>0){
           interceptors.addAll(client.interceptors());
       }
       interceptors.add(retryAndFollowUpInterceptor);
       interceptors.add(new BridgeInterceptor(client));
      // interceptors.add(new CacheInterceptor());
       interceptors.add(new ConnectInterceptor(client));
       interceptors.add(new CallServerInterceptor(callback));


       Interceptor.Chain chain = new RealInterceptorChain(
               interceptors, null, null, null, 0, originalRequest,this, eventListener,client.connectTimeout(),
               client.sendTimeout());
       return chain.proceed(originalRequest);
   }

是不是似曾相识的感觉,这里拦截器功能和okhttp雷同,retryAndFollowUpInterceptor进行连接重试、发送重试、地址切换,BridgeInterceptor主要进行数据的装配,ConnectInterceptor是真正的进行连接和一些编解码器等一些配置的地方,CallServerInterceptor进行数据的写和读,,完成这一套拦截器,那么我们整体一个从建立连接到消息发送和接收的大致流程就有了。

五、鉴权设计

鉴权设计是保证通讯的安全性和可靠性的重要手段。一般来说,通讯SDK鉴权设计需要考虑以下几个方面:

  1. 身份认证:通讯SDK需要验证客户端的身份,以确保只有合法的客户端才能使用SDK提供的服务。身份认证可以采用用户名密码、API密钥等方式进行。
  2. 数据加密:通讯SDK需要对通讯过程中的数据进行加密,以保证数据的机密性和完整性。数据加密可以采用对称加密、非对称加密等方式进行。
  3. 防止中间人攻击:通讯SDK需要防止中间人攻击,以保证通讯过程中的数据不被篡改。防中间人攻击可以采用数字证书、SSL/TLS等方式进行
  4. ...等

本库采用了身份认证,即在消息通讯之前要先与服务端进行身份认证。如果需要握手认证,在TCP和UDP协议上开发者需要添加以下配置:

builder.setShakeHands(MessageShakeHandsHandler shakeHandler) //配置握手鉴权机制
public interface MessageShakeHandsHandler<K extends Object,T extends Object>  {


    /**
     * 发送给服务端的握手包
     * @return
     */
    K ShakeHands();

    /**
     * 是否是握手包回应包     * @param msg
     * @return
     */
    boolean isShakeHands(Object msg);

    /**
     * 客户端端自己判断返回的握手认证回应包是否成功
     * @param pack
     * @return
     */
    boolean isShakeHandsOk(T pack) ;


}

接口中的ShakeHands()方法将在连接建立后会调用,发起一个握手认证,当服务端返回消息后会经过isShakeHands(Object msg)判别是否是握手响应包,是则走isShakeHandsOk(T pack),否则消息流转到下一个消息处理器。当isShakeHandsOk(T pack)返回值代表是否成功,true后会建立心跳机制如果有设置的话,fasle会立马断开此连接。

在websocket协议上由于已存在握手过程,所以我们不需要自己去写这个过程,我们可以在websocket协议头上带上我们的header,填上我们的认证信息,然后服务端对header去做判断即可。在websocket协议上开发者需添加以下配置:

builder.addWsHeader(String key, String value) //添加websocket的协议头
总结:

在tcp和websocket下,这里的握手认证包,建议开发者可以设置为用户id+token的组合形式,用户id可以知道这个会话来自哪个客户端,token用来检查连接发起的是否合法。udp协议虽然没有连接,但是依然可以在业务上去做握手认证,如果服务端一直收到一个用户的包但之前没有做握手认证,服务端可以拒绝处理业务或者一些其他处理。

六、心跳设计

TCP协议实现中是有保活机制的,也就是TCP的KeepAlive机制,大概就是如果一个TCP连接在7200秒(2小时)内没有活动,则内核将发送9个keepalive消息,每个消息之间相隔75秒。如果在发送完所有keepalive消息后仍然没有收到响应,则连接将被关闭。这些默认参数显然是不能满足我们的要求的。另外还几个比较重要的原因,例如NAT超时、服务器判断设备是否还在线等原因, 所以我们需要实现自己的一个心跳机制。

  • 设置心跳包和心跳间隔
     //设置心跳包,heartBeatMsg的数据类型一定要是你的编解码器所支持的格式     
     builder.setHeartBeatMsg(Object heartBeatMsg) 
    //设置前台的心跳间隔,这里的间隔是指在无任何消息发送情况下的空闲时间,而不是固定的间隔时间发送心跳包
     builder.setHeartIntervalForeground(int interval, TimeUnit unit)

     builder.setHeartIntervalBackground(long interval, TimeUnit unit)//设置后台的心跳间隔 
  • 设置读空闲和读空闲是否触发重连
   //设置读空闲是否触发重连,如果为true则一段时间内一直如果没有收到服务端返回的任何消息,则触发重新连接,false的话,设置读空闲的配置失效,不触发重连。
     builder.setReaderIdleReconnectEnabled(boolean readerIdleReconnectEnabled)
       builder.setReaderIdleTimeForeground(long interval, TimeUnit unit)//设置前台读空闲时间
       
       setReaderIdleTimeBackground(long interval, TimeUnit unit) //设置后台读空闲时间
总结:

其实心跳机制还有很多事情是可以做的,不仅是保活、判断在线这些,我们还可以利用心跳的RTT(Round-Trip Time,往返时间)去判断网络情况,我们是否要控制消息发送速度或者更改连接、改变心跳间隔。因为一个网络不佳的情况下,我们频繁去做的消息发送,消息的延迟和阻塞是必然的,还占用了没必要的带宽。

七、消息确认设计

TCP协议是个可靠面向流的传输协议,内部既有确认机制且保证数据有序,那我们为什么还要进行ACK机制设计呢,TCP是传输层协议它只能保证传输层的可靠,是端到端的,但并不能保证应用层的可靠性,例如你应用在接收到数据的时候发生异常,这个消息是不是就丢了。再比如在高并发、高负载、高延迟、不稳定网络等情况下,TCP 协议的性能会受到受到很大影响,且整个IM系统也不能保证可靠性,对于一个IM系统来说,可靠的定义至少是不丢消息消息不重复不乱序,这样才算一个比较稳定的IM。

  • 不丢消息

要保证消息的不丢失,可以模拟TCP协议中的ACK机制,我们定义一套业务层的ACK机制,只要当对方回了ACK我们才认为对方已经收到消息。例如有ClientA --> Server--> ClientB,ClientA发送消息给Server时候,需要等待Server返回ACK代表已发送的,而Server转发消息给ClientB,需要等待ClientB返回ACK才代表ClientB收到,例如ClientB不在线Server可以把消息储存起来,等ClientB上线主动推送离线消息或者等ClientB上线主动拉去。如果ACK回执的消息也丢失了呢,这需要去做个消息发送重试机制,如果一定的时间内没有收到ACK则重发此消息,重试一定次数都没有收到ACK则认为消息发送失败。

  • 消息不重复

在上述的重试机制中,就可能出现消息重复的问题,例如一端发送消息给服务端,在等待ACK的超时后,客服端重新发送消息,发送完后才收到ACK,这样其实服务端就收到两条一样的消息。消息去重处理方式比较简单,每个消息带上一个msgId,如果已经接收到同样的msgId则直接回ACK,不需要额外处理。

  • 不乱序

保证消息的有序,可以借鉴TCP协议中使用序列号,服务端为每一个消息都编上一个序号seqid,客户端根据服务端返回的消息回执中的seqid去为消息进行排序。这样就可以根据序列号和确认号来保证数据的有序传输。

在本库中实现了消息确认机制和消息重传机制,而消息的去重和消息的排序需要业务层自己去实现。

1、注册一个消息确认机制

/**
 * 设置ACK机制,如果设置了,在request里有needACK,则必须收到ACK包 不然会回调onFailure
 *
 * @param ackConsumer
 * @return
 */
public Builder setAckConsumer(Consumer ackConsumer) {
    this.ackConsumer = ackConsumer;
    return this;
}
/**
 * 用于特定消息的消费,例子:我发送了一个特别的消息,然后想订阅该特定消息的后续响应
 * @param <T>
 */
public interface Consumer<T extends Object> {

    /**
     * 
     * @param t 接收的消息
     * @param requestTag 消息的唯一标识
     * @return
     */
    boolean Observable(T t,String requestTag);

    /**
     * 处理该消息
     * @param t
     */
    void accept(T t);
}

接口中的Observable(T t,String requestTag)如果返回true,则代表消息已经收到ack,走发送成功回调。如果是fasle则会一直等待一个消息的发送周期,超时、重发、重试,如果一个周期里都没有正确的ack返回则消息发送失败。
accept(T t)会在Observable(T t,String requestTag)方法返回ture的时候回调,去处理该消息。

2、构建一个需要消息确认回执的消息

Request.Builder builder = new Request.Builder().
         setNeedACK(appMessage.getHead().getMsgId()) //此消息需要ack,且此消息的ack包的tag是msgId
        .setSendRetry(true) // 此消息是否超时重发,如果一定时间内没有收到ACK重发此消息
        .setBody(getStringMsgPack(appMessage)) //发送的消息内容
        .build();

Builder setAckConsumer(Consumer ackConsumer) 

3、发送消息

public void sendMsg(Request request, Callback callback){
    imClient.newCall(request).enqueue(callback);
}
总结:

如果你的消息是有序的,那么你可以通过Delay ACk即延迟发送ack,而不是对每个消息都要进行确认,你可以在一段时间内回一个ack(包含序号)或者在传输数据的时候顺带携带一个ack信息,这样可减少带宽,提供利用率。收到一个ack代表ack序号之前的数据都准确无误的收到。

八、写在最后

感谢大家的阅读!也欢迎大家指出问题、提交issue或者评论都可以哈,希望此篇文章对你有帮助。Github地址

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,425评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,058评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,186评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,848评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,249评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,554评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,830评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,536评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,239评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,505评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,004评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,346评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,999评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,060评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,821评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,574评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,480评论 2 267

推荐阅读更多精彩内容