其实一直对即时通讯的东西很感兴趣,以前刚学编程的时候就一直想写个属于自己的聊天工具,技术有限、时间有限等等种种原因吧,反正一直没有納上日程。从去年开始了解xmpp这个开源的协议。由于公司项目要用这个功能,正好一块研究了,倒节省了我很多时间。
一、XMPP协议介绍(百科。。。)
XMPP是一种基于标准通用标记语言的子集XML的协议,它继承了在XML环境中灵活的发展性。因此,基于XMPP的应用具有超强的可扩展性。经过扩展以后的XMPP可以通过发送扩展的信息来处理用户的需求,以及在XMPP的顶端建立如内容发布系统和基于地址的服务等应用程序。而且,XMPP包含了针对服务器端的软件协议,使之能与另一个进行通话,这使得开发者更容易建立客户应用程序或给一个配好系统添加功能。
XMPP以Jabber协议为基础,而Jabber是即时通讯中常用的开放式协议。XMPP is the IETF's formalization of the base XML streaming protocols for instant messaging and presence developed within the Jabber open-source community in 1999。XMPP(可扩展消息处理现场协议)是基于可扩展标记语言(XML)的协议,它用于即时消息(IM)以及在线现场探测。它在促进服务器之间的准即时操作。这个协议可能最终允许因特网用户向因特网上的其他任何人发送即时消息,即使其操作系统和浏览器不同。
二、准备工作
1、搭建XMPP服务器openfire.Openfire是基于java的实现服务器,当然java环境是必不可少的咯。服务器的数据库采用的是轻量级mysql,体积小,安装快,使用方便,妥妥的好使。具体的搭建步骤百度下吧,教程还挺多的,差不多就一直下一步下一步下一步下一步。。。
2、下载android版xmpp封装源码asmack。由于asmack的底层通讯使用的httpclient,所以android6.0以上还需要HttpClient的jar包支持。百度云盘链接奉上如下:
http://pan.baidu.com/s/1kU9t2CJ
3、准备好多个xmpp客户端及源码(我这版完善了会提供下载源码,只不过要等等咯)。为什么要准备三个呢?因为我要实现各个平台客户端的统一,而且客户端的xmpp封装协议是需要自己封装和修改的,每家公司基于xmpp协议私有功能的实现肯定都是不一样的,基础的xmpp协议客户端封装还不具备图片、音频、视频的实现,所以,为了实现这些功能,开搞。我手里准备了三个客户端,ios,android和pc端。其中pc端采用的是spark。
三、协议分析
由于协议比较庞大,咱们直接从功能开始分析,首先是文字聊天,我用两个客户端发了一条消息,A发给B,协议内容如下:
<message id="3uCqB-7" to="admin@domain" from="liwy@domain" type=“chat” > <body>hello world</body> </message>
to 就是账号B
from 是发送者自己
body 里面就是发送的内容
type 是消息类型(normal,chat,group chat,headline,error)
这只是xmpp的一个基础协议,所有的客户端和服务器都要以此基础来实现,除非他们想改的昏天暗地。。其他功能也将在此协议的基础上进行添加和改进。
四、协议改装
今天就主要围绕着chat类型的消息做下改进。从上面这个简单协议可以看出功能的不完善,如果要发送一张图片,怎么样让对方知道body里是一张图片,而不是文字呢?这就是我们今天要做的。
1、 消息的发送
来,我们先看一段消息发送的代码,如下:public void sendMessage(String content) throws XMPPException { // Force the recipient, message type, and thread ID since the user elected // to send the message through this chat object. Message message = new Message(); message.setProperty(IMMessage.KEY_TIME, time); message.setBody(messageContent); message.setTo(participant); message.setType(Message.Type.chat); message.setThread(threadID); for(Map.Entry<PacketInterceptor, PacketFilter> interceptor : interceptors.entrySet()) { PacketFilter filter = interceptor.getValue(); if(filter != null && filter.accept(message)) { interceptor.getKey().interceptPacket(message); } } // Ensure that messages being sent have a proper FROM value if (message.getFrom() == null) { message.setFrom(connection.getUser()); } connection.sendPacket(message);}
Message对象就是对消息协议的一个封装,其继承自Packet。如果我们要修改其协议,那首先就是从这改起了,对,不要犹豫了,就是这。
2 修改消息封装类Packet和Message
首先我们要对消息的类型做一个分类,下面是我的一个简单分类(还不完善),在type类型的同级,新增一个contentType类型,用来标示消息的内容的类型,暂定分为:text、image、video、audio、file五种类型。好,下面开始操作:
在org.jivesoftware.smack.packet里的Packet文件里新增contentType属性(String),并实现其get\set方法。
重写Packet子类Message里的toXML()方法。在xml内容组装的代码段里将新增的属性组装到xml里,如下:
if (getContentType() != null){ buf.append(" contentType=\"").append(getContentType()).append("\""); }
然后就是发送的时候别忘喽给Message对象的contentType赋值。。。
还有一步比较关键,就是在接收的时候了。收到消息流之后需要将xml流解析为Message对象,既然我们新了一个属性,那我们解析的时候也要把这个属性解析到Message对象里啊,这就需要我们重写下org.jivesoftware.smack.util.PacketParserUtils里的parseMessage方法,新增一行代码:
message.setContentType(parser.getAttributeValue("","contentType"));到此,协议的更改就结束了,下面就是功能的具体实现了。
五、扩展
图片的发送可以将图片转换为base64字节码放在body内容里,然后contentType设置为image即可。同理,录音的实现也是如此。当然这也做有个一缺点,就是xmpp流的内容太大,用户量大的时候会对服务器造成比较大的负担,那我们可以这样解决:在xmpp的协议流里只发送文件在服务器的路径,然后文件上传到单独的一个文件服务器,这样用户会收到一条链接,点击再查看图片。当然服务器也可再改进一下,收到图片后服务器生成个缩略图并且优先发送到另一个客户端,点击了之后再查看原图片,貌似微信QQ的实现就是这样的。