XMPP XEP-0096协议是XMPP中的文件传输协议。
关于文件传输,在xmpp协议中有不少协议可以实现,而XEP-0096协议是其中非常简单的一个协议。由于邮件被删,我的代码demo丢失,因此只能在这里给大家讲一下其中的逻辑实现,大家可以以此来写出代码。
首先申明一下,以下是我个人对XEP-0096协议的一些认识和解释,如有疑问,请发邮件到lizhanzhishang@gmail.com ,欢迎交流~
我们根据openfire服务器做开发,但是服务器在这里只是起路由寻址和转发的作用,实质上是完全点对点的通信,数据处理由客户端来做。
我们可以举一个栗子,有两部手机,互相之间使用message协议传递信息的完整message XML数据,可以看看一个客户端发送的是什么信息,另一个客户端接收的又是什么message信息,要是一样,说明的是服务器是转发的数据,要是不一样的 话,则可能带有IQ ,MSG,Pr信息。(但是总是有例外的,服务器也要对连接的数据做一些心跳包。)
下面是一组请求(一方发出“发送文件”请求,接收方发出“拒绝接受”请求):
这是”发送文件”的一段数据请求,(图片小,可以双击放大)
将接受方拒绝接受文件的信息返回给发送方:
就此请求完毕,一方发出了“发送文件”的请求,另一方“拒绝”了这个请求。
这里有一个完整的数据传输协议,在官方的文档上是这样写的:
In order to enable seamless file transfer and appropriate fall-back mechanisms, implementations of this profile MUST support both SOCKS5 Bytestreams (XEP-0065) [4] and In-Band Bytestreams (XEP-0047) [5], to be preferred in that order. The associated namespaces are to be included as option values for the "stream-method" variable as shown in the examples below.
上面的意思是说文件传输实际上是有协议XEP-0065和XEP-0047来进行的,而XEP-0096实际是传输IQ的消息协议,真正的传输数据并不 在次协议中,实现文件传输协议必须是full JID而且要求接收方在线。如果”to”(接收端), ”from”(发送端) 不是一个full JID,服务器就会发送error信息到”发送端”,说明服务器现在出现异常问题。
现在我们来说说在传输消息的时候,该怎么具体组织消息:
我们可以用IQ或者Message来发送数据。下面是我截取的文档中我觉得相当重要的部分:
Each chunk of data is contained in a <data/> element qualified by the 'http://jabber.org/protocol/ibb' namespace. The data element SHOULD be sent in an IQ stanza to enable proper tracking and throttling, but instead MAY be sent in a message stanza. The data to be sent, prior to base64-encoding and prior to any wrapping in XML, MUST NOT be larger than the 'block-size' determined in the bytestream negotiation.
上面说啥呢,我英语也菜,但是能看懂一点2个关键点,一个是base64-encoding,block-size这个2个关键的单词,说明我们在遇到 大文件的情况下是进行分块发送的,每块的数据都是一定字节的,例如,我们发送数据1024字节,但是那个流怎么发送呢,都是二进制的。我们直接发送呢,数 据太大,不好整的,再则直接转String呢,那也是有问题的,因为String遇到’\o’就说明数据结束,会使数据漏掉很多。 文档中告诉我们,将发送的文件转换为base64之后再转为String类型,放入IQ或者Message数据格式中发送。关于base64的理论资料在 这里:http://zh.wikipedia.org/zh-cn/Base64=
下面是一个数据实例:
参考的url地址:http://xmpp.org/extensions/xep-0096.html
Use of Message Stanzas
It is RECOMMENDED to use IQ stanzas when sending data packets. However, an application MAY use message stanzas instead. If message stanzas are used when sending data packets, the sender SHOULD also useAdvanced Message Processing (XEP-0079) [8] or some other stanza flow-control method. For proper tracking of delivery and processing errors related to data packets, the 'id' attribute SHOULD be used with message stanzas.
上面的base64数据不一定用IQ发送,还可以用Message发送。只是在参数上有个改变一下就可以,哈哈。
例子如下所示:
突然想起一件事情。。。在发送文件的时候,会有一个带<si >标签的数据段,这个数据段有id。这个id很重要,是必须要保存的。主要用在传输数据的时候,要是这个sid要是没有带上,那服务器就会返回错误信息给文件发送方。
还有个小事情,我当初解析命名空间的时候,以为是一般属性,总是解析不出来。
最后发现有专门解析命名空间的东西,大家可以去google下。。。
下面是进行文件传输的XEP-0096协议的完整xml例子:
数据协议都是人定的,哈哈
文件发送方:
我们可以对此IQ数据段解析,当发现file 标签的命名空间是http://jabber.org/protocol/si/profile/file-transfer的 时候 ,则表明这是“文件传输”消息请求。说明有人要传文件了。对于这个xml请求,我们在提取数据的时候必须提取<si>标签的ID。这个ID很 重要,是下面的sid传输数据的重要参数,也是判断是否是同一个数据流的依据。还有就是提取mine-type标签内容,这个是我们用来判断接收的是何种 文件,并以此判断创建该类型文件,把将要传来的数据写入这个文件,还有就是size标签内容,来检验我们接收的文件是否完整。
下面是一个完整的没有进行base64转换的数据请求:
文件接收方:
这里说明,我要接受的数据是什么协议,主要在file var这个参数:stream-method
<x submit>这个表明我要接受数据,你可以发给我了。
发送文件方:
其实也要实现这个xml的,我不知道,当我接受数据之后,为什么要查询对方的机器名字,和一些基本数据,这可能是进行确认。
接受方发出本机基本信息,主要有3个参数,category="client" name="Smack" type="pc"
查询之后就开始要发数据了,哈哈。
发出数据肯定要打开流,发送数据结束也要关闭流的,哈哈
发送方发送打开消息:
这个xml虽然很少,但是每个数据都很重要呢。Open的命名空间很熟悉那。其实这个就是前面刚开始接受数据file里的value,block- size说的是,我每次传数据都是以4096字节发送一个数据流。 这个sid就是发送方发送请求文件的那个<si>标签的id。都是相同的,同时还有一个重要的东西,stanza="iq"这个,相当的重 要,这个标签告诉对方,我要以IQ数据类型发送数据,这里也可以用message代替iq。前面我已经提及到。
下面的是message发送base64数据,如果stanza="message” 自己可以参照IQ发送base64数据。过程基本一样,就是iq变成message而已:
接受方数据xml:
上面的2个IQ是顺序发送的,不可逆,第一个是主要告诉对方。同时,那3个参数是上上面的一样的,要不是无法接收到额,还有一些对特征解释,说明传输的可以哪些流、
然后紧接着告诉对方,我可以解释数据了。
发送方开始发数据了,哈哈:
这个data的value,是前面的数据块,4096字节base64加密数据。如果数据base64大于这个块。我们接到这个数据首先要base64解密之后,再写入刚才建立的文件中,
Sid是上面我们说的第一次si 标签的id。不能改变,更不能为null。也不要不设置,不然服务器就会返回错误信息。 Seq是从0开始的,如果还有流就会依次递增的,
如果接受处理完毕,就发送一个iq。说明接受完成
说明一下哈,data里的数据被我删掉N多,我这主要是说明用。
接受方:
发一个一个4096字节base64加密数据。这个是源文件base64加密偏移的数据。
上面的seq变成1了,而sid依然没变。接受完成处理后,一样要回复一下,告诉他,可以继续传数据了。
最后就是base64加密完成传输,要colse掉数据。
发送方关闭流,这个sid依旧没有变,协议是这样规定的:
好吧,我知道你关闭流了。我们之间的文件传输完成了:
写了几个小时分析,真的累。哎,下面贴出完整的流程xml
下面是整个对话阶段的xml数据:
A:发送方A:
B:接收方
<iq id="x36vr-54" to="zhufu@domian/android" from="saonian@domian/android" type="set">
<si xmlns="http://jabber.org/protocol/si" id="jsi_3326887048779603188" mime-type="image/png" profile="http://jabber.org/protocol/si/profile/file-transfer">
<file xmlns="http://jabber.org/protocol/si/profile/file-transfer" name="image_9T.png" size="5204">
<desc>Sending file</desc>
</file>
<feature xmlns="http://jabber.org/protocol/feature-neg">
<x xmlns="jabber:x:data" type="form">
<field var="stream-method" type="list-single">
<option>
<value>http://jabber.org/protocol/bytestreams</value>
</option>
<option>
<value>http://jabber.org/protocol/ibb</value>
</option>
</field>
</x>
</feature>
</si>
</iq>
复制代码
B:
<iq id="x36vr-54" to="saonian@domian/android" from="zhufu@domian/android" type="result">
<si xmlns="http://jabber.org/protocol/si">
<feature xmlns="http://jabber.org/protocol/feature-neg">
<x xmlns="jabber:x:data" type="submit">
<field var="stream-method">
<value>http://jabber.org/protocol/bytestreams</value>
<value>http://jabber.org/protocol/ibb</value>
</field>
</x>
</feature>
</si>
</iq>
复制代码
A:
<iq id="x36vr-55" to="zhufu@domian/android" type="get" from="saonian@domian/android">
<query xmlns="http://jabber.org/protocol/disco#info"></query>
</iq>
复制代码
B:
<iq id="x36vr-55" to="saonian@domian/android" type="result" from="zhufu@domian/android">
<query xmlns="http://jabber.org/protocol/disco#info">
<identity category="client" name="Smack" type="pc"/>
<feature var="http://www.xmpp.org/extensions/xep-0166.html#ns"/>
<feature var="urn:xmpp:tmp:jingle"/>
</query>
</iq>
复制代码
A:
<iq id="x36vr-56" to="zhufu@domian/android" type="set" from="saonian@domian/android">
<open xmlns="http://jabber.org/protocol/ibb" block-size="4096" sid="jsi_3326887048779603188" stanza="iq">
</open>
</iq>
复制代码
B:
<iq id="x36vr-55" to="saonian@domian/android" type="result" from="zhufu@domian/android">
<query xmlns="http://jabber.org/protocol/disco#info">
<identity category="client" name="Smack" type="pc"/>
<feature var="http://jabber.org/protocol/xhtml-im"/>
<feature var="http://jabber.org/protocol/muc"/>
<feature var="http://jabber.org/protocol/bytestreams"/>
<feature var="http://jabber.org/protocol/commands"/>
<feature var="http://jabber.org/protocol/si/profile/file-transfer"/>
<feature var="http://jabber.org/protocol/si"/>
<feature var="http://jabber.org/protocol/ibb"/>
</query>
</iq>
复制代码
B:
<iq id="x36vr-56" to="saonian@domian/android" from="zhufu@domian/android" type="result"/>
复制代码
A:
<iq id="x36vr-57" to="zhufu@domian/android" type="set" from="saonian@domian/android">
<data xmlns="http://jabber.org/protocol/ibb" seq="0" sid="jsi_3326887048779603188">iVBORw0KGgoAAAANSUhEUgAAAEMAA
</data>
</iq>
复制代码
B:
<iq id="x36vr-57" to="saonian@domian/android" from="zhufu@domian/android" type="result"/>
复制代码
A:
<iq id="x36vr-58" to="zhufu@domian/android" type="set" from="saonian@domian/android">
<data xmlns="http://jabber.org/protocol/ibb" seq="1" sid="jsi_3326887048779603188">dcwRxoSYEFYOgxc0Qx2TSCNDSJZRTxKFgVmZiKFN</data>
</iq>
复制代码
B:
<iq id="x36vr-58" to="saonian@domian/android" from="zhufu@domian/android" type="result"/>
复制代码
A:
<iq id="x36vr-59" to="zhufu@domian/android" type="set" from="saonian@domian/android">
<close xmlns="http://jabber.org/protocol/ibb" sid="jsi_3326887048779603188"/>
</iq>
复制代码
B:
<iq id="x36vr-59" to="saonian@domian/android" from="zhufu@domian/android" type="result"/>
复制代码