Android基于XMPP协议之实现即时通讯的原理

时间:2023-02-09 13:20:20

一、xmpp协议

xmpp可以理解为可扩展的消息和出席协议(eXtensible Messageing and Presence Protocol).出席即可以理解为用户的在线的状态,消息则是服务器与客户端互相通信的消息;常见的xmpp服务器有openfire、Ejabberd等,这里我们用的是openfire;

二、xmpp寻址(jid)

xmpp在网络上通信的每个实体都有统一的ID表示,即为JID(jabber identifier);JID有很多不同的形式,通常类似于邮件的形式;

JID=[ node”@” ] domain [ “/” resource ]
node: 用户名
domain:服务器域名itcast.cn
resource:属于用户的位置或设备

一个用户可以同时以多种资源与一个服务器连接
laowang@itcast.cn/spark
jige@itcast.cn/smack

三、xmpp的传输数据格式

在xmpp中,数据的传输和识别是通过在一个xmpp流上发送和接收xmpp节点完成的,这三个节点分别是presence、message、iq;这三个节点都有通用的属性from、to、type、id;xmpp传输数据也是通过xml文档实现的,该文档始终有一个根节点stream;下面给出一个简单的消息的例子:

<stream:stream>
<iq type='get'>
<query xmlns='jabber:iq:roster'/>
</iq>
<presence type=’available’>
<message from='laowang@itcast.cn' />
</stream:stream>

四、presence节点

presence节点主要显示该用户的在线状态和与其相关的好友的状态设置,控制并且报告JID的可访问性

Mode: 在线chat、离线away 、离开xa、请勿打扰dnd
Type:available, unavailable,subscribe,subscribed,unsubscribe,Unsubscribed,error

普通presence节
普通presence节不含type属性,或者type属性值为unavailable或error。type属性没有available值,因为可以通过缺少type属性来指出这种情况。
用户通过发送不带to属性,直接发往服务器的presence节来操纵自己的出席状态。

<presence/>
<presence type='unavailable'/>
<presence>
<show>away</show>
<status>at the ball</status>
<presence/>
<presence>
<status>touring the countryside</status>
<priority>10</priority>
</presence>
<presence>
<priority>10</priority>
</presence>

简单解释:
(1)show元素用来传达用户的可访问性,只能出现在presence节中。该元素的值可以为:away、chat、dnd和xa,分别表示离开、有意聊天、不希望被打扰和长期离开。
(2)status元素时一个人类可读的字符串,在接收者的聊天客户端中,这个字符串一般会紧挨着联系人名字显示。
(3)priority元素用来指明连接资源的优先级,介于-128~127,默认值为0。

五、message节点

message节是一个实体向另一个实体发送消息;消息类型有不同的type,例如chat、error、normal、groupchat等等;如果没有指定type,默认就是normal;尽管message节可以包含任意扩展元素,但body和thread元素是为向消息中添加内容提供的正常机制。这两种子元素均是可选的。

六、IQ节点

IQ节点表示的是info/query,为xmpp通信提供了请求和响应的机制;它与http协议的基本工作原理很相似,允许获取和设置查询,与http的get和post请求类似,它有两种请求两种响应,如下:
Get: 获取当前域值
Set: 设置或替换get查询的值
Result: 说明成功的响应了先前的查询
Error: 查询和响应中出现的错误

七、连接生命周期

在开始发送任何节之前,必须建立xmpp流,必须先建立通往xmpp服务器的socket连接,建立通往xmpp服务器的连接,xmpp流就启动了。通过向服务器发送起始元素stream节,就可打开xmpp流。服务器通过发送响应流起始标记stream进行相应,一旦双向建立xmpp流,就可以来回发送各种元素。当用户结束xmpp会话时,会终止会话并断开连接。一般终止会话方式是,首先发送无效出席信息,然后关闭stream元素。

xmpp的通信流程

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

八、简单的好友聊天功能实现

1>.连接服务器,建立连接

这里将与服务器的连接封装一个类,用户将连接、xmpp对象和方法的获取等功能封装在一起,ConnectionConfiguration用户获取和openfire服务器的连接,XMPPConnection获取xmpp类,以便于做出连接的操作,还有通过XMPPConnection调用登录方法,调用获取好友列表的方法getRoster,获取聊天的管理类ChatManager,监听器监听来自不同好友的消息addPacketListener,下线操作disconnect, 具体我们先来看看:

public class ConnectionManager {

private static final String HOST = "192.168.138.1";
private static final int PORT = 5222;
private ConnectionManager() {}
private static ConnectionManager sInstance = new ConnectionManager();
//当前用户的帐号
private long mAccount;
private ConnectionConfiguration mConfiguration;
private XMPPConnection mConnection;

public long getAccount() {
return mAccount;
}

public static ConnectionManager getInstance() {
return sInstance;
}

//通过XmppConnection去链接Openfire服务器
public void connect() throws Exception {
mConfiguration = new ConnectionConfiguration(HOST, PORT);
mConfiguration.setDebuggerEnabled(true);
mConnection = new XMPPConnection(mConfiguration);
mConnection.connect();
}

public void login(String account, String password) throws Exception {
mConnection.login(account, password);
}

//提供获取好友列表控制类的方法
public Roster getRoster() {
return mConnection.getRoster();
}

public ChatManager getChatManager() {
return mConnection.getChatManager();
}

//监听器监听来自不同好友的消息
public void addPacketListener(PacketListener packetListener,PacketFilter packetFilter){
mConnection.addPacketListener(packetListener, packetFilter);
}

//通知服务器下线
public void disConnect() {
mConnection.disconnect();
}
}

2>开启欢迎页调用连接的方法与服务器获取连接

ConnectionManager.getInstance().connect();
==========

//通过XmppConnection去链接Openfire服务器
public void connect() throws Exception {
mConfiguration = new ConnectionConfiguration(HOST, PORT);
mConfiguration.setDebuggerEnabled(true);
mConnection = new XMPPConnection(mConfiguration);
mConnection.connect();
}

3>连接成功后,前往登陆页登录,通过xmppconnection调用xmpp登录方法

mConnectionManager.login(account, password);
==========

public void login(String account, String password) throws Exception {
mConnection.login(account, password);
}

4>进入主界面,显示会话信息和联系人界面,先看联系人界面

//获取联系人数据源
mRoster = ConnectionManager.getInstance().getRoster();
//设置监听,添加或者删除好友后要刷新列表和数据
mRoster.addRosterListener(mRosterListener);
==========
private RosterListener mRosterListener = new RosterListener() {
//添加好友后
@Override
public void entriesAdded(Collection<String> addresses) {

Log.i(TAG, "entriesAdded");
for (String userJid : addresses) {
Log.i(TAG, userJid);
}

Utils.runInUIThread(new Runnable() {

@Override
public void run() {
setAdapter();
}
});
}
//更新好友后
@Override
public void entriesUpdated(Collection<String> addresses) {

Log.i(TAG, "entriesUpdated");
for (String userJid : addresses) {
Log.i(TAG, userJid);
}

Utils.runInUIThread(new Runnable() {

@Override
public void run() {
setAdapter();
}
});
}
//删除好友后
@Override
public void entriesDeleted(Collection<String> addresses) {

Log.i(TAG, "entriesDeleted");
for (String userJid : addresses) {
Log.i(TAG, userJid);
}

Utils.runInUIThread(new Runnable() {

@Override
public void run() {
setAdapter();
}
});
}
@Override
public void presenceChanged(Presence presence) {

Log.i(TAG, "presenceChanged");
if (presence != null) {
Log.i(TAG, presence.toXML());
}
}
};

5>会话界面,首先要显示会话信息,将收到的消息存入数据库,可以回显

mConnectionManager.addPacketListener(new PacketListener() {

@Override
public void processPacket(Packet packet) {
Message message = (Message) packet;
//好友发来的消息保存到数据库
String from = message.getFrom();
String userJid = from.substring(0, from.lastIndexOf("/"));
Msg msg = new Msg(Msg.TYPE_MSG_RECEIVE,userJid , message.getBody());
try {
MyDbUtils.saveMsg(msg);
} catch (Exception e) {
e.printStackTrace();
}
Utils.runInUIThread(new Runnable() {

@Override
public void run() {
setAdapter();
}
});
}
}, new PacketFilter() {
//消息过滤,只接受Message类型的packet
@Override
public boolean accept(Packet packet) {
return packet instanceof Message;
}
});
}

6>消息对话界面

进入消息对话洁面后,首先获取聊天的工具类Chat

//得到ChatManager去发起聊天,监听消息的到来
//mFriendUserJid为当前聊天对象的jid
mConnectionManager = ConnectionManager.getInstance();
//获取创建与当前好友对话的工具类
mChat = mChatManager.createChat(mFriendUserJid, mMessageListener);
//接受消息的监听,从好友哪里接受消息,processMessage----->被PacketReader调用
//只能接受当前mFriendUserJid发来的消息
private MessageListener mMessageListener = new MessageListener() {

@Override
public void processMessage(Chat chat, Message message) {
//Utils.showToast(getApplicationContext(), message.toXML());
Msg msg = new Msg(Msg.TYPE_MSG_RECEIVE,mFriendUserJid, message.getBody());
mMsges.add(msg);
//保存消息
try {
MyDbUtils.saveMsg(msg);
} catch (Exception e) {
e.printStackTrace();
}
Utils.runInUIThread(new Runnable() {
@Override
public void run() {
setAdapter();
}
});
}
};

然后是向当前对象发送消息

//发送消息
public void send(View view) {
final String content = mInput.getText().toString().trim();
if (TextUtils.isEmpty(content)) {
return;
}
mInput.setText("");
//消息类型,消息的to,消息from,消息内容,消息的发送时间
Utils.runInThread(new Runnable() {

@Override
public void run() {
try {
mChat.sendMessage(content);
} catch (Exception e) {
e.printStackTrace();
Utils.showToast(getApplicationContext(), "消息发送失败");
}
}
});
Msg msg = new Msg(Msg.TYPE_MSG_SEND,mFriendUserJid, content);
mMsges.add(msg);
try {
//存储消息到数据库方便回显
MyDbUtils.saveMsg(msg);
} catch (Exception e) {
e.printStackTrace();
}
setAdapter();
}

//在聊天界面退出的时候移除MessageListener
@Override
protected void onDestroy() {
super.onDestroy();
mChat.removeMessageListener(mMessageListener);
}