iOS - XMPP 的使用

1、XMPP

  • XMPP 是一个基于 Socket 通信的即时通讯的协议,它规范了即时通信在网络上数据的传输格式,比如登录,获取好友列表等等的格式。XMPP 在网络传输的数据是 XML 格式。
  • 开发架构:

  • iOS 框架:XMPPFramework
  • 服务器:Openfire
  • 数据库:MySQL

2、XMPPFramework 框架简介

2.1 XMPPFramework 简介

  • XMPPFramework 是一个 OS X/iOS 平台的开源项目,使用 Objective-C 实现了 XMPP 协议(RFC-3920),同时还提供了用于读写 XML 的工具,大大简化了基于 XMPP 的通信应用的开发。

2.2 XMPPFramework 结构

  • 1、XMPPFramework 的目录结构如下:

    目录 说明
    Authentication 授权,与授权验证相关,如用户名密码等
    Categories 分类,XMPP 自己写的一些分类,尤其是 NSXMLElement+XMPP 扩展是必备的
    Core 核心,这里是 XMPP 的核心文件目录,我们最主要的目光还是要放在这个目录上
    Extensions 扩展,XMPP 的扩展模块,用于扩展各种协议和各种独立的功能,其下每个子目录都是对应的一个单独的子功能
    Utilities 工具,都是辅助类,我们开发者不用关心这里
    Vendor 第三方库,这个目录是 XMPP 所引用的第三方类库,我们也不用关心这里
    • 虽然这里有很多个目录,但是我们在开发中基本只关心 Core 和 Extensions 这两个目录下的类。
    • 在 Core 中:
      目录 说明
      XMPPElement 是一个基类,延展出三个子类
      XMPPIQ 请求,用户登录,用户注册,添加好友等
      XMPPMessage 消息,用来发各种消息等
      XMPPPresence 展现,用户上线下线提示等
      XMPPStream 流,非常常用,大部分类的加载都在写在流的懒加载里
    • 在 Extensions 中:
      目录 说明
      CoreDataStorage coreData 存储
      Reconnect 重新连接
      Roster 好友管理
      SystemInputActivityMonitor 系统输入的活动监控
    • 在 Vendor 中:
      文件夹 说明
      CocoaAsyncSocket 异步 Socket
      CocoaLumberjack ⽇志相关
      KissXML XML 解析
  • 2、XMPPFramework 中常用的类:
    说明
    XMPPStream XMPP 基础服务类
    XMPPRoster 好友列表类
    XMPPUserCoreDataStorageObject 管理用户的类
    XMPPRosterCoreDataStorage 好友列表(用户账号)在 core data 中的操作类
    XMPPvCardCoreDataStorage 好友名片(昵称,签名,性别,年龄等信息)在 core data 中的操作类
    XMPPvCardTemp 好友名片实体类,从数据库里取出来的都是它
    xmppvCardAvatarModule 好友头像
    XMPPReconnect 如果失去连接,自动重连
    XMPPRoom 提供多用户聊天支持
    XMPPPubSub 发布订阅
    XMPPMessageArchiving 其中有数据表
    XMPPMessageArchiving_Message_CoreDataObject 取出当前信息的类
  • 3、XMPPFramework 几个常用到的扩展协议:
    协议 协议简介
    XEP-0006 使能与网络上某个 XMPP 实体间的通信
    XEP-0009 在两个 XMPP 实体间传输 XML-RPC 编码请求和响应
    XEP-0012 最后的活动(判断上线,离开断开)
    XEP-0045 多人聊天相关协议
    XEP-0054 名片格式的标准文档,个人信息设置
    XEP-0060 提供通用公共订阅功能
    XEP-0065 两个 XMPP 用户之间建立一个带外流,主要用于文件传输,sockets5 字节流
    XEP-0066 二进制数据传输(特殊信息的发送)
    XEP-0082 日期和时间信息的标准化表示
    XEP-0085 聊天对话中通知用户状态,聊天状态通知
    XEP-0100 表述了 XMPP 客户端与提供传统的 IM 服务的代理网关之间交换的最佳实践
    XEP-0115 广播和动态发现客户端、设备、或一般实体能力
    XEP-0136 为服务端备份和检索 XMPP 消息定义机制和偏好设置,聊天记录归档
    XEP-0153 用于交换用户头像,基于名片的头像
    XEP-0184 消息送达回执协议
    XEP-0199 XMPP ping 协议(用来 ping 服务器和 ping 自己)
    XEP-0202 用于交换实体间的本地时间信息
    XEP-0203 用于延迟发送
    XEP-0224 引起另一个用户注意的协议
    XEP-0335 JSON 容器(可能以后某些信息传输将用 JSON 格式)
    • XMPP 的扩展协议 Jingle 使得其支持语音和视频,目前 iOS 尚不支持。

      • iOS 发送附件(图片,语音,文档…)时比较麻烦,XMPP 框架没有提供发送附件的功能,需要自己实现。
      • iOS 发送附件实现方法:
        • 1、将获取到的图片/音频文件通过 base64 加密,直接通过 xmpp 的消息体发送过去,然后解码。
        • 2、通过 http 请求的方式将图片/音频文件上传到服务器,然后将图片/音频文件的下载地址通过 xmpp 消息体发送过去,另外一个客户端下载。
        • 音频文件建议转码为 amr,这种格式的音频文件比较小。

2.3 XMPPJID 类

  • 登录需要到账号,而所谓的账号其实就是用户唯一标识符(JID),在 XMPP 中使用 XMPPJID 类来表示。
  • JID 一般由三部分构成:用户名,域名和资源名,格式为 user@domain/resource,例如:test@example.com/Anthony。对应于 XMPPJID 类中的三个属性 user、domain、resource。
  • 如果没有设置主机名(HOST),则使用 JID 的域名(domain)作为主机名,而端口号是可选的,默认是 5222,一般也没有必要改动它。

2.4 XMPPStream 类

  • 我们要与服务器连接,就必须通过 XMPPStream 类了,它提供了很多的 API 和属性设置,通过 socket 来实现的。Verdor 目录包含了 CocoaAsyncSocket 这个非常有名的 socket 编程库。XMPPStream 类还遵守并实现了 GCDAsyncSocketDelegate 代理,用于客户端与服务器交互。

        @interface XMPPStream : NSObject <GCDAsyncSocketDelegate>
  • 当我们创建 XMPPStream 对象后,我们需要设置代理,才能回调我们的代理方法,这个是支持 multicast delegate,也就是说对于一个 XMPPStream 对象,可以设置多个代理对象,其中协议是XMPPStreamDelegate。
        - (void)addDelegate:(id)delegate delegateQueue:(dispatch_queue_t)delegateQueue;
  • 而当我们不希望某个 XMPPStream 对象继续接收到代理回调时,我们通过这样的方式来移除代理。
        - (void)removeDelegate:(id)delegate delegateQueue:(dispatch_queue_t)delegateQueue;
        - (void)removeDelegate:(id)delegate;
  • 接下来,我们要设置主机和端口,通过设置这两个属性。
        // 主机,可选设置,如果没有设置默认会使用 domain
        @property (readwrite, copy) NSString *hostName;
    
        // 端口号,默认为 5222
        @property (readwrite, assign) UInt16 hostPort;
  • XMPPStream 有 XMPPJID 类对象作为属性,标识用户,因为我们后续很多操作都需要到 myJID。
        @property (readwrite, copy) XMPPJID *myJID;
  • 而管理用户在线状态的就交由 XMPPPresence 类了,它同样被作为 XMPPStream 的属性,组合到 XMPPStream 中,后续很多关于用户的操作是需要到处理用户状态的。
        @property (strong, readonly) XMPPPresence *myPresence;

2.5 XMPPStreamDelegate

  • 这个协议是非常关键的,我们的很多主要操作都集中在这个协议的代理回调上。它分为好几种类型的代理 API,比如授权的、注册的、安全的等。

        @protocol XMPPStreamDelegate
        @optional
    
        // 将要与服务器连接
        - (void)xmppStreamWillConnect:(XMPPStream *)sender;
    
        // 已经与服务器连接,
        // 当 TCP Socket 已经与远程主机连接上时会回调此方法
        // 若 App 要求在后台运行,需要设置 XMPPStream's enableBackgroundingOnSocket 属性
        - (void)xmppStream:(XMPPStream *)sender socketDidConnect:(GCDAsyncSocket *)socket;
    
        // 当 TCP 与服务器建立连接后会回调此方法
        - (void)xmppStreamDidStartNegotiation:(XMPPStream *)sender;
    
        // TLS 传输层协议在将要验证安全设置时会回调
        // 参数 settings 会被传到 startTLS,此方法可以不实现的
        // 若服务端使用自签名的证书,需要在 settings 中添加 GCDAsyncSocketManuallyEvaluateTrust = YES
        - (void)xmppStream:(XMPPStream *)sender willSecureWithSettings:(NSMutableDictionary *)settings;
    
        // 上面的方法执行后,下一步就会执行这个代理回调
        // 用于在 TCP 握手时手动验证是否受信任
        - (void)xmppStream:(XMPPStream *)sender didReceiveTrust:(SecTrustRef)trust
                                              completionHandler:(void (^)(BOOL shouldTrustPeer))completionHandler;
    
        // 当 stream 通过了 SSL/TLS 的安全验证时,会回调此代理方法
        - (void)xmppStreamDidSecure:(XMPPStream *)sender;
    
        // 当 XML 流已经完全打开时(也就是与服务器的连接完成时)会回调此代理方法。此时可以安全地与服务器通信了
        - (void)xmppStreamDidConnect:(XMPPStream *)sender;
    
        // 注册新用户成功时的回调
        - (void)xmppStreamDidRegister:(XMPPStream *)sender;
    
        // 注册新用户失败时的回调
        - (void)xmppStream:(XMPPStream *)sender didNotRegister:(NSXMLElement *)error;
    
        // 授权通过时的回调,也就是登录成功的回调
        - (void)xmppStreamDidAuthenticate:(XMPPStream *)sender;
    
        // 授权失败时的回调,也就是登录失败时的回调
        - (void)xmppStream:(XMPPStream *)sender didNotAuthenticate:(NSXMLElement *)error;
    
        // 将要绑定 JID resource 时的回调,这是授权程序的标准部分
        // 当验证 JID 用户名通过时,下一步就验证 resource。若使用标准绑定处理,return nil 或者不要实现此方法
        - (id <XMPPCustomBinding>)xmppStreamWillBind:(XMPPStream *)sender;
    
        // 如果服务器出现 resouce 冲突而导致不允许 resource 选择时,会回调此代理方法
        // 返回指定的 resource 或者返回 nil 让服务器自动帮助我们来选择。一般不用实现它
        - (NSString *)xmppStream:(XMPPStream *)sender alternativeResourceForConflictingResource:(NSString *)conflictingResource;
    
        // 将要接收 IQ(消息查询)时的回调
        - (XMPPIQ *)xmppStream:(XMPPStream *)sender willReceiveIQ:(XMPPIQ *)iq;
    
        // 将要接收到消息时的回调
        - (XMPPMessage *)xmppStream:(XMPPStream *)sender willReceiveMessage:(XMPPMessage *)message;
    
        // 将要接收到用户在线状态时的回调
        - (XMPPPresence *)xmppStream:(XMPPStream *)sender willReceivePresence:(XMPPPresence *)presence;
    
        // 通过实现此代理方法,可以知道被过滤的原因,有一定的帮助
        // 当 xmppStream:willReceiveX: (也就是前面这三个 API 回调后),过滤了 stanza,会回调此代理方法
        - (void)xmppStreamDidFilterStanza:(XMPPStream *)sender;
    
        // 在接收了 IQ(消息查询后)会回调此代理方法
        - (BOOL)xmppStream:(XMPPStream *)sender didReceiveIQ:(XMPPIQ *)iq;
    
        // 在接收了消息后会回调此代理方法
        - (void)xmppStream:(XMPPStream *)sender didReceiveMessage:(XMPPMessage *)message;
    
        // 在接收了用户在线状态消息后会回调此代理方法
        - (void)xmppStream:(XMPPStream *)sender didReceivePresence:(XMPPPresence *)presence;
    
        // 在接收 IQ/messag、presence 出错时,会回调此代理方法
        - (void)xmppStream:(XMPPStream *)sender didReceiveError:(NSXMLElement *)error;
    
        // 将要发送 IQ(消息查询时)时会回调此代理方法
        - (XMPPIQ *)xmppStream:(XMPPStream *)sender willSendIQ:(XMPPIQ *)iq;
    
        // 在将要发送消息时,会回调此代理方法
        - (XMPPMessage *)xmppStream:(XMPPStream *)sender willSendMessage:(XMPPMessage *)message;
    
        // 在将要发送用户在线状态信息时,会回调此方法
        - (XMPPPresence *)xmppStream:(XMPPStream *)sender willSendPresence:(XMPPPresence *)presence;
    
        // 在发送 IQ(消息查询)成功后会回调此代理方法
        - (void)xmppStream:(XMPPStream *)sender didSendIQ:(XMPPIQ *)iq;
    
        // 在发送消息成功后,会回调此代理方法
        - (void)xmppStream:(XMPPStream *)sender didSendMessage:(XMPPMessage *)message;
    
        // 在发送用户在线状态信息成功后,会回调此方法
        - (void)xmppStream:(XMPPStream *)sender didSendPresence:(XMPPPresence *)presence;
    
        // 在发送 IQ(消息查询)失败后会回调此代理方法
        - (void)xmppStream:(XMPPStream *)sender didFailToSendIQ:(XMPPIQ *)iq error:(NSError *)error;
    
        // 在发送消息失败后,会回调此代理方法
        - (void)xmppStream:(XMPPStream *)sender didFailToSendMessage:(XMPPMessage *)message error:(NSError *)error;
    
        // 在发送用户在线状态失败信息后,会回调此方法
        - (void)xmppStream:(XMPPStream *)sender didFailToSendPresence:(XMPPPresence *)presence error:(NSError *)error;
    
        // 当修改了 JID 信息时,会回调此代理方法
        - (void)xmppStreamDidChangeMyJID:(XMPPStream *)xmppStream;
    
        // 当 Stream 被告知与服务器断开连接时会回调此代理方法
        - (void)xmppStreamWasToldToDisconnect:(XMPPStream *)sender;
    
        // 当发送了 </stream:stream> 节点时,会回调此代理方法
        - (void)xmppStreamDidSendClosingStreamStanza:(XMPPStream *)sender;
    
        // 连接超时时会回调此代理方法
        - (void)xmppStreamConnectDidTimeout:(XMPPStream *)sender;
    
        // 当与服务器断开连接后,会回调此代理方法
        - (void)xmppStreamDidDisconnect:(XMPPStream *)sender withError:(NSError *)error;
    
        // P2P 类型相关的
        - (void)xmppStream:(XMPPStream *)sender didReceiveP2PFeatures:(NSXMLElement *)streamFeatures;
        - (void)xmppStream:(XMPPStream *)sender willSendP2PFeatures:(NSXMLElement *)streamFeatures;
    
        - (void)xmppStream:(XMPPStream *)sender didRegisterModule:(id)module;
        - (void)xmppStream:(XMPPStream *)sender willUnregisterModule:(id)module;
    
        // 当发送非 XMPP 元素节点时,会回调此代理方法
        // 也就是说,如果发送的 element 不是 <iq>, <message> 或者 <presence>,那么就会回调此代理方法
        - (void)xmppStream:(XMPPStream *)sender didSendCustomElement:(NSXMLElement *)element;
    
        // 当接收到非 XMPP 元素节点时,会回调此代理方法
        // 也就是说,如果接收的element不是 <iq>, <message> 或者 <presence>,那么就会回调此代理方法
        - (void)xmppStream:(XMPPStream *)sender didReceiveCustomElement:(NSXMLElement *)element;

2.6 XMPPIQ 类

  • 消息查询(IQ)就是通过此类来处理的了。XMPP 给我们提供了 IQ 方便创建的类,用于快速生成 XML 数据。

        @interface XMPPIQ : XMPPElement
    
        // 生成 IQ
    
        + (XMPPIQ *)iq;
        + (XMPPIQ *)iqWithType:(NSString *)type;
        + (XMPPIQ *)iqWithType:(NSString *)type to:(XMPPJID *)jid;
        + (XMPPIQ *)iqWithType:(NSString *)type to:(XMPPJID *)jid elementID:(NSString *)eid;
        + (XMPPIQ *)iqWithType:(NSString *)type to:(XMPPJID *)jid elementID:(NSString *)eid child:(NSXMLElement *)childElement;
        + (XMPPIQ *)iqWithType:(NSString *)type elementID:(NSString *)eid;
        + (XMPPIQ *)iqWithType:(NSString *)type elementID:(NSString *)eid child:(NSXMLElement *)childElement;
        + (XMPPIQ *)iqWithType:(NSString *)type child:(NSXMLElement *)childElement;
    
        - (id)init;
        - (id)initWithType:(NSString *)type;
        - (id)initWithType:(NSString *)type to:(XMPPJID *)jid;
        - (id)initWithType:(NSString *)type to:(XMPPJID *)jid elementID:(NSString *)eid;
        - (id)initWithType:(NSString *)type to:(XMPPJID *)jid elementID:(NSString *)eid child:(NSXMLElement *)childElement;
        - (id)initWithType:(NSString *)type elementID:(NSString *)eid;
        - (id)initWithType:(NSString *)type elementID:(NSString *)eid child:(NSXMLElement *)childElement;
        - (id)initWithType:(NSString *)type child:(NSXMLElement *)childElement;
    
        // IQ 类型
        - (NSString *)type;
    
        // 判断 type 类型
    
        - (BOOL)isGetIQ;
        - (BOOL)isSetIQ;
        - (BOOL)isResultIQ;
        - (BOOL)isErrorIQ;
    
        // 当 type 为 get 或者 set 时,这个 API 是很有用的,用于指定是否要求有响应
        - (BOOL)requiresResponse;
    
        - (NSXMLElement *)childElement;
        - (NSXMLElement *)childErrorElement;
    
        @end
  • IQ 是一种请求/响应机制,从一个实体发送请求,另外一个实体接受请求并进行响应。例如,Client 在 stream 的上下文中插入一个元素,向 Server 请求得到自己的好友列表,Server 返回一个,里面是请求的结果。
  • <type></type> 有以下类别(可选设置如:<type>get</type>
    type 说明
    get 获取当前域值。类似于 http get 方法
    set 设置或替换 get 查询的值。类似于 http put 方法
    result 说明成功的响应了先前的查询。类似于 http 状态码 200
    error 查询和响应中出现的错误
  • 下面是一个 IQ 例子:
        <iqfrom="huangyibiao@welcome.com/ios"
            id="xxxxxxx"
            to="biaoge@welcome.com/ios"
            type="get">
          <queryxmlns="jabber:iq:roster"/>
        </iq> 

2.7 XMPPPresence 类

  • 这个类代表节点,我们通过此类提供的方法来生成 XML 数据。presence 它代表用户在线状态。

        @interface XMPPPresence : XMPPElement
    
        // Converts an NSXMLElement to an XMPPPresence element in place (no memory allocations or copying)
        + (XMPPPresence *)presenceFromElement:(NSXMLElement *)element;
    
        + (XMPPPresence *)presence;
        + (XMPPPresence *)presenceWithType:(NSString *)type;
    
        // type:用户在线状态,to:接收方的 JID
        + (XMPPPresence *)presenceWithType:(NSString *)type to:(XMPPJID *)to;
    
        - (id)init;
        - (id)initWithType:(NSString *)type;
        - (id)initWithType:(NSString *)type to:(XMPPJID *)to;
    
        - (NSString *)type;
    
        - (NSString *)show;
        - (NSString *)status;
    
        - (int)priority;
    
        - (int)intShow;
    
        - (BOOL)isErrorPresence;
    
        @end
  • presence 用来表明用户的状态,如:online、offline、away、dnd (请勿打扰) 等。当改变自己的状态时,就会在 stream 的上下文中插入一个 Presence 元素,来表明自身的状态。要想接受 presence 消息,必须经过一个叫做 presence subscription 的授权过程。
  • <type></type> 有以下类别(可选设置如:<type>subscribe</type>):
    type 说明
    available 上线
    unavailable 下线
    away 离开
    do not disturb 忙碌
    subscribe 订阅其他用户的状态
    probe 请求获取其他用户的状态
    unavailable 不可用,离线(offline)状态
  • <show></show> 节点有以下类别,如 <show>dnd</show>
    show 说明
    chat 聊天中
    away 暂时离开
    xa eXtend Away,长时间离开
    dnd 勿打扰
  • <status></status> 节点
    • 这个节点表示状态信息,内容比较自由,几乎可以是所有类型的内容。常用来表示用户当前心情,活动,听的歌曲,看的视频,所在的聊天室,访问的网页,玩的游戏等等。
  • <priority></priority> 节点
    • 范围 -128~127。高优先级的 resource 能接受发送到 bare JID 的消息,低优先级的 resource 不能。优先级为负数的 resource 不能收到发送到 bare JID 的消息。
  • 发送一个用户在线状态的例子:
        <presencefrom="alice@wonderland.lit/pda">
          <show>dnd</show>
          <status>浏览器搜索</status>
        </presence> 

2.8 XMPPMessage 类

  • XMPPMessage 是 XMPP 框架给我们提供的,方便用于生成 XML 消息的数据。

        @interface XMPPMessage : XMPPElement
    
        // Converts an NSXMLElement to an XMPPMessage element in place (no memory allocations or copying)
        + (XMPPMessage *)messageFromElement:(NSXMLElement *)element;
    
        + (XMPPMessage *)message;
        + (XMPPMessage *)messageWithType:(NSString *)type;
        + (XMPPMessage *)messageWithType:(NSString *)type to:(XMPPJID *)to;
        + (XMPPMessage *)messageWithType:(NSString *)type to:(XMPPJID *)jid elementID:(NSString *)eid;
        + (XMPPMessage *)messageWithType:(NSString *)type to:(XMPPJID *)jid elementID:(NSString *)eid child:(NSXMLElement *)childElement;
        + (XMPPMessage *)messageWithType:(NSString *)type elementID:(NSString *)eid;
        + (XMPPMessage *)messageWithType:(NSString *)type elementID:(NSString *)eid child:(NSXMLElement *)childElement;
        + (XMPPMessage *)messageWithType:(NSString *)type child:(NSXMLElement *)childElement;
    
        - (id)init;
        - (id)initWithType:(NSString *)type;
        - (id)initWithType:(NSString *)type to:(XMPPJID *)to;
        - (id)initWithType:(NSString *)type to:(XMPPJID *)jid elementID:(NSString *)eid;
        - (id)initWithType:(NSString *)type to:(XMPPJID *)jid elementID:(NSString *)eid child:(NSXMLElement *)childElement;
        - (id)initWithType:(NSString *)type elementID:(NSString *)eid;
        - (id)initWithType:(NSString *)type elementID:(NSString *)eid child:(NSXMLElement *)childElement;
        - (id)initWithType:(NSString *)type child:(NSXMLElement *)childElement;
    
        - (NSString *)type;
        - (NSString *)subject;
        - (NSString *)body;
        - (NSString *)bodyForLanguage:(NSString *)language;
        - (NSString *)thread;
    
        - (void)addSubject:(NSString *)subject;
        - (void)addBody:(NSString *)body;
        - (void)addBody:(NSString *)body withLanguage:(NSString *)language;
        - (void)addThread:(NSString *)thread;
    
        - (BOOL)isChatMessage;
        - (BOOL)isChatMessageWithBody;
        - (BOOL)isErrorMessage;
        - (BOOL)isMessageWithBody;
    
        - (NSError *)errorMessage;
    
        @end
  • message 是一种基本 推送 消息方法,它不要求响应。主要用于 IM、groupChat、alert 和 notification 之类的应用中。
  • <type></type> 有以下类别(可选设置如:<type>chat</type>):
    type 说明
    normal 类似于 email,主要特点是不要求响应
    chat 类似于 qq 里的好友即时聊天,主要特点是实时通讯
    groupchat 类似于聊天室里的群聊
    headline 用于发送 alert 和 notification
    error 如果发送 message 出错,发现错误的实体会用这个类别来通知发送者出错了
  • <body></body> 节点
    • 所要发送的内容就放在 body 节点下
  • 消息节点的例子:
        <messageto="lily@jabber.org/contact" type="chat">
            <body>您好?</body>
        </message> 

3、XMPPFramework 框架使用

3.1 CocoaPods 导入框架

  • 1、通过 CocoaPods 导入第三方框架 XMPPFramework。

    • 在 Podfile 文件中加入如下代码,在终端中,使用命令 pod install 下载添加 XMPPFramework 框架。

          platform :ios, '8.0'
      
          target 'XMPPDemo' do
      
              use_frameworks!
              pod 'XMPPFramework', '~> 3.7.0'
      
          end
  • 2、在需要使用 XMPPFramework 的文件中导入以下头文件。
        #import <XMPPFramework/XMPPFramework.h>

3.2 导入框架过程中问题解决

  • 1、用 Cocoapods 集成 XMPPFramework 遇 Module 'KissXML' not found 等问题解决方法。

    • 一般来说,通过 Coacopods 集成集成第三方框架,不会再有依赖库方面的问题,所以需要检查导入方式是否正确,最终找到原因,仔细看 githup 上导入说明

          Install
      
          The minimum deployment target is iOS 8.0 / macOS 10.8.
      
          The easiest way to install XMPPFramework is using CocoaPods. Remember to add to the top of your Podfile the
          use_frameworks! line (even if you are not using swift):
    • 因此,Podfile 里必须写入这一句
          use_frameworks!
  • 2、Xcode8 之后 XMPP 重定义 Redefinition of module 'dnssd' 问题解决方法。
    • 在升级 Xcode 到 8 之后,原来的关于 XMPP 的项目运行报错,错误信息为: Redefinition of module 'dnssd'。系统和XMPP框架同时用到了 'dnssd',大概就是错误的原因。
    • 解决方案:
          # The version pushed to CocoaPods is very out of date, use master branch for now
          pod 'XMPPFramework', :git => "https://github.com/robbiehanson/XMPPFramework.git", :branch => 'master'
      
          大概意思是需要更新 XMPP 框架,需要把 Podfile 文件中的
              pod 'XMPPFramework', '~> 3.6.6'
          用
              pod 'XMPPFramework', :git => "https://github.com/robbiehanson/XMPPFramework.git", :branch => 'master'
          来替换
      
          或者直接改成
              pod 'XMPPFramework', '~> 3.7.0'
  • 3、在 pod update 的过程中有的童鞋会遇到下面这样的错误。

    • 这个是因为更新的 XMPP 框架中支持的最低版本为 iOS 8.0 / macOS 10.8。The minimum deployment target is iOS 8.0 / macOS 10.8.
    • 把 Podfile 文件中
          platform:ios, '7.0' 
      • 的 7.0 改为 8.0 或以上。
  • 4、pod 更新完成了,出现下面这样的错误。

    • 到报错的工程里面搜一下

          Enable Strict Checking of objc_msgSend Calls
      • 改成相反的值就行了,别改没有报错的工程。

4、XMPPFramework 实现简单聊天

  • 聊天实现的原理就是,一个客户端通过 XMPP 协议把信息传给服务器,服务器再发消息发给另一个客户端。

4.1 用户注册

  • 初始化

        /// 包含头文件
        #import <XMPPFramework/XMPPFramework.h>
    
        /// 遵守协议
        <XMPPStreamDelegate>
    
        /// 定义 XMPP 服务器相关信息
        #define HOST_DOMAIN     @"jhq0228-macbookair.local"
        #define HOST_NAME       @"jhq0228-macbookair.local"
        #define HOST_PORT       5222
    
        /// 注册的账号
        @property (nonatomic, copy) NSString *registerUserName;
    
        /// 注册的密码
        @property (nonatomic, copy) NSString *registerPassWord;
    
        /// XMPP 流
        @property (nonatomic, strong) XMPPStream *stream;
    
        /// 初始化
        self.stream = [[XMPPStream alloc] init];
        self.stream.hostName = HOST_NAME;
        self.stream.hostPort = HOST_PORT;
        [self.stream addDelegate:self delegateQueue:dispatch_get_main_queue()];
  • 与服务器建立链接
        /// 与服务器建立链接
        [self connectToSercerWithUserName:self.registerUserName resource:nil];
    
        #pragma mark 与服务器连接通信
    
            /// 与服务器建立链接,自定义方法
            - (void)connectToSercerWithUserName:(NSString *)userName resource:(NSString *)resource {
    
                if ([self.stream isConnected]) {
                    [self disconnectWithServer];
                }
    
                // jid
                self.stream.myJID = [XMPPJID jidWithUser:userName domain:HOST_DOMAIN resource:resource];
    
                NSError *error = nil;
    
                // 进行链接
                [self.stream connectWithTimeout:30.0 error:&error];
    
                if (error != nil) {
                    NSLog(@"连接出现问题");
                }
            }
  • 进行注册
        #pragma mark XMPPStreamDelegate 协议方法
    
            /// 与服务器连接成功
            - (void)xmppStreamDidConnect:(XMPPStream *)sender {
    
                NSError *error1 = nil;
    
                // 进行注册
                [self.stream registerWithPassword:self.registerPassWord error:&error1];
    
                if (error1 != nil) {
                    NSLog(@"注册出现问题");
                }
            }
    
            /// 与服务器连接超时
            - (void)xmppStreamConnectDidTimeout:(XMPPStream *)sender {
    
                NSLog(@"连接服务器超时,请检查网络链接后再试!");
            }
    
            /// 注册成功
            - (void)xmppStreamDidRegister:(XMPPStream *)sender {
    
                NSLog(@"注册成功");
            }
    
            /// 注册失败
            - (void)xmppStream:(XMPPStream *)sender didNotRegister:(DDXMLElement *)error {
    
                NSLog(@"注册失败");
            }

4.2 用户登录、注销

  • 初始化

        /// 包含头文件
        #import <XMPPFramework/XMPPFramework.h>
    
        /// 遵守协议
        <XMPPStreamDelegate>
    
        /// 定义 XMPP 服务器相关信息
        #define HOST_DOMAIN     @"jhq0228-macbookair.local"
        #define HOST_NAME       @"jhq0228-macbookair.local"
        #define HOST_PORT       5222
    
        /// 登录的账号
        @property (nonatomic, copy) NSString *loginUserName;
    
        /// 登录的密码
        @property (nonatomic, copy) NSString *loginPassWord;
    
        /// XMPP 流
        @property (nonatomic, strong) XMPPStream *stream;
    
        /// 初始化
        self.stream = [[XMPPStream alloc] init];
        self.stream.hostName = HOST_NAME;
        self.stream.hostPort = HOST_PORT;
        [self.stream addDelegate:self delegateQueue:dispatch_get_main_queue()];
  • 与服务器建立链接
        /// 与服务器建立链接
        [self connectToSercerWithUserName:self.loginUserName resource:nil];
    
        #pragma mark 与服务器连接通信
    
            /// 与服务器建立链接,自定义方法
            - (void)connectToSercerWithUserName:(NSString *)userName resource:(NSString *)resource {
    
                if ([self.stream isConnected]) {
                    [self disconnectWithServer];
                }
    
                // jid
                self.stream.myJID = [XMPPJID jidWithUser:userName domain:HOST_DOMAIN resource:resource];
    
                NSError *error = nil;
    
                // 进行连接
                [self.stream connectWithTimeout:30.0 error:&error];
    
                if (error != nil) {
                    NSLog(@"连接出现问题");
                }
            }
  • 进行登录认证
        #pragma mark XMPPStreamDelegate 协议方法
    
            /// 与服务器连接成功
            - (void)xmppStreamDidConnect:(XMPPStream *)sender {
    
                NSError *error = nil;
    
                // 进行登录认证
                [self.stream authenticateWithPassword:self.loginPassWord error:&error];
    
                if (error != nil) {
                    NSLog(@"登录认证出现问题");
                }
            }
    
            /// 与服务器连接超时
            - (void)xmppStreamConnectDidTimeout:(XMPPStream *)sender {
    
                NSLog(@"连接服务器超时,请检查网络链接后再试!");
            }
    
            /// 登录成功
            - (void)xmppStreamDidAuthenticate:(XMPPStream *)sender {
    
                NSLog(@"登录成功");
    
                // 设置用户在线状态,如果没有添加,别人给你发的消息服务器默认为离线状态,是不会给你发送的
                XMPPPresence *presence = [XMPPPresence presenceWithType:@"available"];
                [self.stream sendElement:presence];
            }
    
            /// 登录失败
            - (void)xmppStream:(XMPPStream *)sender didNotAuthenticate:(DDXMLElement *)error {
    
                NSLog(@"登录失败");
            }
  • 与服务器断开链接,用户注销
        #pragma mark 与服务器连接通信
    
            /// 与服务器断开链接,用户注销,自定义方法
            - (void)disconnectWithServer {
    
                // 断开链接
                [self.stream disconnect];
            }
    
        #pragma mark XMPPStreamDelegate 协议方法
    
            /// 注销成功
            - (void)xmppStreamDidDisconnect:(XMPPStream *)sender withError:(NSError *)error {
    
                NSLog(@"注销成功");
    
                // 设置用户下线状态
                XMPPPresence *presene = [XMPPPresence presenceWithType:@"unavailable"];
                [self.stream sendElement:presene];
            }
  • 用户登录信息本地化存储
        /// 包含头文件
        #import <SAMKeychain/SAMKeychain.h>
    
        /// 用户名和密码
        @property (nonatomic, copy) NSString *userName;
        @property (nonatomic, copy) NSString *userPasswd;
    
        /// 是否记住密码
        @property (nonatomic, assign, getter=isSavePasswd) BOOL savePasswd;
    
        /// 保存用户登录信息
        - (void)saveUserLoginInfo {
    
            NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
    
            [userDefaults setObject:self.userName forKey:@"userNameKey"];
            [userDefaults setBool:self.isSavePasswd forKey:@"isSavePwdKey"];
            [userDefaults synchronize];
    
            if (self.isSavePasswd) {
                [SAMKeychain setPassword:self.userPasswd forService:[NSBundle mainBundle].bundleIdentifier account:self.userName];
    
                NSLog(@"保存用户登录信息");
    
            } else {
                self.userPasswd = nil;
                [SAMKeychain deletePasswordForService:[NSBundle mainBundle].bundleIdentifier account:self.userName];
    
                NSLog(@"不保存用户登录信息");
            }
        }
    
        /// 读取用户登录信息
        - (void)loadUserLoginInfo {
    
            NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
    
            self.userName = [userDefaults objectForKey:@"userNameKey"];
            self.savePasswd = [userDefaults boolForKey:@"isSavePwdKey"];
    
            self.userPasswd = [SAMKeychain passwordForService:[NSBundle mainBundle].bundleIdentifier account:self.userName];
        }

4.3 好友管理

  • 初始化

        /// 遵守协议
        <XMPPStreamDelegate, XMPPRosterDelegate, XMPPRosterMemoryStorageDelegate>
    
        /// 好友列表
        @property (nonatomic, strong) XMPPRoster *roster;
    
        /// 本地好友存储器
        @property (nonatomic, strong) XMPPRosterMemoryStorage *rosterMemoryStorage;
    
        // 添加好友模块
        self.rosterMemoryStorage = [[XMPPRosterMemoryStorage alloc] init];
        self.roster = [[XMPPRoster alloc] initWithRosterStorage:self.rosterMemoryStorage
                                                  dispatchQueue:dispatch_get_global_queue(0, 0)];
        [self.roster activate:self.stream];                                       // 激活
        [self.roster addDelegate:self delegateQueue:dispatch_get_main_queue()];   // 设置代理
        [self.roster setAutoFetchRoster:YES];                                     // 设置好友同步策略,XMPP 一旦连接成功,自动同步好友到本地
        [self.roster setAutoAcceptKnownPresenceSubscriptionRequests:NO];          // 关掉自动接收好友请求,默认开启自动同意
  • 获取好友列表
        // 手动同步好友列表到本地好友存储器
        [self.roster fetchRoster];
    
        // 获取好友列表,从本地好友存储器中读取好友信息
        NSArray *users = self.rosterMemoryStorage.unsortedUsers;
    
        // 获取好友账号名称
        NSString *userName = [user[0] jid].user;
    
        // 获取好友昵称
        NSString *userName = [users[0] nickname];
    
        // 获取好友在线状态
        BOOL userStatus = [user[0] isOnline];
    
        #pragma mark XMPPRosterDelegate 协议方法
    
            /// 开始同步好友列表到本地
            - (void)xmppRosterDidBeginPopulating:(XMPPRoster *)sender withVersion:(NSString *)version {
    
            }
    
            /// 同步到一个好友节点到本地
            - (void)xmppRoster:(XMPPRoster *)sender didReceiveRosterItem:(NSXMLElement *)item {
    
            }
    
            /// 同步好友列表到本地完成
            - (void)xmppRosterDidEndPopulating:(XMPPRoster *)sender {
    
            }
  • 刷新好友列表
        #pragma mark - XMPPRosterMemoryStorageDelegate 协议方法
    
            /// 本地好友存储器发生改变
            - (void)xmppRosterDidChange:(XMPPRosterMemoryStorage *)sender {
    
                // 如果设置了自动同步,当服务器的好友列表发生改变时,会自动同步存入本地好友存储器
            }
  • 刷新好友状态
        #pragma mark XMPPStreamDelegate 协议方法
    
            /// 好友状态改变
            - (void)xmppStream:(XMPPStream *)sender didReceivePresence:(XMPPPresence *)presence {
    
                // 收到对方取消定阅我的消息,对方删除我、对方状态改变
    
                if ([presence.type isEqualToString:@"unsubscribe"]) {
    
                    // 从我的本地好友存储器中将对方移除
                    [self.roster removeUser:presence.from];
                }
            }
  • 添加好友
        /// 添加好友,自定义方法
        - (void)addFriendWithUserName:(NSString *)userName remarkName:(NSString *)remarkName {
    
            NSString *jidString = userName;
    
            // 判断有没有域名,如果没有域名,自己添加形成完整的 jid
            NSString *domainString = [NSString stringWithFormat:@"@%@", HOST_DOMAIN];
            if (![jidString containsString:domainString]) {
                jidString = [jidString stringByAppendingString:domainString];
            }
    
            XMPPJID *friendJID = [XMPPJID jidWithString:jidString];
    
            // 添加好友,remarkName 为备注名称
            [self.roster addUser:friendJID withNickname:remarkName];
    
            // [self.roster subscribePresenceToUser:friendJID];
        }
  • 收到添加好友申请
        #pragma mark XMPPRosterDelegate 协议方法
    
            /// 收到添加好友请求
            - (void)xmppRoster:(XMPPRoster *)sender didReceivePresenceSubscriptionRequest:(XMPPPresence *)presence {
    
                NSString *name = [NSString stringWithFormat:@"添加 %@ 为好友?", presence.from.user];
    
                // 同意并添加对方为好友,YES 存入本地好友存储器
                [self.roster acceptPresenceSubscriptionRequestFrom:presence.from andAddToRoster:YES];
    
                // 拒绝添加对方为好友
                [self.roster rejectPresenceSubscriptionRequestFrom:presence.from];
            }
  • 删除好友
        /// 删除好友,自定义方法
        - (void)removeFriendWithUserName:(NSString *)userName {
    
            NSString *jidString = userName;
    
            // 判断有没有域名,如果没有域名,自己添加形成完整的 jid
            NSString *domainString = [NSString stringWithFormat:@"@%@", HOST_DOMAIN];
            if (![jidString containsString:domainString]) {
                jidString = [jidString stringByAppendingString:domainString];
            }
    
            XMPPJID *friendJID = [XMPPJID jidWithString:jidString];
    
            // 删除好友
            [self.roster removeUser:friendJID];
        }

4.4 文本消息管理

  • 初始化

        /// 遵守协议
        <XMPPStreamDelegate>
    
        /// 定义 XMPP 服务器相关信息
        #define HOST_DOMAIN     @"jhq0228-macbookair.local"
  • 发送文本消息
        /// 发送文本消息,自定义方法
        - (void)sendMessage:(NSString *)message toUser:(NSString *)userName {
    
            // 消息结构
            /*
                <message type="chat" to="xiaoming@example.com">
                    <body>Hello World</body>
                </message>
            */
    
            NSString *jidString = userName;                                             // 设置消息接收者
            NSString *domainString = [NSString stringWithFormat:@"@%@", HOST_DOMAIN];
            if (![jidString containsString:domainString]) {
                jidString = [jidString stringByAppendingString:domainString];
            }
    
            // 构建消息
            NSXMLElement *msg = [NSXMLElement elementWithName:@"message"];
            [msg addAttributeWithName:@"type" stringValue:@"chat"];
            [msg addAttributeWithName:@"to" stringValue:jidString];
    
            NSXMLElement *body = [NSXMLElement elementWithName:@"body"];
            [body setStringValue:message];                                              // 设置文本消息内容
    
            [msg addChild:body];
    
            // 发送
            [self.stream sendElement:msg];
        }
  • 接收文本消息
        #pragma mark XMPPStreamDelegate 协议方法
    
            /// 接收到消息
            - (void)xmppStream:(XMPPStream *)sender didReceiveMessage:(XMPPMessage *)message {
    
                NSString *msg = [[message elementForName:@"body"] stringValue];
            }
  • 消息回执
    • 这个是 XEP-0184 协议的内容。
    • 发送消息时附加回执请求
          // 消息结构
          /*
              <message
                  from="northumberland@shakespeare.lit/westminster"
                  id="richars2-4.1.247"
                  to="kingrichard@royalty.england.lit/throne">
                  <body>Hello World</body>
                  <request xmlns="urn:xmpp:receipts"/>
              </message>
           */
      
          NSString *jidString = userName;                                             // 设置消息接收者
          NSString *domainString = [NSString stringWithFormat:@"@%@", HOST_DOMAIN];
          if (![jidString containsString:domainString]) {
              jidString = [jidString stringByAppendingString:domainString];
          }
      
          // 构建消息
          NSString *siID = [XMPPStream generateUUID];
          XMPPJID *jid = [XMPPJID jidWithString:jidString];
      
          XMPPMessage *msg = [XMPPMessage messageWithType:@"chat" to:jid elementID:siID];
      
          NSXMLElement *receipt = [NSXMLElement elementWithName:@"request" xmlns:@"urn:xmpp:receipts"];
          [msg addChild:receipt];                                                     // 设置消息回执
      
          [msg addBody:message];                                                      // 设置消息内容
      
          // 发送
          [self.stream sendElement:msg];
    • 收到回执请求的消息,发送回执
          /// 接收到消息,XMPPStreamDelegate 协议方法
          - (void)xmppStream:(XMPPStream *)sender didReceiveMessage:(XMPPMessage *)message {
      
              // 消息结构
              /*
                  <message
                      from="kingrichard@royalty.england.lit/throne"
                      id="bi29sg183b4v"
                      to="northumberland@shakespeare.lit/westminster">
                      <received xmlns="urn:xmpp:receipts" id="richars2-4.1.247">
                  </message>
               */
      
              // 回执判断
              NSXMLElement *request = [message elementForName:@"request"];
      
              if (request) {
      
                  // 消息回执
                  if ([request.xmlns isEqualToString:@"urn:xmpp:receipts"]) {
      
                      // 组装消息回执
                      XMPPMessage *msg = [XMPPMessage messageWithType:[message attributeStringValueForName:@"type"]
                                                                   to:message.from
                                                            elementID:[message attributeStringValueForName:@"id"]];
      
                      NSXMLElement *recieved = [NSXMLElement elementWithName:@"received" xmlns:@"urn:xmpp:receipts"];
                      [msg addChild:recieved];
      
                      // 发送回执
                      [self.stream sendElement:msg];
                  }
      
              } else {
      
                  NSXMLElement *received = [message elementForName:@"received"];
      
                  if (received) {
      
                      // 消息回执
                      if ([received.xmlns isEqualToString:@"urn:xmpp:receipts"]) {
      
                          // 发送成功
                          NSLog(@"message send success!");
                      }
                  }
              }
      
              // 消息处理
              // ...
          }

4.5 图片消息管理

  • 图片和语音文件发送的基本思路:

    • 先将图片/语音转化成二进制文件,然后将二进制文件进行 base64 编码,编码成字符串。在即将发送的 message 内添加一个子节点,节点的 stringValue(节点的值)设置这个编码后的字符串。
    • 然后消息发出后取出消息文件的时候,通过 messageType 先判断是不是图片/语音信息,如果是图片/语音信息先通过自己之前设置的节点名称,把这个子节点的 stringValue 取出来,应该是一个 base64 之后的字符串。
  • 选择图片
        /// 遵守协议
        <UIImagePickerControllerDelegate, UINavigationControllerDelegate>
    
        UIImagePickerController *picker = [[UIImagePickerController alloc]init];
        picker.delegate = self;
        [self presentViewController:picker animated:YES completion:nil];
    
        #pragma mark - UIImagePickerControllerDelegate 代理方法
    
            - (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info {
    
                UIImage *image = info[UIImagePickerControllerOriginalImage];
                NSData *imageData = UIImagePNGRepresentation(image);
    
                // 发送图片消息,自定义方法
                [[XMPPManager defaultManager] sendMessage:imageData msgType:@"image" toUser:self.userName];
    
                [self dismissViewControllerAnimated:YES completion:nil];
            }
  • 发送图片消息
    • msgType 自定义消息类型,image:图片消息,audio:音频消息
        // 发送图片消息
        [[XMPPManager defaultManager] sendMessage:imageData msgType:@"image" toUser:self.userName];
    
        /// 发送图片/音频消息,自定义方法
        - (void)sendMessage:(NSData *)msgData msgType:(NSString *)type toUser:(NSString *)userName {
    
            NSString *jidString = userName;                                             // 设置消息接收者
            NSString *domainString = [NSString stringWithFormat:@"@%@", HOST_DOMAIN];
            if (![jidString containsString:domainString]) {
                jidString = [jidString stringByAppendingString:domainString];
            }
    
            XMPPJID *jid = [XMPPJID jidWithString:jidString];
            XMPPMessage *msg = [XMPPMessage messageWithType:@"chat" to:jid];
    
            [msg addBody:type];
    
            // 转换成 base64 的编码
            NSString *base64str = [msgData base64EncodedStringWithOptions:0];
    
            // 设置节点内容
            XMPPElement *attachment = [XMPPElement elementWithName:@"attachment" stringValue:base64str];
    
            // 包含子节点
            [msg addChild:attachment];
    
            // 发送消息
            [self.stream sendElement:msg];
        }
  • 接收图片消息
        #pragma mark XMPPStreamDelegate 协议方法
    
            /// 接收到消息
            - (void)xmppStream:(XMPPStream *)sender didReceiveMessage:(XMPPMessage *)message {
    
                if ([message.body isEqualToString:@"image"]) {
    
                    for (XMPPElement *node in message.children) {
    
                        // 取出消息的解码
                        NSString *base64str = node.stringValue;
                        NSData *data = [[NSData alloc] initWithBase64EncodedString:base64str options:0];
    
                        UIImage *image = [[UIImage alloc] initWithData:data];
                    }
                }
            }

4.6 语音消息管理

  • 图片和语音文件发送的基本思路:

    • 先将图片/语音转化成二进制文件,然后将二进制文件进行 base64 编码,编码成字符串。在即将发送的 message 内添加一个子节点,节点的 stringValue(节点的值)设置这个编码后的字符串。
    • 然后消息发出后取出消息文件的时候,通过 messageType 先判断是不是图片/语音信息,如果是图片/语音信息先通过自己之前设置的节点名称,把这个子节点的 stringValue 取出来,应该是一个 base64 之后的字符串。
  • 录制/播放语音
        /// 包含头文件
        #import <AVFoundation/AVFoundation.h>
    
        /// 录音器
        @property(nonatomic, strong) AVAudioRecorder *recorder;
    
        /// 录音时长
        @property(nonatomic, assign) NSTimeInterval recordTime;
    
        /// 录音地址
        @property(nonatomic, strong) NSURL *recordURL;
    
        /// 播放器
        @property(nonatomic, strong) AVAudioPlayer *player;
    
        /// 开始录音
    
            // 自定义方法
            - (IBAction)startRecord:(UIButton *)sender {
    
                // 创建录音文件保存路径
                NSString *urlStr = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
                self.recordURL = [NSURL URLWithString:[urlStr stringByAppendingPathComponent:@"myRecord.caf"]];
    
                // 创建录音格式设置
                NSMutableDictionary *dicM = [NSMutableDictionary dictionary];
                [dicM setObject:@(kAudioFormatLinearPCM) forKey:AVFormatIDKey];     // 设置录音格式
                [dicM setObject:@(8000) forKey:AVSampleRateKey];                    // 设置录音采样率,8000 是电话采样率,对于一般录音已经够了
                [dicM setObject:@(1) forKey:AVNumberOfChannelsKey];                 // 设置通道,这里采用单声道
                [dicM setObject:@(8) forKey:AVLinearPCMBitDepthKey];                // 每个采样点位数,分为 8、16、24、32
                [dicM setObject:@(YES) forKey:AVLinearPCMIsFloatKey];               // 是否使用浮点数采样
                NSDictionary *setting = [dicM copy];
    
                // 创建录音机
                self.recorder = [[AVAudioRecorder alloc] initWithURL:self.recordURL settings:setting error:NULL];
    
                // 开始录音
                [self.recorder record];
            }
    
        /// 停止录音
    
            // 自定义方法
            - (IBAction)stopRecord:(UIButton *)sender {
    
                NSTimeInterval time = self.recorder.currentTime;
                [self.recorder stop];
    
                if (time < 1.5) {
                    NSLog(@"时间太短");
                } else {
                    NSLog(@"录音完成");
                }
            }
    
        /// 播放录音
    
            // 自定义方法
            - (void)playAudioData:(NSData *)data {
    
                self.player = [[AVAudioPlayer alloc] initWithData:data error:NULL];
                self.player.numberOfLoops = 0;
                [self.player prepareToPlay];
                [self.player play];
            }
  • 发送语音消息
    • msgType 自定义消息类型,image:图片消息,audio:音频消息
        // 发送语音消息
        NSData *audioData = [NSData dataWithContentsOfURL:self.recordURL];
        NSString *type = [NSString stringWithFormat:@"audio:%.1f秒", self.recordTime];
        [[XMPPManager defaultManager] sendMessage:audioData msgType:type toUser:self.userName];
    
        /// 发送图片/音频消息,自定义方法
        - (void)sendMessage:(NSData *)msgData msgType:(NSString *)type toUser:(NSString *)userName {
    
            NSString *jidString = userName;                                             // 设置消息接收者
            NSString *domainString = [NSString stringWithFormat:@"@%@", HOST_DOMAIN];
            if (![jidString containsString:domainString]) {
                jidString = [jidString stringByAppendingString:domainString];
            }
    
            XMPPJID *jid = [XMPPJID jidWithString:jidString];
            XMPPMessage *msg = [XMPPMessage messageWithType:@"chat" to:jid];
    
            [msg addBody:type];
    
            // 转换成 base64 的编码
            NSString *base64str = [msgData base64EncodedStringWithOptions:0];
    
            // 设置节点内容
            XMPPElement *attachment = [XMPPElement elementWithName:@"attachment" stringValue:base64str];
    
            // 包含子节点
            [msg addChild:attachment];
    
            // 发送消息
            [self.stream sendElement:msg];
        }
  • 接收语音消息
        #pragma mark XMPPStreamDelegate 协议方法
    
            /// 接收到消息
            - (void)xmppStream:(XMPPStream *)sender didReceiveMessage:(XMPPMessage *)message {
    
                if ([message.body hasPrefix:@"audio"]) {
    
                    for (XMPPElement *node in message.children) {
    
                        // 取出消息的解码
                        NSString *base64str = node.stringValue;
                        NSData *data = [[NSData alloc] initWithBase64EncodedString:base64str options:0];
                    }
                }
            }

4.7 心跳检测

  • 为了监听服务器是否有效,增加心跳监听,用 XEP-0199 协议。
  • 在 XMPPFrameWork 框架下,封装了 XMPPAutoPing 和 XMPPPing 两个类都可以使用,因为 XMPPAutoPing 已经组合进了 XMPPPing 类,所以 XMPPAutoPing 使用起来更方便。
  • 初始化并启动 ping
        /// 包含头文件
        #import <XMPPFramework/XMPPFramework.h>
    
        /// 遵守协议
        <XMPPAutoPingDelegate>>
    
        /// 心跳检测
        @property (nonatomic, strong) XMPPAutoPing *autoPing;
    
        // 添加心跳检测模块
        self.autoPing = [[XMPPAutoPing alloc] init];    // 发送的是一个 stream:ping,对方如果想表示自己是活跃的,应该返回一个 pong
        [self.autoPing activate:self.stream];           // 激活
        [self.autoPing addDelegate:self delegateQueue:dispatch_get_main_queue()];
        self.autoPing.pingInterval = 1000;              // 定时发送 ping 时间
        self.autoPing.respondsToQueries = YES;          // 不仅仅是服务器来得响应,如果是普通的用户,一样会响应
        self.autoPing.targetJID = [XMPPJID jidWithString:HOST_DOMAIN];      // 设置 ping 目标服务器
                                                                           // 如果为 nil,则监听 stream 当前连接上的那个服务器
    
        #pragma mark - XMPPAutoPingDelegate 协议方法
    
            /// 已经发送 ping
            - (void)xmppAutoPingDidSendPing:(XMPPAutoPing *)sender {
    
                NSLog(@"xmppAutoPingDidSendPing");
            }
    
            /// 接收到 pong
            - (void)xmppAutoPingDidReceivePong:(XMPPAutoPing *)sender {
    
                NSLog(@"xmppAutoPingDidReceivePong");
            }
    
            /// ping 超时
            - (void)xmppAutoPingDidTimeout:(XMPPAutoPing *)sender {
    
                NSLog(@"xmppAutoPingDidTimeout");
            }
  • 停止 ping
        // 停止 ping
        [self.autoPing deactivate];
        [self.autoPing removeDelegate:self];
        self.autoPing = nil;

4.8 自动重连

  • 当意外与服务器断开连接,自动重新连接上去,并且将上一次的信息自动加上去。
  • 初始化
        /// 包含头文件
        #import <XMPPFramework/XMPPFramework.h>
    
        /// 遵守协议
        <XMPPReconnectDelegate>>
    
        /// 自动重连
        @property (nonatomic, strong) XMPPReconnect *reconnect;
    
        // 添加自动重连模块
        self.reconnect = [[XMPPReconnect alloc] init];
        [self.reconnect activate:self.stream];          // 激活
        [self.reconnect addDelegate:self delegateQueue:dispatch_get_main_queue()];
        self.reconnect.autoReconnect = YES;             // 设置是否自动重新连接
    
        #pragma mark - XMPPReconnectDelegate 协议方法
    
            /// 设置是否自动重新连接
            - (BOOL)xmppReconnect:(XMPPReconnect *)sender shouldAttemptAutoReconnect:(SCNetworkConnectionFlags)connectionFlags {
    
                return YES;
            }
    
            /// 意外断开连接
            - (void)xmppReconnect:(XMPPReconnect *)sender didDetectAccidentalDisconnect:(SCNetworkConnectionFlags)connectionFlags {
    
                NSLog(@"didDetectAccidentalDisconnect");
            }

5、XMPPFramework 快速登录

6、XMPPFramework 重连以及其他问题

时间: 2024-09-20 21:41:01

iOS - XMPP 的使用的相关文章

iOS - XMPP Openfire 服务器的搭建

前言 提前下载好相关软件,且安装目录最好安装在全英文路径下.如果路径有中文名,那么可能会出现一些莫名其妙的问题. 提前准备好的软件: jdk-8u91-macosx-x64.dmg mysql-5.7.17-macos10.12-x86_64.dmg mysql-workbench-community-6.3.9-osx-x86_64.dmg openfire_4_1_1.dmg Openfire 官网 MySQL 官网 JDK 官网 在安装配置 Openfire 或其他 xmpp 服务器前,需

ios xmpp探索研究:了解什么是XMPP协议

Extensible Messaging and Presence Protocol (XMPP) 简介 本文来转自: http://www.ibm.com/developerworks/cn/xml/tutorials/x-realtimeXMPPtut/section3.html  XMPP 简介 本小节将简要介绍 XMPP,它的起源,以及为何它是一个适合实时 web 通信的协议.您将检查 XMPP 通信设置的组件,并查看展示这些组件如何使用的示例. Web 标准和 XMPP XMPP 是一

ios xmpp研究探索:删除好友

XMPP中删除好友,就是发送一个presence,其类型为:unsubscribe - (void)removeBuddyWithJid:(NSString *)jidString completion:(HYBCompletionBlock)completion { if (![jidString hasSuffix:kServer]) { jidString = [NSString stringWithFormat:@"%@@%@", jidString, kServer]; }

iOS XMPP研究探索:登录与注册

首页了解用户唯一标识有什么组成(JID): JID 一般由三部分构成:用户名,域名和资源名,例如 test@example.com/Anthony 如果没有设置主机名,则使用 JID 的域名作为主机名 端口号是可选的,默认是 5222 下面动手开始,来创建一个单例,用于全权负责管理与XMPP交互, @interface HYBXMPPHelper : NSObject <XMPPStreamDelegate, XMPPRosterDelegate> /** * Singleton shared

ios xmpp研究探索:接收消息

在接收到好友发过来的消息时, 如果是正在与之聊天,则需要更新 当前聊天列表,即插入一条新的消息记录: - (void)xmppStream:(XMPPStream *)sender didReceiveMessage:(XMPPMessage *)message { NSLog(@"xmpp stream 接收到好友消息:%@", [message XMLString]); if (self.getNewMessageBlock) { XHMessage *newMessage = [

iOS XMPP 探索研究:Openfire+spark环境搭建

首先到官网下载openfire+spark 下载地址:http://www.igniterealtime.org/downloads/index.jsp 选择MAC版下载dmg文件. 双击运行dmg文件,安装完成后,到finder->系统偏好设置->openfire->开启, 默认是开启的,然后点击进入管理页面,首先进入需要配置: 配置服务器: 选择中文 配置域的时候,使用本机127.0.0.1,如果使用localhost,我这里出现用spark时,无法创建服务器,但是ping loca

ios xmpp研究探索:发送文本消息

/* 发送消息的格式 <message type="chat" to="hehe@example.com"> <body>Hello World!<body /> <message /> */ - (void)sendText:(NSString *)text toJid:(NSString *)jidString completion:(HYBCompletionBlock)completion { self.sen

ios xmpp研究探索:获取好友列表

/* 一个 IQ 请求: <iq type="get" from="xiaoming@example.com" to="example.com" id="1234567"> <query xmlns="jabber:iq:roster"/> <iq /> type 属性,说明了该 iq 的类型为 get,与 HTTP 类似,向服务器端请求信息 from 属性,消息来源,这

iOS XMPP研究探索:添加好友

公开一个API,提供添加好友功能: - (void)addBuddyWithJid:(NSString *)jidString completion:(HYBCompletionBlock)completion { if (![jidString hasSuffix:kServer]) { jidString = [NSString stringWithFormat:@"%@@%@", jidString, kServer]; } // 先判断是否已经是我的好友,如果是,就不再添加 i