【XMPP】Smack源码之消息接收与解析
XmpPullParser
鉴于xmpp协议都是以xml格式来传输,因此源码中解析协议都是用到XmpPullParser来解析xml
XmpPullParser很简单,先简单介绍几个比较常用的方法
//定义一个事件采用回调方式,直到xml完毕
public int getEventType() throws XmlPullParserException ;
//遍历下一个事件,返回一个事件类型
public int next() throws XmlPullParserException, IOException
//得到当前tag的名字
public String getName();
//获得当前文本
public String getText();
//当前tag下的属性数量
public int getAttributeCount() ;
//获得当前指定属性位置的名称
public String getAttributeName(int index);
//获得当前指定属性位置的值
public String getAttributeValue(int index);
为了更好的理解后面的源码,加一段代码来分析:
public PacketExtension parseExtension(XmlPullParser parser) throws Exception {
MUCUser mucUser = new MUCUser();
boolean done = false;
while (!done) {
int eventType = parser.next();
if (eventType == XmlPullParser.START_TAG) {
if (parser.getName().equals("invite")) {
mucUser.setInvite(parseInvite(parser));
}
if (parser.getName().equals("item")) {
mucUser.setItem(parseItem(parser));
}
if (parser.getName().equals("password")) {
mucUser.setPassword(parser.nextText());
}
if (parser.getName().equals("status")) {
mucUser.setStatus(new MUCUser.Status(parser.getAttributeValue("", "code")));
}
if (parser.getName().equals("decline")) {
mucUser.setDecline(parseDecline(parser));
}
if (parser.getName().equals("destroy")) {
mucUser.setDestroy(parseDestroy(parser));
}
}
else if (eventType == XmlPullParser.END_TAG) {
if (parser.getName().equals("x")) {
done = true;
}
}
}
return mucUser;
}
里面的对象先不用理它,只需看他是如何分析这段xml的:
//协议解释,从123456789发送一段协议给12345678这个用户,邀请用户123456789进入房间,理由hi join us。
<message id="WEzG6-11" to="123456789@xxx-pc/Smack" from="12345678@xxx-pc/Smack" type="get">
<x xmlns="http://jabber.org/protocol/muc#user">
<invite to="123456789@xxx-pc">
<reason>hi join us</reason>
</invite>
</x>
</message>
parser.next();
- 获得第一个事件,判断是否开始标签(XmlPullParser.START_TAG)
- 然后再里面判断每个标签的名字
- 处理完后判断结尾标签(XmlPullParser.END_TAG)是否需要结束本次循环。
//取xmlpullparse对象
- XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
- XmlPullParser parser = factory.newPullParser();
//设置关联数据源
parser.setInput(reader);
接收消息以及如何解析消息
- 在android里面用的smack包其实叫做asmack,该包提供了两种不同的连接方式:socket和httpclient。
- 该包并且提供了很多操作xmpp协议的API,也方便各种不同自定义协议的扩展。
- 我们不需要自己重新去定义一套接收机制来扩展新的协议,只需继承然后在类里处理自己的协议就可以了。
总的思路
- 使用socket连接服务器
- 将XmlPullParser的数据源关联到socket的InputStream
- 启动线程不断循环处理消息
- 将接收到的消息解析xml处理封装好成一个Packet包
- 将包广播给所有注册事件监听的类
逐步击破
先理解一下smack的使用,看下smack类图
下图只显示解释所要用到的类和方法,减缩了一些跟本文主题无关的代码,只留一条贯穿着从建立连接到接收消息的线。

1.解析这块东西打算从最初的调用开始作为入口,抽丝剥茧,逐步揭开。
PacketListener packetListener = new PacketListener() {
@Override
public void processPacket(Packet packet) {
System.out.println("Activity----processPacket" + packet.toXML());
}
};
PacketFilter packetFilter = new PacketFilter() {
@Override
public boolean accept(Packet packet) {
System.out.println("Activity----accept" + packet.toXML());
return true;
}
};
解释:创建包的监听以及包的过滤,当有消息到时就会广播到所有注册的监听,当然前提是要通过packetFilter的过滤。
2.在这构造函数里面主要配置ip地址和端口
super(new ConnectionConfiguration("169.254.141.109", 9991));
3.注册监听,开始初始化连接。
connection.addPacketListener(packetListener, packetFilter); connection.connect();
4.通过之前设置的ip和端口,建立socket对象
public void connect() {
// Stablishes the connection, readers and writers
connectUsingConfiguration(config);
}
private void connectUsingConfiguration(ConnectionConfiguration config) {
String host = config.getHost();
int port = config.getPort();
try {
this.socket = new Socket(host, port);
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
initConnection();
}
5.建立reader和writer的对象关联到socket的InputStream
private void initReaderAndWriter() {
try {
reader = new BufferedReader(new InputStreamReader(socket
.getInputStream(), "UTF-8"));
} catch (UnsupportedEncodingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
initDebugger();
}
6.实例化ConsoleDebugger,该类主要是打印出接收到的消息,给reader设置了一个消息的监听
protected void initDebugger() {
Class<?> debuggerClass = null;
try {
debuggerClass = Class.forName("com.simualteSmack.ConsoleDebugger");
Constructor<?> constructor = debuggerClass.getConstructor(
Connection.class, Writer.class, Reader.class);
debugger = (SmackDebugger) constructor.newInstance(this, writer,
reader);
reader = debugger.getReader();
} catch (ClassNotFoundException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
} catch (Exception e) {
throw new IllegalArgumentException(
"Can't initialize the configured debugger!", e);
}
}
7.接着建立PacketReader对象,并启动。PacketReader主要负责消息的处理和通知
private void initConnection() {
// Set the reader and writer instance variables
initReaderAndWriter();
packetReader = new PacketReader(this);
addPacketListener(debugger.getReaderListener(), null);
// Start the packet reader. The startup() method will block until we
// get an opening stream packet back from server.
packetReader.startup();
}
看看PacketReader类
public class PacketReader {
private ExecutorService listenerExecutor;
private boolean done;
private XMPPConnection connection;
private XmlPullParser parser;
private Thread readerThread;
protected PacketReader(final XMPPConnection connection) {
this.connection = connection;
this.init();
}
/**
* Initializes the reader in order to be used. The reader is initialized
* during the first connection and when reconnecting due to an abruptly
* disconnection.
*/
protected void init() {
done = false;
readerThread = new Thread() {
public void run() {
parsePackets(this);
}
};
readerThread.setName("Smack Packet Reader ");
readerThread.setDaemon(true);
// create an executor to deliver incoming packets to listeners.
// we will use a single thread with an unbounded queue.
listenerExecutor = Executors
.newSingleThreadExecutor(new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread thread = new Thread(r,
"smack listener processor");
thread.setDaemon(true);
return thread;
}
});
resetParser();
}
/**
* Starts the packet reader thread and returns once a connection to the
* server has been established. A connection will be attempted for a maximum
* of five seconds. An XMPPException will be thrown if the connection fails.
*
*/
public void startup() {
readerThread.start();
}
/**
* Shuts the packet reader down.
*/
public void shutdown() {
done = true;
// Shut down the listener executor.
listenerExecutor.shutdown();
}
private void resetParser() {
try {
parser = XmlPullParserFactory.newInstance().newPullParser();
parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
parser.setInput(connection.reader);
} catch (XmlPullParserException xppe) {
xppe.printStackTrace();
}
}
/**
* Parse top-level packets in order to process them further.
*
* @param thread
* the thread that is being used by the reader to parse incoming
* packets.
*/
private void parsePackets(Thread thread) {
try {
int eventType = parser.getEventType();
do {
if (eventType == XmlPullParser.START_TAG) {
if (parser.getName().equals("message")) {
processPacket(PacketParserUtils.parseMessage(parser));
}
System.out.println("START_TAG");
} else if (eventType == XmlPullParser.END_TAG) {
System.out.println("END_TAG");
}
eventType = parser.next();
} while (!done && eventType != XmlPullParser.END_DOCUMENT
&& thread == readerThread);
} catch (Exception e) {
e.printStackTrace();
if (!done) {
}
}
}
private void processPacket(Packet packet) {
if (packet == null) {
return;
}
// Loop through all collectors and notify the appropriate ones.
for (PacketCollector collector : connection.getPacketCollectors()) {
collector.processPacket(packet);
}
// Deliver the incoming packet to listeners.
listenerExecutor.submit(new ListenerNotification(packet));
}
/**
* A runnable to notify all listeners of a packet.
*/
private class ListenerNotification implements Runnable {
private Packet packet;
public ListenerNotification(Packet packet) {
this.packet = packet;
}
public void run() {
for (ListenerWrapper listenerWrapper : connection.recvListeners
.values()) {
listenerWrapper.notifyListener(packet);
}
}
}
}
- 创建该类时就初始化线程和ExecutorService
- 接着调用resetParser() 方法为parser设置输入源(这里是重点,parser的数据都是通过这里获取)
- 调用startup启动线程,循环监听parser
- 如果接收到消息根据消息协议的不同将调用PacketParserUtils类里的不同方法,这里调用parseMessage()该方法主要处理message的消息,在该方法里分析message消息并返回packet包。
- 返回的包将调用processPacket方法,先通知所有注册了PacketCollector的监听,接着消息(listenerExecutor.submit(new ListenerNotification(packet)); )传递给所有注册了PacketListener的监听。
- 这样在activity开始之前注册的那个监听事件就会触发,从而完成了整个流程。
辅助包
比如PacketCollector 这个类,它的用处主要用来处理一些需要在发送后需要等待一个答复这样的请求。
protected synchronized void processPacket(Packet packet) {
System.out.println("PacketCollector---processPacket");
if (packet == null) {
return;
}
if (packetFilter == null || packetFilter.accept(packet)) {
while (!resultQueue.offer(packet)) {
resultQueue.poll();
}
}
}
该方法就是将获取到的包,先过滤然后放到队列里,最后通过nextResult来获取包,这样就完成一个请求收一个答复。
public Packet nextResult(long timeout) {
long endTime = System.currentTimeMillis() + timeout;
System.out.println("nextResult");
do {
try {
return resultQueue.poll(timeout, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) { /* ignore */
}
} while (System.currentTimeMillis() < endTime);
return null;
}
这样整个流程就完成了,最后总结一下,如图

参考文章
http://www.cnblogs.com/not-code/archive/2011/08/01/2124340.html
【XMPP】Smack源码之消息接收与解析的更多相关文章
- [源码分析] 消息队列 Kombu 之 启动过程
[源码分析] 消息队列 Kombu 之 启动过程 0x00 摘要 本系列我们介绍消息队列 Kombu.Kombu 的定位是一个兼容 AMQP 协议的消息队列抽象.通过本文,大家可以了解 Kombu 是 ...
- [源码分析] 消息队列 Kombu 之 Consumer
[源码分析] 消息队列 Kombu 之 Consumer 目录 [源码分析] 消息队列 Kombu 之 Consumer 0x00 摘要 0x01 综述功能 0x02 示例代码 0x03 定义 3.1 ...
- [源码分析] 消息队列 Kombu 之 Producer
[源码分析] 消息队列 Kombu 之 Producer 目录 [源码分析] 消息队列 Kombu 之 Producer 0x00 摘要 0x01 示例代码 0x02 来由 0x03 建立 3.1 定 ...
- [源码分析] 消息队列 Kombu 之 Hub
[源码分析] 消息队列 Kombu 之 Hub 0x00 摘要 本系列我们介绍消息队列 Kombu.Kombu 的定位是一个兼容 AMQP 协议的消息队列抽象.通过本文,大家可以了解 Kombu 中的 ...
- 鸿蒙内核源码分析(消息队列篇) | 进程间如何异步传递大数据 | 百篇博客分析OpenHarmony源码 | v33.02
百篇博客系列篇.本篇为: v33.xx 鸿蒙内核源码分析(消息队列篇) | 进程间如何异步传递大数据 | 51.c.h .o 进程通讯相关篇为: v26.xx 鸿蒙内核源码分析(自旋锁篇) | 自旋锁 ...
- v77.01 鸿蒙内核源码分析(消息封装篇) | 剖析LiteIpc(上)进程通讯内容 | 新的一年祝大家生龙活虎 虎虎生威
百篇博客分析|本篇为:(消息封装篇) | 剖析LiteIpc进程通讯内容 进程通讯相关篇为: v26.08 鸿蒙内核源码分析(自旋锁) | 当立贞节牌坊的好同志 v27.05 鸿蒙内核源码分析(互斥锁 ...
- v78.01 鸿蒙内核源码分析(消息映射篇) | 剖析LiteIpc(下)进程通讯机制 | 百篇博客分析OpenHarmony源码
百篇博客分析|本篇为:(消息映射篇) | 剖析LiteIpc(下)进程通讯机制 进程通讯相关篇为: v26.08 鸿蒙内核源码分析(自旋锁) | 当立贞节牌坊的好同志 v27.05 鸿蒙内核源码分析( ...
- [源码分析] 消息队列 Kombu 之 mailbox
[源码分析] 消息队列 Kombu 之 mailbox 0x00 摘要 本系列我们介绍消息队列 Kombu.Kombu 的定位是一个兼容 AMQP 协议的消息队列抽象.通过本文,大家可以了解 Komb ...
- Spring源码情操陶冶#task:scheduled-tasks解析器
承接前文Spring源码情操陶冶#task:executor解析器,在前文基础上解析我们常用的spring中的定时任务的节点配置.备注:此文建立在spring的4.2.3.RELEASE版本 附例 S ...
随机推荐
- python下sqlite增删查改方法(转)
sqlite读写 #coding=utf-8 import sqlite3 import os #创建数据库和游标 if os.path.exists(' test.db'): conn=sqli ...
- UVA 129困难的串【DFS】
题目链接 题目大意: 给出n,l:要求按特定格式输出由前l个大写字母构成的按字母表排列的第n个没有连续重复子串的字符串以及该字符串长度. 此题是一道dfs递归回溯的基础题,难点在于对当前字符串是否有连 ...
- golang实现分布式缓存笔记(一)基于http的缓存服务
目录 前言 cache 缓存服务接口 cache包实现 golang http包使用介绍 hello.go Redirect.go http-cache-server 实现 cacheHandler ...
- 10.30 正睿停课训练 Day12
目录 2018.10.30 正睿停课训练 Day12 A 强军战歌(DP 树状数组 容斥) B 当那一天来临(思路) C 假如战争今天爆发(贪心) 考试代码 B C 2018.10.30 正睿停课训练 ...
- Servlet中的过滤器
在web.xml中配置:(用eclipse工具,可以在创建filter的时选择,web.xml中的配置可以自动生成) <filter> <display-name>LoginF ...
- 并查集 (Union-Find Sets)及其应用
定义 并查集是一种树型的数据结构,用于处理一些不相交集合(Disjoint Sets)的合并及查询问题.常常在使用中以森林来表示. 集就是让每个元素构成一个单元素的集合,也就是按一定顺序将属于同一组的 ...
- STL——map/unordered_map基础用法
map /multimap map是STL里重要容器之一. 它的特性总结来讲就是:所有元素都会根据元素的键值key自动排序(也可根据自定义的仿函数进行自定义排序),其中的每个元素都是<key, ...
- js实现的map方法
/** * * 描述:js实现的map方法 * @returns {Map} */ function Map(){ var struct = function(key, value) { this.k ...
- Array and Linkedlist区别与操作的时间复杂度(转载)
转载自:http://blog.csdn.net/tm_wb/article/details/6319146 数组链表堆栈和队列 数组链表堆栈和队列是最基本的数据结构,任何程序都会涉及到其中的一种或多 ...
- A very simple C++ module to encrypt/decrypt strings based on B64 and Vigenere ciper.
A very simple C++ module to encrypt/decrypt strings based on B64 and Vigenere ciper. https://github. ...