XMPP、BOSH、ejabberd、Openfire、Strophe

2,104 阅读5分钟

什么是BOSH

BOSH即(Bidirectional-streams Over Synchronous HTTP),是一种传输协议。它可以利用同步的HTTP协议模拟两个实体(如客户端--服务器端)双向流传输,而不需要轮询或者异步组件。

XMPP基于BOSH定义了相关标准,用于XMPP数据传输。这个就解决方案主要是为了解决HTTP协议无法保持TCP长连接的问题。根据BOSH的说法就是客户端向服务器发起请求之后,服务器如果当前没有数据的话,就hold住这个请求,不立即返回,当有数据的时候,再返回此次请求.怎样hold住就是问题的关键了。直接把请求的当前线程挂起是不行的,应该有一种异步处理机制

什么是XMPP

XMPP即(Extensible Messaging and Presence Protocol,可扩展消息处理和现场协议) ,是一种网络即时通讯协议,它基于XML,具有很强的扩展性,被广泛使用在即时通讯软件、网络游戏聊天、Web聊天及Web消息推送、联网的实时文档协作工具、移动设备的消息推送等场景

由于在Web浏览器上的JavaScript不能直接处理TCP协议,所以XMPP服务器通常会提供BOSH接口,通过HTTP长轮训(long-polling)可以实现Web浏览器即时聊天。Strophe.js是一个通过BOSH连接Web浏览器和XMPP服务器的工具库。

XMPP服务器和客户端之间,是通过XML节来进行通讯。其中有三种非常重要的XML节类型:<message>、<presence>、<iq>

<message>:

用于从一个实体向另外一个实体发送消息,并可以传输任何类型的结构化信息,不保证传输可靠性。聊天消息的发送和接收就是通过message节来实现。例如xxg1@host发送一条信息"你好"给xxg2@hostxxg1@host客户端会将下面的这段XML发送到XMPP服务器,服务器再推送给xxg2@host客户端。其中<message>的from属性是发送者,to属性是接收者,<body>子元素的内容就是聊天信息。type属性,私聊是"chat",而群聊是"groupchat","headline"用于发送警告或通知,"normal"类似于email,发出后不等待回应,"error"发送错误信息

<message from="xxg1@host" to="xxg2@host" type="chat">
    <body>你好</body>
</message>

<presence>:

presence提供网络实体的可访问性。用户发出presence节,表明自己上线,这样可以会有更大的概率与别人通信(人们更愿意与在线的人交流),但是我们也不用担心任何人都可以看到自己的在线状态,除非我们订阅了该用户的状态,订阅之后,用户的状态信息会自动发送到订阅者处。实际上,XMPP的presence节是一个简单的专用的发布-订阅方法。

在IM中,presence体现在花名册(roster)中,花名册保存有JID列表以及用户与这些JID的订阅关系,一旦上线,用户发送presence节,剩下的就由服务器处理了(通知自己在线,以及获取联系人的状态信息)

可用于表明用户的状态,例如用户状态改变成“Do not disturb”(“请勿打扰”),会向服务器发送:

<presence from="xxg@host">
    <status>Do not disturb</status>
    <priority>0</priority>
    <show>dnd</show>
</presence>

<iq>:

iq即Info/Query,采用“请求-响应”机制,类似于HTTP的机制。下面的例子是客户端通过<iq>请求获取联系人、回复心跳等,XMPP服务器将结果返回:

<iq from='xxg@host' id='bv1bs71f' type='get'>
    <query xmlns='jabber:iq:roster'/>
</iq>

服务器结果返回:

<iq to='xxg@host' id='bv1bs71f' type='result'>
    <query xmlns='jabber:iq:roster'>
        <item jid='xxg2@host'/>
        <item jid='xxg3@host'/>
    </query>
</iq>

搭建XMPP服务器

在实现Web浏览器聊天之前,首先要搭建一个XMPP服务器。例如Openfire、Tigase、Ejabberd是常用的XMPP服务器。其中Openfire、Tigase是基于Java实现,Ejabberd是Erlang实现。虽然实现的语言不同,但是都遵循XMPP协议,所以使用其中任意一个XMPP服务器即可。都有后台管理系统去设置一些配置。还有Spark客户端

  • ejabberd:ejabberd 中的 e 指的是 Erlang,一种软实时编程语言。这一技术基石使 ejabberd 非常快。它还与 XMPP 核心和相关标准高度兼容。ejabberd 可以安装在大多数环境中。
  • Openfire:Openfire 用 Java语言编写,用户友好,安装方便。
  • Tigase:Linux搭建XMPP服务器Tigase(Spark客户端测试)

Strophe

Strophe.js是为XMPP写的一个js类库。因为http协议本身不能实现持久连接,所以strophe利用BOSH模拟实现持久连接。

<!DOCTYPE html>
<html>
<head>
    <script src='http://cdn.bootcss.com/jquery/1.9.1/jquery.min.js'></script>
    <script src='http://cdn.bootcss.com/strophe.js/1.1.3/strophe.min.js'></script>
    <script src='test2.js'></script>
</head>
<body>
    JID:<input type="text" id="input-jid">
    <br>
    密码:<input type="password" id="input-pwd">
    <br>
    <button id="btn-login">登录</button>
    <div id="msg" style="height: 400px; width: 400px; overflow: scroll;"></div>
    <br>
    消息:
    <br>
    <textarea id="input-msg" cols="30" rows="4"></textarea>
    <br>
    <button id="btn-send">发送</button>
</body>
</html>
var BOSH_SERVICE = 'http://host:5280';// XMPP服务器BOSH地址
var ROOM_JID = 'xxgroom@conference.im.xxx.com.cn';// 房间JID
var connection = null;// XMPP连接

// 当前状态是否连接
var connected = false;
// 当前登录的JID
var jid = "";

// 连接状态改变的事件
function onConnect(status) {
    if (status == Strophe.Status.CONNFAIL) {
        alert("连接失败!");
    } else if (status == Strophe.Status.AUTHFAIL) {
        alert("登录失败!");
    } else if (status == Strophe.Status.DISCONNECTED) {
        alert("连接断开!");
        connected = false;
    } else if (status == Strophe.Status.CONNECTED) {
        alert("连接成功,可以开始聊天了!");
        connected = true;
        
        // 当接收到<message>节,调用onMessage回调函数
        connection.addHandler(onMessage, null, 'message', null, null, null);
        
        // 首先要发送一个<presence>给服务器(initial presence)
        connection.send($pres().tree());

        // 发送<presence>元素,加入房间
        connection.send($pres({
            from: jid,
            to: ROOM_JID + "/" + jid.substring(0,jid.indexOf("@"))
        }).c('x',{xmlns: 'http://jabber.org/protocol/muc'}).tree());
    }
}

// 接收到<message>
function onMessage(msg) {
    
    console.log(msg);
    // 解析出<message>的from、type属性,以及body子元素
    var from = msg.getAttribute('from');
    var type = msg.getAttribute('type');
    var elems = msg.getElementsByTagName('body');

    if (type == "groupchat" && elems.length > 0) {
        var body = elems[0];
        $("#msg").append(from.substring(from.indexOf('/') + 1) + ":<br>" + Strophe.getText(body) + "<br>")
    }
    return true;
}

$(document).ready(function() {
    // 通过BOSH连接XMPP服务器
    $('#btn-login').click(function() {
        if(!connected) {
            connection = new Strophe.Connection(BOSH_SERVICE);
            connection.connect($("#input-jid").val(), $("#input-pwd").val(), onConnect);
            jid = $("#input-jid").val();
        }
    });
    
    // 发送消息
    $("#btn-send").click(function() {
        if(connected) {

            // 创建一个<message>元素并发送
            var msg = $msg({
                to: ROOM_JID, 
                from: jid, 
                type: 'groupchat'
            }).c("body", null, $("#input-msg").val());
            connection.send(msg.tree());

            $("#input-msg").val('');
        } else {
            alert("请先登录!");
        }
    });
});

参考链接