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

一、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);
    }
  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值