Openfire XMPP Smack RTC IM 即时通讯 聊天

Posted baiqiantao

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Openfire XMPP Smack RTC IM 即时通讯 聊天相关的知识,希望对你有一定的参考价值。

Openfire XMPP Smack RTC IM 即时通讯 聊天


目录

简介

Demo地址:https://github.com/baiqiantao/OpenFireTest.git
官网
官方文档
OpenFire下载
技术分享图片

Openfire 简介

Openfire

  • Openfire是一个根据开源Apache许可证授权的实时协作服务器 real time collaboration (RTC)。它使用唯一广泛采用的即时消息开放协议XMPP(Jabber)。 Openfire非常容易设置和管理,但提供坚如磐石的安全性和性能。
  • Openfire是一个功能丰富即时消息和跨平台实时协作服务器,使用XMPP协议提供全面的群聊和即时消息服务
  • OpenFire是采用Java编程语言开发的实时协作服务器,可以轻易的构建高效率的即时通信服务器,安装和使用简单,利用 Web 进行管理,单台服务器可支持上万并发用户

相关的几个名词

简单说,OpenFire 是服务器,XMPP 是协议,Smack 是类库,Spark 是客户端。

Smack

GitHub
Flowdalic/asmack

  • Smack 是一个基于 XMPP 协议的 Java 实现,提供一套可扩展的API,与 OpenFire 进行通信。
  • Smack 是一个开源,易于使用的 XMPP 客户端类库,可以实现即时通讯和聊天。
  • Smack 是Spark项目的核心。

优点:

  • 简单,功能强大,只需短短几行代码就可以向用户发送文本消息;
  • 不像其他类库那样强制你进行包级别的编码,Smack提供了智能的、更高级的构造,像Chat和Roster类,可以让你进行更高效的编程;
  • 你不需要熟悉 XMPP XML 格式,甚至不需要熟悉XML;
  • 提供了简单的机器到机器通讯,允许在每个消息中设置任意数量的属性,包括java对象;
  • Apache许可下的开源类库,这意味着使用者可以将Smack整合进商业的或者非商业的应用中。

缺点是其API并非为大量并发用户设计,每个客户要1个线程,占用资源大。

Spark

  • Spark 相当与电脑版QQ,通过 smack 与 openfire 进行通信。
  • Spark 是一个 XMPP 协议通信聊天的CS端的IM软件,它可以通过 openfire 进行聊天对话。
<message from="[email protected]" to="[email protected]">消息内容</message>

JID

  • 基于历史原因, 一个XMPP实体的地址称为Jabber IdentifierJID,它用来标示XMPP网络中的各个XMPP实体。
  • 鉴于协议的分布式特征, JID 应包含联系到用户所需的所有信息。
  • 个人认为可以把JID理解为Email地址,就比较好理解了。
  • 一个合法的JID包括节点名user、域名domain、资源名resource,其中 user 和 resource 是可有可无的,domain 是必须的。domain和user部分是不分大小写的,但是resource区分大小写。
    • domainpart 通常指网络中的网关或者服务器
    • localpart(user、node) 通常表示一个向服务器或网关请求和使用网络服务的实体(比如一个客户端),当然它也能够表示其他的实体(比如在多用户聊天系统中的一个房间)。
    • resourcepart:通常表示一个特定的会话(与某个设备),连接(与某个地址),或者一个附属于某个节点ID实体相关实体的对象(比如多用户聊天室中的一个参加者)。
  • JID的格式为:jid = [ localpart "@" ] domainpart [ "/" resourcepart ],例如:
    • [email protected]:表示服务器jabber.org上的用户stpeter
    • [email protected]:一个用来提供多用户聊天服务的特定的聊天室。这里 room 是聊天室的名字,service 是多用户聊天服务的主机名
    • [email protected]/nick:加入了聊天室的用户nick的地址。这里 nick 是用户在聊天室的昵称

XMPP

Extensible Messaging and Presence Protocol,可扩展通讯和表示协议

  • XMPP 是基于 XML 的协议,这表明 XMPP 是可扩展的。
  • XMPP 包含了针对服务器端的软件协议,用于即时消息以及在线现场探测。
  • XMPP 的前身是Jabber(1998 年),一个开源形式组织产生的网络即时通信协议。
  • XMPP 是一个由IETF标准化的开放协议,由XMPP标准基金会支持和扩展。

XMPP是一种基于标准通用标记语言的子集XML的协议,它继承了在XML环境中灵活的发展性。因此,基于XMPP的应用具有超强的可扩展性。经过扩展以后的XMPP可以通过发送扩展的信息来处理用户的需求,以及在XMPP的顶端建立如内容发布系统和基于地址的服务等应用程序。而且,XMPP包含了针对服务器端的软件协议,使之能与另一个进行通话,这使得开发者更容易建立客户应用程序或给一个配好系统添加功能。

优点:开放、可扩展、标准、证实可用、分散、安全
缺点 :数据负载过重,没有二进制传输

基本网络结构
XMPP中定义了三个角色,客户端,服务器,网关,通信能够在这三者的任意两个之间双向发生。
服务器同时承担了客户端信息记录,连接管理和信息的路由功能。
网关承担着与异构即时通信系统的互联互通,异构系统可以包括SMS,MSN,ICQ等。
基本的网络形式是单客户端通过TCP/IP连接到单服务器,然后在之上传输XML。

XMPP 工作流程

  • 节点连接到服务器
  • 服务器利用本地目录系统中的证书对其认证
  • 节点指定目标地址,让服务器告知目标状态
  • 服务器查找、连接并进行相互认证
  • 节点之间进行交互

XMPP核心协议通信的基本模式就是先建立一个stream,然后协商一堆安全之类的东西,中间通信过程就是客户端发送XML Stanza(节点),一个接一个的。服务器根据客户端发送的信息以及程序的逻辑,发送XML Stanza给客户端。但是这个过程并不是一问一答的,任何时候都有可能从一方发信给另外一方。通信的最后阶段是</stream>关闭流,关闭TCP/IP连接。

传输的内容
传输的是与即时通讯相关的指令。在以前这些命令要么用2进制的形式发送(比如QQ),要么用纯文本指令加空格加参数加换行符的方式发送(比如MSN)。而XMPP传输的即时通讯指令的逻辑与以往相仿,只是协议的形式变成了XML格式的纯文本。这不但使得解析容易了,人也容易阅读了,方便了开发和查错。
XMPP 的核心部分就是一个在网络上分片段发送 XML 的流协议。这个流协议是 XMPP 的即时通讯指令的传递基础,可以说 XMPP 用 TCP 传的是 XML 流。

真实通讯案例
Xmpp协议是建立在xml的基础上的,所以,看起来,xmpp协议就像一个xml。

客户端 8049a646c63e65e8 发出去的消息:

<message from=‘[email protected]/phone‘ id=‘5U6Mk-5‘ to=‘[email protected]‘ type=‘chat‘>
    <body>{"fromId":"8049a646c63e65e8","fromName":"韩大东","messageType":1,"secret":false,"textContent":"你好","toName":"郑西风","toUserID":"903e652d2334628a"}</body>
    <request xmlns=‘urn:xmpp:receipts‘/>
</message>

技术分享图片

客户端 8049a646c63e65e8 接收到的消息:

<message from="[email protected]/phone" id="Bw4c9-4" to="[email protected]" type="chat">
    <body>{"fromId":"903e652d2334628a","fromName":"郑西风","messageType":1,"secret":false,"textContent":"你好"}</body>
    <request xmlns="urn:xmpp:receipts"/>
    <send time="2018-10-19 16:08:21:999" xmlns="icitic:msg:single"/>
</message>

技术分享图片

其实 XMPP 是一种很类似于http协议的一种数据传输协议,用户只需要明白它接收的类型,并理解它返回的类型,就可以很好的利用xmpp来进行数据通讯。

目前不少IM应用系统如Google公司的Google Talk以及Jive Messenger等开源应用,都是遵循XMPP协议集而设计实现的,这些应用具有很好的互通性。

Openfire 安装配置

安装时除了修改一下安装路径,其他一路Next就Ok了。
安装完毕后会自动启动Openfire服务并自动打开 配置页面 (可能需要手动刷新一下)。也可以通过双击 Openfireinopenfire.exeOpenfireinopenfired.exe 启动Openfire服务后手动打开配置页面。
技术分享图片

然后按照指引设置 Openfire 服务器:

  • 选择语言:中文简体
  • 配置服务器域名【127.0.0.1】
    技术分享图片

  • 选择数据库
    技术分享图片

  • 选择特性配置,默认即可

  • 设置管理员帐户【[email protected]】【123456a】
    技术分享图片

  • 提示安装完成,点击登录管理员控制台页面【admin】【123456a】

  • 进入后可以看到服务器名称等信息【127.0.0.1】
    技术分享图片

  • 创建用户【admin】【baiqiantao】【bqt】【test】
    技术分享图片

  • 安装spark客户端,这个spark仅仅是拿来测试用的。

至此代码以外的环境已经配置好了。

测试代码

Demo地址:https://github.com/baiqiantao/OpenFireTest.git

初始化

XMPPConnection的连接需要通过XMPPTCPConnectionConfiguration.builder()配置你在Openfire设置的配置,代码如下:

/**
 * 初始化
 */
public static synchronized void init(CharSequence username, String password) {
      if (connection == null) {
            //初始化XMPPTCPConnection相关配置
            XMPPTCPConnectionConfiguration configuration = XMPPTCPConnectionConfiguration.builder()
                        .setUsernameAndPassword(username, password)//设置登录openfire的用户名和密码
                        .setServiceName("oatest.dgcb.com.cn")//设置服务器名称
                        .setHost("oatest.dgcb.com.cn")//设置主机地址
                        .setPort(25222)//设置端口号
                        .setResource("phone") //默认为Smack
                        .setDebuggerEnabled(true)//是否查看debug日志
                        //**********************************************  以下为进阶配置  *************************************************
                        .setConnectTimeout(10 * 1000)//设置连接超时的最大时间
                        .setSecurityMode(ConnectionConfiguration.SecurityMode.disabled)//设置安全模式,关闭安全模式
                        .setCompressionEnabled(false) //开启通讯压缩,开启后传输的流量将节省90%
                        .setSendPresence(false)
                        .setCustomSSLContext(getSSLContext()) //自定义的TLS登录
                        .setHostnameVerifier((hostname, session) -> true)
                        .build();

            connection = new XMPPTCPConnection(configuration);
            connection.addConnectionListener(new MyConnectionListener()); //监听connect状态

            //SASL认证

            SASLAuthentication.blacklistSASLMechanism("SCRAM-SHA-1");
            SASLAuthentication.blacklistSASLMechanism(SASLPlainMechanism.DIGESTMD5);
            SASLAuthentication.registerSASLMechanism(new SASLPlainMechanism());

            Roster.getInstanceFor(connection).addRosterListener(new MyRosterListener());
            ChatManager.getInstanceFor(connection).addChatListener(new MyChatManagerListener()); //监听与聊天相关的事件
            MultiUserChatManager.getInstanceFor(connection).addInvitationListener(new MyInvitationListener()); //被邀请监听
      }
}

登录服务器

通过了上面的配置后,咱们可以登录Openfire系统了,相当简单:

/**
 * 登录
 */
public static void login(CharSequence username, String password) {
      try {
            if (!XMPPUtils.getConnection().isConnected()) {
                  XMPPUtils.getConnection().connect();
            }
            if (XMPPUtils.getConnection().isConnected()) {
                  Log.i("bqt", "开始登录");
                  XMPPUtils.getConnection().login(username, password);
                  Log.i("bqt", "登录成功");
            } else {
                  Log.i("bqt", "登录失败");
            }
      } catch (SmackException e) {
            e.printStackTrace();
      } catch (IOException e) {
            e.printStackTrace();
      } catch (XMPPException e) {
            e.printStackTrace();
      }
}

登录底层报文通讯简要解析

1、在建立了Socket后,client会向服务器发出一条xml:
技术分享图片

<stream:stream xmlns:stream=‘http://etherx.jabber.org/streams‘
               from=‘[email protected]‘
               to=‘oatest.dgcb.com.cn‘
               version=‘1.0‘
               xmlns=‘jabber:client‘
               xml:lang=‘en‘>

服务器解析到上面的指令后,会返回用于告诉client可选的SASL方式
技术分享图片

<?xml version=‘1.0‘ encoding=‘UTF-8‘?>
<stream:stream xmlns:stream="http://etherx.jabber.org/streams"
               from="oatest.dgcb.com.cn"
               id="36ebm4blnf"
               version="1.0"
               xmlns="jabber:client"
               xml:lang="en">
    <stream:features>
        <starttls xmlns="urn:ietf:params:xml:ns:xmpp-tls"></starttls>
        <mechanisms xmlns="urn:ietf:params:xml:ns:xmpp-sasl">
            <mechanism>PLAIN</mechanism>
            <mechanism>SCRAM-SHA-1</mechanism>
            <mechanism>CRAM-MD5</mechanism>
            <mechanism>DIGEST-MD5</mechanism>
        </mechanisms>
        <compression xmlns="http://jabber.org/features/compress">
            <method>zlib</method>
        </compression>
        <ver xmlns="urn:xmpp:features:rosterver"/>
        <register xmlns="http://jabber.org/features/iq-register"/>
    </stream:features>

2、客户端选择PLAIN认证方式
技术分享图片

<auth mechanism=‘PLAIN‘
      xmlns=‘urn:ietf:params:xml:ns:xmpp-sasl‘>ADgwNDlhNjQ2YzYzZTY1ZTgAQkRFNEM3QzBGMzdENEZGRTlENDlGNDcwMTdFNUJCRjc=
</auth>

服务器通过计算加密后的密码后,服务器将返回
技术分享图片

<success xmlns="urn:ietf:params:xml:ns:xmpp-sasl"/>

3、当客户端收到以上命令后,将首次发起连接的id发送到服务器
技术分享图片

<stream:stream xmlns:stream=‘http://etherx.jabber.org/streams‘
               from=‘[email protected]‘
               id=‘36ebm4blnf‘
               to=‘oatest.dgcb.com.cn‘
               version=‘1.0‘
               xmlns=‘jabber:client‘
               xml:lang=‘en‘>

这时服务器会返回如下内容说明此时已经成功绑定了当前的Socket
技术分享图片

<?xml version=‘1.0‘ encoding=‘UTF-8‘?>
<stream:stream xmlns:stream="http://etherx.jabber.org/streams"
               from="oatest.dgcb.com.cn"
               id="36ebm4blnf"
               version="1.0"
               xmlns="jabber:client"
               xml:lang="en">
    <stream:features>
        <compression xmlns="http://jabber.org/features/compress">
            <method>zlib</method>
        </compression>
        <ver xmlns="urn:xmpp:features:rosterver"/>
        <bind xmlns="urn:ietf:params:xml:ns:xmpp-bind"/>
        <session xmlns="urn:ietf:params:xml:ns:xmpp-session">
            <optional/>
        </session>
        <sm xmlns=‘urn:xmpp:sm:2‘/>
        <sm xmlns=‘urn:xmpp:sm:3‘/>
    </stream:features>

4、压缩
4.1、客户端在接收到如上的内容后会告诉服务器开启压缩

项目中没有使用压缩,所以下面的过程不存在,以下为参考别人的案例

<compress xmlns=‘http://jabber.org/protocol/compress‘><method>zlib</method></compress>

服务器返回

<compressed xmlns=‘http://jabber.org/protocol/compress‘/>

4.2、客户端收到服务器的响应命令后,重新建立一个Socket,发送指令

<stream:stream 
    xmlns=‘jabber:client‘       
    to=‘server domain‘ 
    xmlns:stream=‘http://etherx.jabber.org/streams‘ 
    version=‘1.0‘ 
    from=‘[email protected] domain‘  
    id=‘c997c3a8‘ 
    xml:lang=‘en‘>

服务器将返回,不知道你有没有发现,这里的id还是那个id

<?xml version=‘1.0‘ encoding=‘UTF-8‘?>
    <stream:stream 
        xmlns:stream="http://etherx.jabber.org/streams" 
        xmlns="jabber:client" 
        from="im" 
        id="c997c3a8" 
        xml:lang="en" 
        version="1.0">
        <stream:features>
            <mechanisms 
            xmlns="urn:ietf:params:xml:ns:xmpp-sasl">
                <mechanism>PLAIN</mechanism>
                <mechanism>ANONYMOUS</mechanism>
                <mechanism>JIVE-SHAREDSECRET</mechanism>
            </mechanisms>
            <bind 
                xmlns="urn:ietf:params:xml:ns:xmpp-bind"/>
                <session 
                    xmlns="urn:ietf:params:xml:ns:xmpp-session"/>
    </stream:features>

实际上到这里客户端的登录已经完成了,但是还没算成功,接下来可以开始做绑定Socket的操作了

登录底层报文通讯简要解析

1、客户端发送绑定Socket的指令:
技术分享图片

<iq
    id=‘SG6jR-3‘
    type=‘set‘>
    <bind xmlns=‘urn:ietf:params:xml:ns:xmpp-bind‘>
        <resource>phone</resource>
    </bind>
</iq>

服务器返回绑定了具有指定 JID 的客户端
技术分享图片

<iq
    id="SG6jR-3"
    to="oatest.dgcb.com.cn/36ebm4blnf"
    type="result">
    <bind xmlns="urn:ietf:params:xml:ns:xmpp-bind">
        <jid>[email protected]/phone</jid>
    </bind>
</iq>

2、开启一个session

项目中没有开启一个session的逻辑,所以下面的过程不存在,以下为参考别人的案例

<iq id=‘b86j8-6‘ type=‘set‘><session xmlns=‘urn:ietf:params:xml:ns:xmpp-session‘/></iq>

这时服务器返回

<iq 
    type="result" 
    id="b86j8-6" 
    to="[email protected]/c997c3a8"/>

3、接着会自动发送一条获取通讯录的指令
技术分享图片

<iq
    id=‘gZYnq-5‘
    type=‘get‘>
    <query xmlns=‘jabber:iq:roster‘></query>
</iq>

服务器将返回
技术分享图片

<iq
    id="SG6jR-5"
    to="[email protected]/phone"
    type="result">
    <query ver="-491295515"
           xmlns="jabber:iq:roster">
        <item
            name="李**"
            jid="[email protected]"
            subscription="to"/>
        <item
            jid="[email protected]"
            subscription="from"/>
        <item
            ask="subscribe"
            jid="[email protected]"
            subscription="none"/>
    </query>
</iq>

服务器判断客户端是否在线

服务器会定时(3分钟)主动发送一条 ping 消息,以确定客户端是否在线:
技术分享图片

<iq
    from="oatest.dgcb.com.cn"
    id="553-595"
    to="[email protected]/phone"
    type="get">
    <ping xmlns="urn:xmpp:ping"/>
</iq>

客户端响应:

<iq
    id=‘553-595‘
    to=‘oatest.dgcb.com.cn‘
    type=‘result‘></iq>

到此,整个登录流程已经成功了,接下来可以做一些用户信息的获取等操作。

发送消息

发送方式一:

ChatManager.getInstanceFor(XMPPUtils.getConnection()).createChat(to).sendMessage(text);//直接发送一条文本
<message
    from=‘[email protected]/phone‘
    id=‘WRULf-15‘
    to=‘[email protected]/phone‘
    type=‘chat‘>
    <body>你好,我是包青天</body>
    <thread>a86ee445-0028-4058-8d08-98803c9b6fdb</thread>
</message>

发送方式二:

XMPPUtils.getConnection().sendStanza(msg);//发送一个Message对象,可包含一些信息,一般使用后者
<message
    from=‘[email protected]/phone‘
    id=‘1539957065416‘
    to=‘[email protected]/phone‘
    type=‘chat‘>
    <body>你好,我是包青天</body>
</message>

服务器回执:

<message
    from="[email protected]/phone"
    to="[email protected]/phone">
    <received msgId="WRULf-15"
              status="1"
              time="2018-10-19 21:50:23:848"
              xmlns="urn:xmpp:receipts"/>
</message>

由于项目中有集成离线推送功能,而通过Demo登录时会认为没有正常登录,所以实现对方收不到消息,这个以后有空再走走流程。

推送的消息:
技术分享图片

测试案例代码

项目结构

技术分享图片

implementation ‘org.igniterealtime.smack:smack-android:4.1.4‘
implementation ‘org.igniterealtime.smack:smack-tcp:4.1.4‘
implementation ‘org.igniterealtime.smack:smack-im:4.1.4‘
implementation ‘org.igniterealtime.smack:smack-extensions:4.1.4‘

MainActivity

public class MainActivity extends ListActivity {
    private boolean switchUser = false;
    private EditText etAccount, etPassword, etChat;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        String[] array = {"初始化",
                "登录",
                "注销登录",
                "发消息",
                "获取好友信息",
                "创建聊天室",
                "加入聊天室",
                "邀请好友进入聊天室",
                "",};
        setListAdapter(new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, Arrays.asList(array)));
        etAccount = new EditText(this);
        etPassword = new EditText(this);
        etChat = new EditText(this);

        etAccount.setText(switchUser ? "8049a646c63e65e8" : "903e652d2334628a");
        etPassword.setText(switchUser ? "BDE4C7C0F37D4FFE9D49F47017E5BBF7" : "40C61DE3492C41B1846281833434D997");
        etChat.setText(switchUser ? "[email protected]/phone" : "[email protected]/phone");
        getListView().addFooterView(etAccount);
        getListView().addFooterView(etPassword);
        getListView().addFooterView(etChat);//要聊天的用户的ID
    }

    @Override
    protected void onListItemClick(ListView l, View v, int position, long id) {
        String account = etAccount.getText().toString();
        String password = etPassword.getText().toString();
        String jid = etChat.getText().toString();
        new Thread(() -> testApi(position, account, password, jid)).start();
    }

    private void testApi(int position, String account, String password, String jid) {
        switch (position) {
            case 0:
                XMPPUtils.init(account, password);//初始化
                break;
            case 1:
                XMPPUtils.login(account, password);//登录
                break;
            case 2:
                XMPPUtils.logout();//注销登录
                break;
            case 3:
                XMPPUtils.sendMessage(account + "@oatest.dgcb.com.cn/phone", jid, "你好,我是包青天");//发消息
                break;
            case 4:
                XMPPUtils.getMyFriends();//获取好友信息
                break;
            case 5:
                XMPPUtils.createMucRoom(jid, "包青天");//创建聊天室
                break;
            case 6:
                XMPPUtils.joinChatRoom(jid, account);//加入聊天室
                break;
            case 7:
                XMPPUtils.inviteToTalkRoom(jid, account, password, "快来参加第二十八届英雄大会");//邀请好友进入聊天室
                break;
            default:
                break;
        }
    }
}

常用功能封装的工具栏

public class XMPPUtils {
    private static XMPPTCPConnection connection;

    /**
     * 初始化
     */
    public static synchronized void init(CharSequence username, String password) {
        if (connection == null) {
            //初始化XMPPTCPConnection相关配置
            XMPPTCPConnectionConfiguration configuration = XMPPTCPConnectionConfiguration.builder()
                    .setUsernameAndPassword(username, password)//设置登录openfire的用户名和密码
                    .setServiceName("oatest.dgcb.com.cn")//设置服务器名称
                    .setHost("oatest.dgcb.com.cn")//设置主机地址
                    .setPort(25222)//设置端口号
                    .setResource("phone") //默认为Smack
                    .setDebuggerEnabled(true)//是否查看debug日志
                    //**********************************************  以下为进阶配置  *************************************************
                    .setConnectTimeout(10 * 1000)//设置连接超时的最大时间
                    .setSecurityMode(ConnectionConfiguration.SecurityMode.disabled)//设置安全模式,关闭安全模式
                    .setCompressionEnabled(false) //开启通讯压缩,开启后传输的流量将节省90%
                    .setSendPresence(false)
                    .setCustomSSLContext(getSSLContext()) //自定义的TLS登录
                    .setHostnameVerifier((hostname, session) -> true)
                    .build();

            connection = new XMPPTCPConnection(configuration);
            connection.setFromMode(XMPPConnection.FromMode.USER);
            connection.addConnectionListener(new MyConnectionListener()); //监听connect状态
            connection.addAsyncStanzaListener(new MyStanzaListener(), StanzaTypeFilter.MESSAGE);// 注册包的监听器

            //SASL认证
            SASLAuthentication.blacklistSASLMechanism("SCRAM-SHA-1");
            SASLAuthentication.blacklistSASLMechanism(SASLPlainMechanism.DIGESTMD5);
            SASLAuthentication.registerSASLMechanism(new SASLPlainMechanism());

            Roster.getInstanceFor(connection).addRosterListener(new MyRosterListener());
            ChatManager.getInstanceFor(connection).addChatListener(new MyChatManagerListener()); //监听与聊天相关的事件
            MultiUserChatManager.getInstanceFor(connection).addInvitationListener(new MyInvitationListener()); //被邀请监听
        }
    }

    private static SSLContext getSSLContext() {
        SSLContext context = null;
        try {
            context = SSLContext.getInstance("TLS");
            context.init(null, new TrustManager[]{new TLSUtils.AcceptAllTrustManager()}, new SecureRandom());
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (KeyManagementException e) {
            e.printStackTrace();
        }
        return context;
    }

    public static XMPPTCPConnection getConnection() {
        return connection;
    }

    /**
     * 登录
     */
    public static void login(CharSequence username, String password) {
        try {
            if (!XMPPUtils.getConnection().isConnected()) {
                XMPPUtils.getConnection().connect();
            }
            if (XMPPUtils.getConnection().isConnected()) {
                Log.i("bqt", "开始登录");
                XMPPUtils.getConnection().login(username, password);
                Log.i("bqt", "登录成功");
            } else {
                Log.i("bqt", "登录失败");
            }
        } catch (SmackException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (XMPPException e) {
            e.printStackTrace();
        }
    }

    /**
     * 注销登录
     */
    public static void logout() {
        XMPPUtils.getConnection().disconnect();
    }

    /**
     * 发消息
     */
    public static void sendMessage(String from, String to, String text) {
        try {
            ChatManager.getInstanceFor(XMPPUtils.getConnection()).createChat(to).sendMessage(text);//直接发送一条文本

            Message msg = new Message(to, Message.Type.chat);
            msg.setStanzaId(System.currentTimeMillis() + "");
            msg.setFrom(from);
            msg.setBody(text);
            XMPPUtils.getConnection().sendStanza(msg);//发送一个Message对象,可包含一些信息,一般使用后者
        } catch (SmackException.NotConnectedException e) {
            e.printStackTrace();
        }
    }

    /**
     * 获取好友信息
     */
    public static void getMyFriends() {
        //并不需要访问网络,因为在登录后已经拿到用户的通讯录了,这里是直接从缓存中读取的
        Set<RosterEntry> set = Roster.getInstanceFor(XMPPUtils.getConnection()).getEntries();
        for (RosterEntry entry : set) {
            Log.i("bqt", "JID:" + entry.getUser() + ",Name:" + entry.getName());
        }
    }

    /**
     * 创建聊天室
     */
    public static void createMucRoom(String jid, String nickname) {
        try {
            MultiUserChat muc = MultiUserChatManager.getInstanceFor(XMPPUtils.getConnection()).getMultiUserChat(jid);
            muc.create(nickname);//昵称
            Form form = muc.getConfigurationForm();
            Form submitForm = form.createAnswerForm();

            for (FormField field : form.getFields()) {
                if (!FormField.Type.hidden.equals(field.getType()) && field.getVariable() != null) {
                    submitForm.setDefaultAnswer(field.getVariable());
                }
            }
            List<String> list = new ArrayList<>();
            list.add("20");
            List<String> owners = new ArrayList<>();
            owners.add("[email protected]");
            submitForm.setAnswer("muc#roomconfig_roomowners", owners);
            submitForm.setAnswer("muc#roomconfig_maxusers", list);
            submitForm.setAnswer("muc#roomconfig_roomname", "room01");
            submitForm.setAnswer("muc#roomconfig_persistentroom", true);
            submitForm.setAnswer("muc#roomconfig_membersonly", false);
            submitForm.setAnswer("muc#roomconfig_allowinvites", true);
            submitForm.setAnswer("muc#roomconfig_enablelogging", true);
            submitForm.setAnswer("x-muc#roomconfig_reservednick", true);
            submitForm.setAnswer("x-muc#roomconfig_canchangenick", false);
            submitForm.setAnswer("x-muc#roomconfig_registration", false);
            muc.sendConfigurationForm(submitForm);
        } catch (XMPPException.XMPPErrorException e) {
            e.printStackTrace();
        } catch (SmackException e) {
            e.printStackTrace();
        }
    }

    /**
     * 加入聊天室
     */
    public static void joinChatRoom(String jid, String nickname) {
        try {
            MultiUserChat muc = MultiUserChatManager.getInstanceFor(XMPPUtils.getConnection()).getMultiUserChat(jid);
            muc.join(nickname);
        } catch (SmackException.NoResponseException e) {
            e.printStackTrace();
        } catch (XMPPException.XMPPErrorException e) {
            e.printStackTrace();
        } catch (SmackException.NotConnectedException e) {
            e.printStackTrace();
        }
    }

    /**
     * 邀请好友进入聊天室
     */
    public static void inviteToTalkRoom(String jid, String nickname, String user, String reason) {
        try {
            MultiUserChat muc = MultiUserChatManager.getInstanceFor(XMPPUtils.getConnection()).getMultiUserChat(jid);
            muc.addInvitationRejectionListener((invitee, rejectReason) -> Log.i("bqt", "拒绝了," + invitee + "," + rejectReason));
            muc.join(nickname);
            muc.invite(user, reason);
        } catch (SmackException.NotConnectedException e) {
            e.printStackTrace();
        } catch (SmackException.NoResponseException e) {
            e.printStackTrace();
        } catch (XMPPException.XMPPErrorException e) {
            e.printStackTrace();
        }
    }
}

2018-10-19





















































以上是关于Openfire XMPP Smack RTC IM 即时通讯 聊天的主要内容,如果未能解决你的问题,请参考以下文章

使用 xmpp + smack + openfire 在 android 中阻止用户

使用 Openfire 服务器和 XMPP(SMACK)客户端的 Android 聊天应用程序 [关闭]

Android 基于XMPP Smack openfire 开发的聊天室

Android基于XMPP Smack及Openfire学习笔记

如何使用 Smack 在 XMPP openfire 中了解打字状态

如何使用 Smack 在 XMPP openfire 中了解打字状态