基于XMPP协议的aSmack源码分析

      在研究如何实现Pushing功能期间,收集了很多关于Pushing的资料,其中有一个androidnp开源项目用的人比较多,但是由于长时间没有什么人去维护,听说bug的几率挺多的,为了以后自己的产品稳定些,所以就打算自己研究一下asmack的源码,自己做一个插件,androidnp移动端的源码中包含了一个叫做asmack的jar。

Reader和Writer

     在asmack中有两个非常重要的对象PacketReader和PacketWriter,那么从类名上看Packet + (Reader/Wirter),而TCP/IP传输的数据,叫做Packet(包),asmack使用的是XMPP协议,XMPP简单讲就是使用TCP/IP协议 + XML流协议的组合。所以这个了对象的作用从字面上看应该是,写包与读包,作用为从服务端读写数据。

     PacketWriter中一定含有一个Writer对象,这个Writer是一个输出流,同样的PacketReader对象中有一个Reader,而这个Reader是一个输入流,Writer和Reader对象就是一个简单的读写器,他们是从socket对象中获取出来后,经过装饰变成现在这个样子。

1 reader = new BufferedReader(new InputStreamReader(socket.getInputStream(), "UTF-8"));
2 writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream(), "UTF-8"));

没有什么神奇的地方,主要看PacketWriter/Reader,这两个对象分别把对应的Writer和Reader引用到自己的内部进行操作,下面就先看一个PacketWriter。

    /*** Creates a new packet writer with the specified connection.** @param connection the connection.*/protected PacketWriter(XMPPConnection connection) {this.queue = new ArrayBlockingQueue<Packet>(500, true);this.connection = connection;init();}

     还有就是PacketWriter初始化的时候将XMPPConnection对象传了进来,因为在init方法中使用到了XMPPConnection对象的writer成员,我想说的是,为什么不直接传递writer成员?而是将整个对象XMPPConnection传了过来?其实这就是设计模式的好处,我们如果每次都传递的是自己的成员,那么如果后期有改动,实现一个新的XMPPConnection与PacketWriter关联,那么老的代码维护起来是很巨大的,如果这里XMPPConnection和他的同事类PacketWriter都有相对应的接口,(XMPPConnection的接口是Connection)那就更完美了,而这里用到的模式应该是中介者,不是绝对意义的中介者,由于形成中介者的条件比较高,所以实际开发中多是变形使用。PacketWriter对象在XMPPConnection中的connect方法中被初始化,它的最大作用是在其自身的内部创建了两个消息循环,其中一个用30s的heartbeats向服务器发送空白字符,保持长连接。而第二个循环则时刻从队列中主动取消息并发往服务器,而向外部提供的sendPacket方法则是向queue中添加消息,前面提到的循环机制都是在线程中工作,而消息的队列用的是ArrayBlockingQueue,这个无边界阻塞队列可以存放任何对象,这里存放的是Packet对象。

 1 public void sendPacket(Packet packet) {
 2         if (!done) {
 3             try {
 4                 queue.put(packet);
 5             }
 6             catch (InterruptedException ie) {
 7                 ie.printStackTrace();
 8                 return;
 9             }
10             synchronized (queue) {
11                 queue.notifyAll();
12             }
13         }
14     }
while (!done && (writerThread == thisThread)) {Packet packet = nextPacket();if (packet != null) {synchronized (writer) {writer.write(packet.toXML());writer.flush();// Keep track of the last time a stanza was sent to the serverlastActive = System.currentTimeMillis();}}}

      消息循环则是一个通过各种成员变量控制的while loop,第一行的nextPacket方法是向queue中获取Packet消息,并且通过weiter将包发出去,这样生产/消费的模型就搭建好了,这里需要注意的是,我删减了很多影响阅读的代码,并没有全部贴上。关于heartbeats循环其实也是一个在线程中运行的while loop,也是通过一些成员控制。wirter向服务端写了写什么?看下面的这个方法

void openStream() throws IOException {StringBuilder stream = new StringBuilder();stream.append("<stream:stream");stream.append(" to=\"").append(connection.getServiceName()).append("\"");stream.append(" xmlns=\"jabber:client\"");stream.append(" xmlns:stream=\"http://etherx.jabber.org/streams\"");stream.append(" version=\"1.0\">");writer.write(stream.toString());writer.flush();}

XML,没错,这也是符合XMPP协议规范的一种表现吧,至于更多XMPP协议的好处,由于本人的经验有限,就不多做点评,希望后续会对其深入了解。

下面看一个PacketReader这个类都包含了什么职责。

PacketReader

PacketReader所有的核心逻辑都在一个线程中完成的,PacketReader的工作很专注,同样的在一个while loop中 不停的解析、刷新reader对象、同时作为事件源发送解析过后的各种Packet,解析这里用的是Android独特的Pull解析,Pull解析的特点事件驱动,在这里被完全的利用了起来,随着不同的标签,PacketReader都会做出不同的处理,处理完这些数据用不同Pocket对象封装,最后,分发出去,由监听者做最后的业务处理。

readerThread = new Thread() {
    public void run() {
        parsePackets(this);
    }
};

由于解析过程的代码量过于多,我写到什么地方就分解什么地方,大家有时间最好自己看源码。

一、初始化/重置解析器

private void resetParser() {
    try {
        //用的是Pull解析
        parser = XmlPullParserFactory.newInstance().newPullParser();
        parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
        parser.setInput(connection.reader);
    }
    catch (XmlPullParserException xppe) {
        xppe.printStackTrace();
    }
}

上面这个resetParser方法还会在解析的过程中碰到不同的业务需求会不断的被调用,有用和业务逻辑比较紧密,没什么技术含量,关键是要看解析的方式和同时作为事件源发送解析过后的各种Packet,这两部分的设计,是非常的迷人的。

二、解析

do {
    if (eventType == XmlPullParser.START_TAG) {
        if (parser.getName().equals("message")) {
            processPacket(PacketParserUtils.parseMessage(parser));
    }
    else if (parser.getName().equals("iq")) {
        processPacket(PacketParserUtils.parseIQ(parser, connection));
    }
    else if (parser.getName().equals("presence")) {
        processPacket(PacketParserUtils.parsePresence(parser));
    }

PacketParserUtils是一个工具类,各个静态方法传入的还是Parser对象,内部同样的使用Pull的方式进行解析,但是由于Pull是驱动解析,不会无故的浪费资源只会加载感兴趣的内容,试想一下,如果这里用Dom解析……PacketParserUtils的这些静态解析方法返回的实例对象也不一样,从方法名可以看出有IQ、message、presence等,他们的父类为Packet,这些对象又被执行processPacket方法的时候传入

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));
}

processPacket方法内部有一个循环来转调collector.processPacket(packet);方法,前提是connection.getPacketCollectors()内部有货,到目前位置都没有涉及到PacketCollector这个接口的内容,他的作用其实是一个观察者模式中的执行者的作用,也就是传说中的监听器,凡是注册了它的对象,都可以通过processPacket这个抽象方法,监听packet的变化。可是到现在任何对象都没有注册它,所以这个Loop还没有作用,因为目前我们还处在连接的步骤(还没绕出来)。

listenerExecutor.submit(new ListenerNotification(packet));其中ListenerNotification是个Runnable
/**
 * 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);
        }
    }
}

我们上面看到listenerExecutor是一个线程池,在线程池中执行了一个凡是注册了ListenerWrapper的对象,都将接收到packet,同样的,到目前为止没有对象注册,(在RegisterTask过程中ListenerWrapper被注册)

else if (eventType == XmlPullParser.END_TAG) {
    if (parser.getName().equals("stream")) {
        // Disconnect the connection
        connection.disconnect();
    }
}

当文档读取结束是将断开连接

void cleanup() {
    connection.recvListeners.clear();
    connection.collectors.clear();
}

看到了吗,只是将监听器接口集合清空而已,并没有断开连接,或者取消消息循环

PacketReader对象的startup方法比较复杂,大体上执行了读取流,并将解析好的Packet对象发送给观察者,由观察者继续后续操作,目前观察者还没有出现,还有就是使用了线程池和令牌来操作执行线程,而且维护了一个connectionID成员,这个成员的作用还需要再看,这就不多说了。
关于Packet对象,packet对象有很多子类,上面举例了3个,其实还有很多,都是在parser时封装的
AuthMechanism\Challenge\Failure\IQ\Message\Presence\Response\Success
还有就是Pull解析的优点体现了出来,可以一个parser对象包含了很多信息,但可能没到一个时刻我们需要的信息只是一小部分,这样用Pull解析的驱动式就大大减少了冗余的过程,PacketReader对象使用了2个监听器集合对象,PacketCollector、listenerWrapper,还是那句话,还没看到观察者,所以还不知道什么情况下需要注册这两个监听。
到目前位置packetReader.startup()方法终于告一个段落了。

 

register过程分析

    RegisterTask这个task在运行中,添加了一个监听,上面说道的PacketReader中有一个消息机制,在不停的解析服务器返回的结果,然后将解析过后的包分发给各个监听器(观察者),而register中就注册了一个监听器,比较有意思的是,监听器被注册时还加了一个过滤器,这个过滤器的目的是监听器只接收自己感兴趣的内容,这个设计真的很赞。这样就不必在数据源头PacketReader中对数据进行过滤了,只要后期扩展自己Packet和自己的过滤器,就能达到排除自己不关心的信息的功能。

    Registration registration = new Registration();
    PacketFilter packetFilter = new AndFilter(new PacketIDFilter(registration.getPacketID()), new PacketTypeFilter(IQ.class));

其中Registration的类型其实一个IQ的子类,IQ是Packet的子类。
AndFilter是PacketFilter的子类,PacketFilter的种类型有很多,也可以自己扩展,AndFilter就是其中一个、PacketTypeFilter也是、PacketIDFilter也是,
其中PacketTypeFilter的构造方法传入一个IQ.class,其实就是通过这个类文件来过滤packet,这个PacketTypeFilter就是要设置关心的Packet,这里面它告诉监听器,只接收类型为IQ的Packet,这些Filter中都有一个关键方法,accept(Packet packet).这个accept方法每个Filter的实现方式都不一样,我们可可以扩展自己的Filter并且重写这个方法,最有意思的是AndFilter这个类,他的构造方法传入的是一个动态数组,类型为PacketFilter,你可以传入你需要的过滤器,将他们当成组合条件使用来过滤Packet,这个就是典型的装饰设计模式和职责链模式的组合使用。

注册监听器

 1 PacketListener packetListener = new PacketListener() {
 2     //这一部分就是监听器接收到Packet后执行的后续操作
 3     public void processPacket(Packet packet) {
 4         Log.d("RegisterTask.PacketListener", "processPacket().....");
 5         Log.d("RegisterTask.PacketListener", "packet=" + packet.toXML());
 6 
 7         if (packet instanceof IQ) {
 8             IQ response = (IQ) packet;
 9             if (response.getType() == IQ.Type.ERROR) {
10                 if (!response.getError().toString().contains("409")) {
11                     Log.e(LOGTAG,
12                             "Unknown error while registering XMPP account! "
13                                     + response.getError()
14                                             .getCondition());
15                 }
16             } else if (response.getType() == IQ.Type.RESULT) {
17                 xmppManager.setUsername(newUsername);
18                 xmppManager.setPassword(newPassword);
19                 Log.d(LOGTAG, "username=" + newUsername);
20                 Log.d(LOGTAG, "password=" + newPassword);
21 
22                 Editor editor = sharedPrefs.edit();
23                 editor.putString(Constants.XMPP_USERNAME,
24                         newUsername);
25                 editor.putString(Constants.XMPP_PASSWORD,
26                         newPassword);
27                 editor.commit();
28                 Log
29                         .i(LOGTAG,
30                                 "Account registered successfully");
31                 //执行task
32                 xmppManager.runTask();
33             }
34         }
35     }
36 };

addPacketListener方法传入一个监听器和过滤器,看一下内部

/*** Registers a packet listener with this connection. A packet filter determines* which packets will be delivered to the listener. If the same packet listener* is added again with a different filter, only the new filter will be used.* * @param packetListener the packet listener to notify of new received packets.* @param packetFilter   the packet filter to use.*/
public void addPacketListener(PacketListener packetListener, PacketFilter packetFilter) {if (packetListener == null) {throw new NullPointerException("Packet listener is null.");}ListenerWrapper wrapper = new ListenerWrapper(packetListener, packetFilter);recvListeners.put(packetListener, wrapper);
}

可以看到,监听器和过滤器被 ListenerWrapper 再次封装,后续的recvListeners这个集合将ListenerWrapper收入囊中,好整个注册过程完毕,就等待接收信息了,那么发送信息的地方在什么地方呢?分析connect过程时,上面的PacketReader中已经开始循环发送了,代码如下

listenerExecutor.submit(new ListenerNotification(packet));其中ListenerNotification是个Runnable

/*** 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);}}
}

而listenerWrapper的notifyListener(packet)内部,使用了传入的过滤器对Packet进行了过滤

/*** Notify and process the packet listener if the filter matches the packet.* * @param packet the packet which was sent or received.*/
public void notifyListener(Packet packet) {if (packetFilter == null || packetFilter.accept(packet)) {packetListener.processPacket(packet);}

而具体的过滤机制还是转调了传入的过滤器本身的过滤方式accept,非常的灵活。过滤完的Packet将被发送出去

这个方法connection.sendPacket(registration);将一个Registration对象发了出去,

public void sendPacket(Packet packet) {if (!isConnected()) {throw new IllegalStateException("Not connected to server.");}if (packet == null) {throw new NullPointerException("Packet is null.");}packetWriter.sendPacket(packet);
}

内部转调的是 packetWriter.sendPacket(packet);以前提到过PacketWirter中有两个循环机制,其中一个就是在不停的访问队列来获取Packet,而这个sendPacket方法就是将消息写入队列中供消费者使用。

/*** Sends the specified packet to the server.** @param packet the packet to send.*/
public void sendPacket(Packet packet) {if (!done) {// Invoke interceptors for the new packet that is about to be sent. Interceptors// may modify the content of the packet.//内部执行了一个发送数据源的动作,也是为某些监听器对象服务的interceptorWrapper.notifyListener(packet);
        connection.firePacketInterceptors(packet);try {//将一个Packet对象放入到阻塞队列中,在上面的witerPacket方法中的wile循环中发送出去
            queue.put(packet);}catch (InterruptedException ie) {ie.printStackTrace();return;}synchronized (queue) {queue.notifyAll();}// Process packet writer listeners. Note that we're using the sending// thread so it's expected that listeners are fast.
        connection.firePacketSendingListeners(packet);}
}   

其实,注册的过程就是在注册监听,这样在有消息发出时,才可以根据业务需求对消息进行接收和处理。

http://www.cnblogs.com/rioder/archive/2013/01/23/2873176.html

转载于:https://www.cnblogs.com/eustoma/p/4216940.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/376342.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

[Reverse] - 百度杯”CTF比赛 2017 二月场-CrackMe-1

环境&#xff1a;Windows XP 工具&#xff1a; IDA OD EXEINFOPE 0x00 查壳 0x01 分析 拖入OD&#xff0c;字符串查找看一看。 跟进去看就可以知道关键call了 0040196A . 48 dec eax 0040196B . 0F85 C4000000 jnz CrackMe1.00401A35 00401971 . …

【DeepLearning】Exercise:Learning color features with Sparse Autoencoders

Exercise:Learning color features with Sparse Autoencoders 习题链接&#xff1a;Exercise:Learning color features with Sparse Autoencoders sparseAutoencoderLinearCost.m function [cost,grad,features] sparseAutoencoderLinearCost(theta, visibleSize, hiddenSize,…

win7 64位系统下 PL/SQL无法连接的问题

第一步&#xff1a;下载oracle客户端 由于 PLSQL Developer 没有64位版本&#xff0c;所以在64位系统上运行该程链接64位Oracle时就会报错&#xff0c;笔者为这个问题纠结了好几天&#xff0c;后来通过请教Google 动手实践&#xff0c;终于搞定了这个问题。现在把笔者解决的过…

2016腾讯安全挑战赛第一轮-PC游戏方向

0x00 查壳 无壳的VC程序 0x01 测试 没有消息弹窗&#xff0c;尝试对函数下断点。 OD载入后&#xff0c;CtrlN查找函数&#xff0c;找到GetDlgItem 程序运行起来&#xff0c;等输入完后点击确定后程序断下。一路F8就来到这里 00401EED . E8 6A5B0000 call Tenc…

JavascriptDOM(三)

简介 今天学习第二天的知识,js和BOM对象,再接再厉,fighting~学习原则 脚踏实地的走好每一步,要想学好高级部分,基础知识必须扎实呀 掌握基本语法,完成小案例,写博客作总结,基本就是这个节奏,下面我们一起快速入门吧JavaScript快速入门 1. js简介 1.js基于对象和事件驱动的脚本语…

看雪 2016CrackMe 攻防大赛 - 1-Crack_Me-凉飕飕

环境&#xff1a; Windows xp 工具&#xff1a; IDA EXEINFOPE OD 0x00 查壳 EXEINFOPE查壳&#xff0c; 无壳 0x01 分析 if ( (unsigned __int16)wParam 0x40B ) // 成功{*(_OWORD *)v22 xmmword_41DB98;v25 0;v23 xmmword_41DBA8;v24 xmmword_41DBB8;memset_4039D0…

使用CSDN-markdown编辑器

这里写自定义目录标题欢迎使用Markdown编辑器新的改变功能快捷键合理的创建标题&#xff0c;有助于目录的生成如何改变文本的样式插入链接与图片如何插入一段漂亮的代码片生成一个适合你的列表创建一个表格设定内容居中、居左、居右SmartyPants创建一个自定义列表如何创建一个注…

C++ vector类详解

转自http://blog.csdn.net/whz_zb/article/details/6827999 vector简介 vector是STL中最常见的容器&#xff0c;它是一种顺序容器&#xff0c;支持随机访问。vector是一块连续分配的内存&#xff0c;从数据安排的角度来讲&#xff0c;和数组极其相似&#xff0c;不同的地方就是…

ViewPager的使用方法和实现过程

看图先&#xff1a; 页面中填充内容是随机关键词飞入和飞出动画效果&#xff0c;随后会更新&#xff0c;现在请先无视吧 首先是 导入jar包 下载地址&#xff1a; android-support-v4.jar 布局文件里添加viewPager布局 [html] view plaincopyprint?<android.support.v4.vi…

如何通过浏览器在所有响应内容中查找文本

使用浏览器的开发者工具查找响应文件的内容 ** Chrome ** 版本&#xff1a; 快捷键&#xff1a;CtrlShiftF 可以看到已经查找出来了 ** firefox ** 版本

【Leetcode】【Easy】Implement strStr()

Implement strStr(). Returns the index of the first occurrence of needle in haystack, or -1 if needle is not part of haystack. 解题&#xff1a; 本题为典型的KMP算法考察题&#xff0c;KMP算法描述为&#xff1a; 设主串S&#xff0c;匹配串P&#xff0c;i为S的索引下…

Jenkins入门指南

新手学习使用Jenkins 安装好Jenkins后如何运行脚本 1.新建item 2.输入任务名称&#xff0c;选择项目类型&#xff0c;点击确定 3.填个描述就好了&#xff0c;新手学jenkins&#xff0c;其他都不看&#xff0c;跑起来再说 4.点这个高级&#xff0c;选择你要运行的脚本所在…

Sublime Text 3 史上最性感的编辑器

↑ ↑ ↑ ↑ ↑ 请看文件夹 ↑ ↑ ↑ ↑ ↑ 下载 / 安装 windows / MAC OS 官网下载&#xff0c;双击安装&#xff0c;这个都会吧&#xff5e; linux linux下安装&#xff0c;一种办法是从官网下载 tar.bz &#xff0c;手动安装。 这里介绍用 apt-get 自己主动安装方法&#xf…

swift入门之TableView

IOS8更新了&#xff0c;oc还将继续但新增了swift语言&#xff0c;能够代替oc编写ios应用&#xff0c;本文将使用swift作为编写语言&#xff0c;为大家提供step by step的教程。 工具 ios每次更新都须要更新xcode&#xff0c;这次也不例外&#xff0c;但使用xcode6&#xff0c;须…

Jmeter BeanShell学习(一) - BeanShell取样器(一)

通过利用BeanShell取样器设置请求发送的参数。 第一步&#xff1a;添加BeanShell取样器 第二步&#xff1a;在BeanShell中输入执行的代码 log.info("脚本开始执行"); //意思是将字符串输出到日志消息中 vars.put("username","123163.com");//…

Jmeter BeanShell学习(一) - BeanShell取样器(二)

利用BeanShell取样器获取接口返回的JSON格式的结果&#xff0c;并将该结果写入到文件。 第一步&#xff1a;添加BeanShell取样器 前面几个取样器的内容查看&#xff1a; https://blog.csdn.net/goodnameused/article/details/96985514 第二步&#xff1a;查看返回的结果格式 …

160 - 50 DueList.5

环境&#xff1a; Windows xp sp3 工具&#xff1a; Ollydbg exeinfope 0x00 查壳 可以看出程序有加壳&#xff0c;那么我们下一步就是脱壳了。 0x01 脱壳 看上去没什么特别的地方&#xff0c;就直接 单步跟踪法 来脱壳吧 近call F7&#xff0c;远call F8 来到这里 哈&…

关键路径的概念和算法

AOE网&#xff1a;在一个表示工程的带权有向图中&#xff0c;用顶点表示事件&#xff0c;用有向边表示活动&#xff0c;边上的权值表示活动的持续时间&#xff0c;称这样的有向图叫做边表示活动的网&#xff0c;简称AOE网。AOE网中没有入边的顶点称为始点&#xff08;或源点&am…

160 - 51 DueList.6

环境&#xff1a; Windows xp sp3 工具&#xff1a; Ollydbg exeinfope 0x00 查壳 发现程序没有加壳&#xff0c;那么我们可以直接分析了。 0x01 分析 运行程序看一看 看到错误信息的字符串后我们可以直接搜索了。 可以看到程序会比较输入的长度是否为8位&#xff0c;如…

160 - 52 egis.1

环境&#xff1a;windows xp 工具&#xff1a; 1、OllyDBG 2、exeinfo 3、IDA 0x00 查壳 加了UPX壳&#xff0c;那么就要脱壳了。可以使用单步法来脱壳。 UPX壳还是比较简单的&#xff0c;开头pushad&#xff0c;找个popad&#xff0c;然后就是jmp了。 然后就可以用OD来…