Java自旋锁、排队自旋锁、MCS锁、CLH锁

自旋锁(Spin lock)

自旋锁是指当一个线程尝试获取某个锁时,如果该锁已被其他线程占用,就一直循环检测锁是否被释放,而不是进入线程挂起或睡眠状态。

自旋锁适用于锁保护的临界区很小的情况,临界区很小的话,锁占用的时间就很短。

简单的实现

    import java.util.concurrent.atomic.AtomicReference;

    public class SpinLock {
       private AtomicReference<Thread> owner = new AtomicReference<Thread>();

       public void lock() {
           Thread currentThread = Thread.currentThread();

                  // 如果锁未被占用,则设置当前线程为锁的拥有者
           while (owner.compareAndSet(null, currentThread)) {
           }
       }

       public void unlock() {
           Thread currentThread = Thread.currentThread();

                  // 只有锁的拥有者才能释放锁
           owner.compareAndSet(currentThread, null);
       }
    }

SimpleSpinLock里有一个owner属性持有锁当前拥有者的线程的引用,如果该引用为null,则表示锁未被占用,不为null则被占用。

这里用AtomicReference是为了使用它的原子性的compareAndSet方法(CAS操作),解决了多线程并发操作导致数据不一致的问题,确保其他线程可以看到锁的真实状态。

缺点

  • CAS操作需要硬件的配合;
  • 保证各个CPU的缓存(L1、L2、L3、跨CPU Socket、主存)的数据一致性,通讯开销很大,在多处理器系统上更严重;
  • 没法保证公平性,不保证等待进程/线程按照FIFO顺序获得锁。

Ticket Lock

Ticket Lock 是为了解决上面的公平性问题,类似于现实中银行柜台的排队叫号:锁拥有一个服务号,表示正在服务的线程,还有一个排队号;每个线程尝试获取锁之前先拿一个排队号,然后不断轮询锁的当前服务号是否是自己的排队号,如果是,则表示自己拥有了锁,不是则继续轮询。

当线程释放锁时,将服务号加1,这样下一个线程看到这个变化,就退出自旋。

简单的实现

    import java.util.concurrent.atomic.AtomicInteger;

    public class TicketLock {
       private AtomicInteger serviceNum = new AtomicInteger(); // 服务号
       private AtomicInteger ticketNum = new AtomicInteger(); // 排队号

       public int lock() {
             // 首先原子性地获得一个排队号
             int myTicketNum = ticketNum.getAndIncrement();

                  // 只要当前服务号不是自己的就不断轮询
           while (serviceNum.get() != myTicketNum) {
           }

           return myTicketNum;
        }

        public void unlock(int myTicket) {
            // 只有当前线程拥有者才能释放锁
            int next = myTicket + 1;
            serviceNum.compareAndSet(myTicket, next);
        }
    }

缺点

  • Ticket Lock 虽然解决了公平性的问题,但是多处理器系统上,每个进程/线程占用的处理器都在读写同一个变量serviceNum ,每次读写操作都必须在多个处理器缓存之间进行缓存同步,这会导致繁重的系统总线和内存的流量,大大降低系统整体的性能。

下面介绍的CLH锁和MCS锁都是为了解决这个问题的。

MCS 来自于其发明人名字的首字母: John Mellor-Crummey和Michael Scott。

CLH的发明人是:Craig,Landin and Hagersten。

MCS锁

MCS Spinlock 是一种基于链表的可扩展、高性能、公平的自旋锁,申请线程只在本地变量上自旋,直接前驱负责通知其结束自旋,从而极大地减少了不必要的处理器缓存同步的次数,降低了总线和内存的开销。

    import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;

    public class MCSLock {
        public static class MCSNode {
            volatile MCSNode next;
            volatile boolean isBlock = true; // 默认是在等待锁
        }

        volatile MCSNode queue;// 指向最后一个申请锁的MCSNode
        private static final AtomicReferenceFieldUpdater UPDATER = AtomicReferenceFieldUpdater
                .newUpdater(MCSLock.class, MCSNode.class, "queue");

        public void lock(MCSNode currentThread) {
            MCSNode predecessor = UPDATER.getAndSet(this, currentThread);// step 1
            if (predecessor != null) {
                predecessor.next = currentThread;// step 2

                while (currentThread.isBlock) {// step 3
                }
            }
        }

        public void unlock(MCSNode currentThread) {
            if (currentThread.isBlock) {// 锁拥有者进行释放锁才有意义
                return;
            }

            if (currentThread.next == null) {// 检查是否有人排在自己后面
                if (UPDATER.compareAndSet(this, currentThread, null)) {// step 4
                    // compareAndSet返回true表示确实没有人排在自己后面
                    return;
                } else {
                    // 突然有人排在自己后面了,可能还不知道是谁,下面是等待后续者
                    // 这里之所以要忙等是因为:step 1执行完后,step 2可能还没执行完
                    while (currentThread.next == null) { // step 5
                    }
                }
            }

            currentThread.next.isBlock = false;
            currentThread.next = null;// for GC
        }
    }

CLH锁

CLH锁也是一种基于链表的可扩展、高性能、公平的自旋锁,申请线程只在本地变量上自旋,它不断轮询前驱的状态,如果发现前驱释放了锁就结束自旋。

    import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;

    public class CLHLock {
        public static class CLHNode {
            private boolean isLocked = true; // 默认是在等待锁
        }

        @SuppressWarnings("unused" )
        private volatile CLHNode tail ;
        private static final AtomicReferenceFieldUpdater<CLHLock, CLHNode> UPDATER = AtomicReferenceFieldUpdater
                      . newUpdater(CLHLock.class, CLHNode .class , "tail" );

        public void lock(CLHNode currentThread) {
            CLHNode preNode = UPDATER.getAndSet( this, currentThread);
            if(preNode != null) {//已有线程占用了锁,进入自旋
                while(preNode.isLocked ) {
                }
            }
        }

        public void unlock(CLHNode currentThread) {
            // 如果队列里只有当前线程,则释放对当前线程的引用(for GC)。
            if (!UPDATER .compareAndSet(this, currentThread, null)) {
                // 还有后续线程
                currentThread. isLocked = false ;// 改变状态,让后续线程结束自旋
            }
        }
    }

CLH锁 与 MCS锁 的比较

下图是CLH锁和MCS锁队列图示:

差异:

  • 从代码实现来看,CLH比MCS要简单得多。
  • 从自旋的条件来看,CLH是在前驱节点的属性上自旋,而MCS是在本地属性变量上自旋。
  • 从链表队列来看,CLH的队列是隐式的,CLHNode并不实际持有下一个节点;MCS的队列是物理存在的。
  • CLH锁释放时只需要改变自己的属性,MCS锁释放则需要改变后继节点的属性。

注意:这里实现的锁都是独占的,且不能重入的。

java枚举Enum的理解

常见应用

枚举类是 java.lang.Enum 类的子类,下述示例代码中:

NEGRO、WHITE、ASIAN都是Human预定义好的Human的实例(public static final)
在运行期间,我们无法再创建新的Enum的实例(构造方法私有)
示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public enum Human {
// 利用构造函数传参
NEGRO(1, "黑人"),
WHITE(2, "白人"),
ASIAN(4, "黄种人");
// 定义私有变量
private int value;
private String desc;
// 构造函数,枚举类型只能为私有,默认为私有
Human( int value, String desc) {
this.value = value;
this.desc = desc;
}
// Getter and Setter
... ...
}

综上所述:

enum 类的实例,默认是:public static final,例如上述的 NEGRO、WHITE、ASIAN
enum 类的默认构造方法是 private,也只能是private,保证外界无法创建 enum实例

为什么要用 enum

实际上,枚举类的实例是静态常量,那为什么不直接使用静态常量呢?在没有 enum之前,静态常量的写法如下:

1
2
3
4
5
6
7
8
// 方法 1:静态常量
public class WeekDay {
public static final int MONDAY = 1;
public static final int TUESDAY = 2;
public static final int WENSDAY = 3;
public static final int THURSDAY = 4;
public static final int FRIDAY = 5;
}

使用枚举类时:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 方法 2:枚举类
// enum 类的等价实现方式,后文会单独写一个 enum
public class WeekDay {
public static final WeekDay MONDAY = new WeekDay(1);
public static final WeekDay TUESDAY = new WeekDay(2);
public static final WeekDay WENSDAY = new WeekDay(3);
public static final WeekDay THURSDAY = new WeekDay(4);
public static final WeekDay FRIDAY = new WeekDay(5);
private int value;
private WeekDay(int i){
this.value = i;
}
public int getValue(){
return value;
}
}

方法 1,使用静态常量,存在几个问题:

  • 类型不安全:由于常量的对应值是整数形,所以程序执行过程中很有可能给星期变量传入一个任意的整数值,导致出现错误。
  • 没有命名空间:由于常量只是类的属性,必须通过类来访问 如: Weekday.SUNDAY。
  • 一致性差:因为整形枚举属于编译期常量,所以编译过程完成后,所有客户端和服务器端引用的地方,会直接将整数值写入,修改需要重新编译。
  • 类型无指意性:由于枚举值仅仅是一些无任何含义的整数值,如果在运行期调试时候,你就会发现日志中有很多魔术数字(0-6),其他人很难明白具体含义。

相比来说,方法 2 ,使用静态常量类(枚举类),好处:

  • 语意性强:字面即可理解功能
  • 不直接接受 int 类型数据,只接受 WeekDay 预定义好的static final的实例
  • 所有对象都是 public static final,是单例的,因此可以直接使用 equals 或者 ==

下面写一个标准的枚举类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public enum WeekDayEnum {
MONDAY(1),
TUSDAY(2),
WENSDAY(3),
THURSDAY(4),
FRIDAY(5);
private int value;
WeekDayEnum(int value){
this.value = value;
}
public int getValue(){
return this.value;
}
}

NOTE:实际上,枚举类继承自java.lang.Enum抽象类。

应用场景

枚举的7中常用方式,主要有:

  • 表示常量
  • 用于switch
  • 添加更多方法
  • 覆盖Object方法
  • 实现接口
  • 枚举集合EnumSet和EnumMap,EnumSet保证集合中的元素不重复;EnumMap中的key是enum类型,而value则可以是任意类型。

特别写法

枚举类,每个静态常量实例,都有一个序号(从 0 开始),常见的写法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public enum WeekDayEnum {
MONDAY("周一"),
TUSDAY("周二"),
WENSDAY("周三"),
THURSDAY("周四"),
FRIDAY("周五");
private int ordinal;
WeekDayEnum(){
this.ordinal = this.ordinal();
}
public int getOrdinal(){
return this.ordinal;
}
}

避免错误使用 Enum

不过在使用 Enum 时候有几个地方需要注意:

  • enum 类型不支持 public 和 protected 修饰符的构造方法,因此构造函数一定要是 private 或 friendly 的。也正因为如此,所以枚举对象是无法在程序中通过直接调用其构造方法来初始化的。
  • 定义 enum 类型时候,如果是简单类型,那么最后一个枚举值后不用跟任何一个符号;但如果有定制方法,那么最后一个枚举值与后面代码要用分号’;’隔开,不能用逗号或空格。
  • 由于 enum 类型的值实际上是通过运行期构造出对象来表示的,所以在 cluster 环境下,每个虚拟机都会构造出一个同义的枚举对象。因而在做比较操作时候就需要注意,如果直接通过使用等号 ( ‘ == ’ ) 操作符,这些看似一样的枚举值一定不相等,因为这不是同一个对象实例。

技术合伙人为什么喜欢谈钱胜过情怀?

本文系转载自缘创派

相信无数创业者都有过这样的经历,尤其是那些完全不懂技术的创始人,当他们去找技术合伙人的时候,往往发现技术人员更喜欢谈钱而胜过情怀,为什么呢?

最近,收到一封 缘创派 用户的反馈:“我这二周谈了十几个技术合伙人。很多人并没有做好心理准备,只是希望找个兼职或者赚个外包。所以我用一个词形容他们叫叶公好龙。是否真的准备好创业,愿意承担风险和准备投入去做对于创业来讲非常重要。”这些朋友在抱怨:为什么找技术合伙人的时候,很多人会直白的谈到参与创业项目的费用呢?为什么很多只想先兼职呢?

这些创始人认为,既然我是在找合伙人,那大家就是要全情投入,立刻辞职,不拿薪水,共同奋斗,为了未来的收益而努力拼搏。你这上来朝我要钱,那怎么能算合伙人呢,这不就是外包和招聘了吗?

随着更多类似的抱怨,我开始仔细思考这个问题。有一次,我在我们的线下活动中对技术人员这个群体做了一些分析。告诉现场的一些创业主导者,为什么很多技术人员会这样做。大家听完之后,表示很认可。

因为我在 CSDN 工作过十多年,经常与技术人员打交道。比较了解他们的情况和思维模式。所以,我希望简单介绍一下,大家可以和技术合伙人互换一下角色,站在他们的角度来思考和观察创业。这样就能够更好地理解他们,并更好地合作。

技术人员与其他的创业者相比,有几个很大的不同

一、技术人员的职业生涯很短暂,机会成本高,他们对风险的控制要求极高。

很久之前,就有“程序员和 ** 一样,都是吃青春饭”的说法。这并不是危言耸听,事实上,在中国,程序员的职业生涯的确有限制。从二十多岁到三十多岁,一般四十岁之后再编程就很难得到认可(现在,开始有很好的转变了。)而在这个期间,早期的几年还是处在学习的阶段,而在工作超过六年以后,很可能他在公司中会有强大的发展,获得了足够的重视。

其实,对于技术人员来说,只有中间寥寥几年会是技术水平可以,同时关注创业的时机。当他面临选择的时候,他考虑并不是所谓的虚拟的 1% 的成功机会带来的巨额回报,而是自己的职业发展向何处去。

技术人员评估风险主要有:技术成长和收入,其中技术成长的重要性更大。很多技术人员加入 BAT 这样的公司,除了收入不菲,更重要的因素就是这些公司的技术大牛多,自己的技术水平可以提高。而创业公司,往往是实现某个特定的业务,对他们的技术成长可能帮助不大。

我们都知道,95% 的创业项目会失败。如果失败,与主导创业者、市场或者运营人员相比,技术人员的损失和挫折感最大。因为,其他的创业者会感觉自己虽然没有成功,但得到了很多教训,这也是非常有价值的。但技术人员会认为,自己写的代码最终的价值等于零,这段时间的付出完全没有回报。如果技术再没有什么进步,这对他们的打击是致命性的。

这些原因造成他们对走出创业这一步的风险控制要求极高。再加上双方之前不认识,要基于陌生关系开始合作,这个信任基础需要逐步建立。

如果你还不相信,请看《乔布斯传》里的真实故事:沃茨负责 Apple 电脑的技术,当他把产品设计出来之后,乔布斯让沃茨出来创业成立公司一起做。沃茨直接拒绝了乔布斯,因为自己在惠普工作,他给乔布斯提议自己能不能先兼职干(要知道,他们两个是从小时候就认识的。请哪些感觉自己创业项目很 NB 的创始人体会一下。) 最后是乔布斯到沃茨家去哭,给他所有的亲戚打电话,逼着沃茨才出来的。这是因为,技术人员天生就是对风险控制的,他们是非常理性的人群,要他们去冒险需要更强的说服力。

所以,当对方提出来兼职的时候,不要把这个看作他们不想创业,不是技术合伙人。他们只是在控制风险,觉得没有到时机。

二、技术合伙人是互联网创业中前期最重要的实现者,但他们的核心价值是有阶段性和替代性的。

如果问你,在互联网创业中,技术合伙人重要吗?你肯定要说废话,当然重要。但很多创业项目的创始人并不是特别明白:技术合伙人什么时候最重要?

技术合伙人在开始阶段最重要。因为,只要是互联网创业项目,你总是需要要依托技术实现某个产品原型的。现在要想拿到投资,基本上很难凭一个 idea 了,至少需要一个原型,甚至是早期的产品上线。但事实上,因为找不到早期的技术人员参与,大部分人都折在只有一个创业项目的阶段。

所有的投资人都说,创业最重要的是团队,所以很多创始人想先找到合适的技术合伙人。这对于已经成型,有着良好磨合的创业团队,是毋庸置疑的。但对于很多项目来说,其实其实投资人主要看的团队是创始人自己,另外就是你的原型产品和早期的运营。所以,越早把你设想的产品做出来,运营起来,这才是最重要的。技术合伙人在这个阶段,最重要的任务是帮助你把产品做出来。

但是,双方之前不认识,没有磨合过,怎么办呢?这个信任基础从何而来的,钱其实是一种快速开始合作,进行磨合,并且建立初步信任的变通手段。

我分析过很多找到合伙人的案例,结论是:技术合伙人要钱并没有错,但不能按照外包的价格要钱。

我看到一个做手机游戏的创业项目,创始人给兼职的技术合伙人很少的一点钱,一个月 3000,技术人员便将自己所有的业余时间全部用来项目的开发,每天晚上都工作到 12 点,甚至到凌晨两点。这个参与者都表示:如果做出来产品,推到市场的有效果,那大家就可以全职出来做。这个创始人也表示,如果是雇佣或者外包,没有任何人会只拿 3 千块,用自己所有的业余时间辛苦做事的。

事实上,大部分创业项目,成功与否不在技术本身,而是在产品和运营。你只要找到一个愿意合作的技术合伙人,给一些象征性的费用,做出一个版本,上线运营测试一下,便能够知道自己的项目是否靠谱。在 36kr 的很多创业经验中,都提到过这种方式。

我个人感觉,如果对方要的费用如果在其薪资的 1/5 到 1/3 期间,不应该认为技术人员是为了钱,他们是为了控制风险。

三、技术人员的核心思维模式是质疑和要求短期回馈,这恰恰给了创始人主导项目的机会。

在创业团队中,创始人一定是最具有激情的哪个人。是他发现了一个市场空白,找到了一个机会,然后认为这就是成功的机会。于是说服了自己。

但是,技术人员恰恰相反,他们是极其理智的。因为长期和电脑打交道,形成的是逻辑性的思维。当外人给他讲述一个理念时,他的第一反应是质疑。是真的有这个需求吗?做出来有人用吗?怎么推广,怎么产生收入?很多时候,说服他们是非常困难的。

技术人员创业为什么很容易失败?因为他们只和计算机打交道,除了自己的需求,他们并不了解市场和用户。所以,他们自然对别人所讲的市场需求是质疑态度。

技术人员在编程的时候,逻辑就是有输入,就有输出,而且回馈速度越快越好,这个可能会影响技术人员的世界观的,他们希望得到回馈的周期短。

很多项目创始人表示,我愿意拿出超过 30% 甚至更多的股份给技术合伙人。其实,除了代表创始人不够成熟外,这样的许诺不仅技术人员不重视,甚至反而会招致技术人员更大的质疑:“是不是这个项目不靠谱,所以才让我先做呢?”

但是,这恰恰给创始人主导项目的机会。只要你能说服技术合伙人,稍微付出一些费用,这样就可以把项目的未来价值握在自己手中。一定要谨记,技术合伙人最重要的职责是理解业务,把产品做出来。在整个创业团队中,更倾向于一个实现者或者翻译的角色。不能指望技术合伙人对项目的理解和认同和自己一样。

相信很多人看到这里,还是会不屑的说:我觉得他们还不是合伙人。为什么不能不要钱,立刻和我光膀子干呢?

并不是没有这样的技术合伙人,也有很多创业团队组成之后,大家都不拿薪水。但我注意到,这种团队有几个特点:1. 大家都是技术或者产品背景,都参与具体的开发工作。所以大家不拿薪水感觉公平 2. 股份平均化。既然都不拿薪资,那大家的股份就会平均化。(但事实上,股份平均化对于创业项目来讲有很大的弊端。)

如果你想主导创业,如果自己不是技术背景的创业者,还是试着互换一下角色,去理解技术合伙人吧。毕竟,把项目做出来才是第一位的。而且,这个阶段其实是一个相互磨合,不断建立信任的过程。创始人要想把握项目的主动权,只凭借一个想法,而不是一些实际付出是不现实的。

最后再总结一下

  • 技术合伙人对创业想法首先是质疑的,他们要短期回报并不只看钱,是为了控制风险。

  • 技术人员愿意参与你的项目,本身就在冒风险。因为一旦失败,他们失去的最多。

  • 兼职是某些陌生合作下的过渡阶段,在合作中提升信任,最终成为真正的合伙人。

分享一个用户自发需求的反向O2O设想

这个想法来自实现网

去年5月份左右,产生了一个做校园兼职APP的想法,当时在老家,三线小城市,就想靠微信和微信订阅号先试水市场,慢慢运营。边做边思考如何让用户在毕业之后依然使用APP。

后来,口袋兼职融到资,火了。我才知道我不是唯一一个想到这个idea的人。

再后来,4、5家校园兼职都融到资了,我放弃之前维护的三线小城市,来到上海。想找投资,更想找技术团队。

但是,我还是要做校园兼职么?NO!

兼职,是人类使用自己的闲置技能满足别人无法完成的工作的行为。

so.

有的技能可以开店,比如会做美甲,能修电脑,会做推拿……

有的技能不可以开店,不能开店的技能不是说技能不好,而是拥有技能的人没有资金,没有人脉,没有……等等一系列的缺乏。

好了,仔细一看,这不就是O2O么。

把传统商业上线,让用户在线上挑选。

原本的校园兼职是把兼职上线,现在也有把美甲服务上线的,推拿按摩服务上线的,上周去某院校听讲座,还有把洗车上线的,做卫生上线的……

如果O2O只是把线上的商业服务做上线,那我们的手机里该下载多少款APP?

而且,这些服务,我们并不需要每天使用。

美甲需求,6周产生一次。

按摩需求,1周产生一次。

洗车需求,1月产生一次。

……

如果为了偶尔产生一次的需求,我们就要下载一款APP,使用,完成,最后我们也一定会卸载,然后下次产生需求的时候再安装,如此反复,我们也就厌烦了。

所以,我想。

做一个APP,上面不是商家,而是用户发布的需求,供用户周边的传统商家有的放矢,当然除了商家,更会有很多非商家的个人也可以满足用户的需求。

所以,我的标题是,连接用户需求和“手艺”。

未来,你是想要自己的手机里有N多个APP,或者自己记住N多个APP的名字来满足自己N多个的需求;还是只要一个APP,就满足自己的所有需求?

目前我在上海,拿到了一份投资意向,但对方需要我找到技术团队之后才能启动。

so,我们距离成功,只差一步,资金和技术人才。

面向企业和个人O2O图书租赁服务设想

——阅读即服务。取代购买书籍的一种阅读方式。

问题:

1.员工工作中的成长和学习机会
2.企业的书架管理和员工福利

服务方案:

1.企业购买书架网服务;
2.企业员工可以在书架网借阅任意图书;
3.书架网在每周一次约定的日期将送书上门,并收回已看过的书。

目标用户:企业。以智力集中型企业为主,比如IT、咨询等
用户价值价值实现:让企业的智力资产升值。

商业模式:

买书架服务以及周边增值服务
定价:企业人数人均可同时拥有书本数80元
例:
企业成本:30人企业,每人可同时读3本书,那就是7200/年;

企业一笔帐:

如果觉得每一个员工应该每周都能看完3本书,那么一年企业一共可以获得
30523=4680本书,如果采用购买的方式,按照每本50元算,共需支出 23.4万;

如果觉得每一个员工一个月应该看一本书,那么30人*12月=360本 ,按照每本50元算,供需支出18000元;

现在的方式:18000元+购买书籍+统计买书需求+书架管理+行政人工
(每个人1个月看一本公司买的书)

使用书架网:7200元
(每人每周可以看三本任意自己喜欢的书)
后续增值服务:
推荐企业员工应该阅读的书籍
企业员工个人/集体读书行业排行榜
企业员工培训

业务扩展:

面向个人
面向家庭的儿童教育、绘本等;

关于书籍的储备,前期可以考虑平台购买,企业放书籍于平台,统一管理,统一入库。

细节欢迎大家讨论。

关于电商商品评价评论独立第三方平台构想

现在电子商务盛行,核心是产品与服务,归根结底还是以人为本,产品和服务都是提供给消费者,得有人知晓你的这个商品,那怎么体现呢?一个是商品自身描述,另一个就是商品的评价与口碑;我想到做一个独立的第三方商品评价评论平台

想法由来

目前中国电商混战,大型的B2B,B2C,C2C,O2O平台层出不穷,垂直电商领域,导购,海淘,比价的大平台也是多于牛毛;但是商品质量,服务质量参差不齐,刷评问题严重,监管困难,各大平台各自为阵.
独立的第三方评价评论平台应该出来了,在这种平台上买卖商品已经不是核心了,核心是体现商品价值,体现商品的使用价值,让更多的评价,评论把商品质量暴露出来

项目实施

评价平台最主要的任务就是商品的标准化,涉及到各大平台具体商品的标准化,评论的整合,各个平台商品名称编码整合;

前期实施

  • 丰富平台商品数量,种类
  • 丰富平台商品的评论
  • 平台面向用户的展现形式主要是APP,网站

可能出现的问题或缺陷

国外例子 www.dooyoo.com,www.ciao.com

关于随手拍图片人脸识别APP需求的想法

早上在地铁上突然想到这个需求,觉得是刚需,而且应用场景应该很广,大致说下我的想法:

初级

能有效的识别随手拍的人脸,给出关于这个人的基础信息,信用信息,社会评价,人品得分,职业信息,家庭信息,社交元素,系统建议等等有用的实时信息;
当然可能识别出多个人出来,初步可以接受;
支持用户上传个人真实信息,如果信息不真实被系统识别,会降低人品得分;
支持手机APP上传图片,给出系统识别信息,可能来自互联网,也可能来自系统其他用户提供;

中级

构建个人信用信息数学模型,人脸识别准确性要突破提高;
构建人品得分数学模型,准备预测某个人的真实一面;
增加人物评价体系,作为人品得分的量化参数之一;
评价可以匿名化;
基础人物信息收集;

高级

构建人类基础人脸信息库;构建人类基础信用信息库;
构建人类基础个人信息库;考虑识别非人类物体,动物,植物,商业产品等;
google眼镜可以是个很好地系统硬件媒介,拍摄人与物;

应用场景

  • 交友,通过APP认识陌生人,认识有眼缘的人,认识有趣的人,通过自己的第六感预测值得认识这个人;
  • 避免黑车,第六感通常不准,坐上黑车了不知道,随手拍下,看看这个是否可信;
  • 识别骗子,让骗子无法隐藏;
  • 寻找失踪儿童,迷途少年,失踪少女,失踪老人,失踪人口让社会更和谐;
  • 通过系统的个人信用信息查询,识别某个人的信用情况;

可能依赖的内外部条件

  • 图形图像识别;
  • 人脸识别;
  • 个人信息采集;
  • 数学模型构建;
  • 人肉搜索,真实信息辨别;

徐神的诗



        今早在群里看到徐架构的两个代码片段,惊呼徐架构的造诣之高,见解之深,手法之妙,特此写下读后感,以作勉励!下面我以整体局部再整体的方式剖析下徐神的诗.

        这首诗是我迄今为止看到徐架构,最最精华的一首;首先诗的架构精妙绝伦,设计思想超乎寻常,层次分明,语句工整,言简意赅,段落堆叠有致,是后现代主义的代表作;注解手法的运用,让我们看到此首诗只应天上有,人间那得几回闻;单词,命名,语法丝毫不差,处处透露着徐神的高超技艺.

        细节之处的把握也是毫厘必究,字字斟酌.类的命名典型的驼峰式,代表着徐神心思缜密;方法的注释详细,用词准确,也流露出徐神对代码重构抽象的考究;注解类中方法的个数,每个方法的参数格式,无不体现徐神设计思想博大精深;方法默认值的给出,让我们这些墨守成规的人,立马顿悟,此处应有掌声;四个方法每一个都是那么的重要,那么的抽象,那么的活灵活现,仿佛是有生命的精灵,作用不同,协同调用,发挥着强大的思想感召力;提示信息不可少,徐神的设计可谓匠心独用,精益求精;再聚焦下类名,方法名,用形容词而不用动词,见文识意,生动灵活,不经让我感叹,天下之大,山外有山,人外有人,徐神正是山外山,人外人;数组也是徐神必备的利器,在这里用到了,而且是恰到好处,妙笔生花,令人望洋兴叹,却又意犹未尽;基础数据类型在徐神眼里,也是兴头上的事,信手拈来,毫无顾忌,挥洒自如,在这里我看到了徐神对编程的理解,对架构的领悟,对技术人生的参透,真是入木三分哇;组合思想的使用,再一次证明了他是徐神,此处省略一万词,我此刻激动之情已经无以言表,只可意会;还有一个细节,没错,徐神用到字符串,也是点睛之笔;段落末尾,那惊人的分号,苍劲有力,是徐神情感的爆发,对当代社会的批判,对行业不公的蔑视,对代码人生的宣泄;徐神的代码是艺术的升华,是情感的迸发,是思想的解脱.

        整首诗字字珠玑,优雅而曼妙;每一个字母都不少也都不多,甚至连空格都是那么的弥足珍贵;徐神在他成千上万的代码块中,独独选择了这两首,用心良苦,展示了徐神对安全的重视,徐神这种工匠精神也给同行业从业者敲响了警钟;在徐神看来是其貌不扬的两首诗,在我看来却是精华之笔,实属无价之宝;读完徐神的诗,如同醍醐灌顶一般,久久还在脑中回荡,真是酣畅淋漓,受益匪浅.看到这里,我想结束自己散漫不羁,碌碌无为的生活,徐神是个无法逾越的神话,闲暇的时间我愿追随徐神,学习,进取,奋发.

        整首诗抑扬顿挫,婉约细腻;即便是放到寻常百姓家,也丝毫不觉得违和;可见徐神功底之深,已臻化境,此等炉火纯青的架构经验,已非常人努力四十余年可匹敌;短短几十余字,徐神从架构,设计,编程思想,算法,效率,安全各个技术领域,全面阐述其编程之道,架构之魂;徐神对各种技术无不精通,无不涉猎,给当代码农竖起了一座丰碑.

        古往今来,横贯中外,执代码之牛耳,唯徐神一人矣!致天下之治者在人才,成天下之才者在教化,我当以徐神为师,学而不倦.

Openfire服务端错误解决

最近发现Openfire服务端隔段时间就会报这样的错:

有时候是这样:

决定处理一下这个“黑盒”,从异常信息看,这个异常有关socket连接超时或者是连接被重置。查了一下相关问题出现原因:(引)

常出现的Connection reset by peer: 原因可能是多方面的,不过更常见的原因是:

服务器的并发连接数超过了其承载量,服务器会将其中一些连接Down掉;
客户端断掉连接,而服务器还在给客户端发送数据;
该异常在客户端和服务器端均有可能发生,引起该异常的原因有两个,一个是如果一端的Socket被关闭(或主动关闭或者因为异常退出而引起的关闭),另一端仍发送数据,发送的第一个数据包引发该异常(Connect reset by peer)。另一个是一端退出,但退出时并未关闭该连接,另一端如果在从连接中读数据则抛出该异常(Connection reset)。简单的说就是由连接断开后的读和写操作引起的。

如果频繁出现,就表示很多客户端连接到Apache服务器的响应时间太长了,可能是网络的问题或者服务器性能问题。

1
Connection timed out:

连接超时,其实还是找不到已连接的客户端。可能出现原因:客户端网络问题导致连接没断开。

由于Openfire是基于mina做的socket连接,mina本身封装了socket的各种异常,只剩下ProtocolCodecException、ProtocolEncoderException、ProtocolDecoderException、RecoverableProtocolDecoderException 四种异常,其他都作为运行时异常抛出。

Openfire的连接管理类是ConnectionHandler,继承了mina的IoHandlerAdapter。通过exceptionCaught方法实现连接过程中的异常处理。

源码如下(Openfire 3.7.2版本):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@Override
public void exceptionCaught(IoSession session, Throwable cause) throws Exception {
if (cause instanceof IOException) {
// TODO Verify if there were packets pending to be sent and decide what to do with them
Log.info("ConnectionHandler reports IOException for session: " + session, cause);
}
else if (cause instanceof ProtocolDecoderException) {
Log.warn("Closing session due to exception: " + session, cause);
// PIO-524: Determine stream:error message.
final StreamError error;
if (cause.getCause() != null && cause.getCause() instanceof XMLNotWellFormedException) {
error = new StreamError(StreamError.Condition.xml_not_well_formed);
} else {
error = new StreamError(StreamError.Condition.internal_server_error);
}
final Connection connection = (Connection) session.getAttribute(CONNECTION);
connection.deliverRawText(error.toXML());
session.close();
}
else {
Log.error("ConnectionHandler reports unexpected exception for session: " + session, cause);
}
}

可以看出,openfire本身没有处理IO异常,只是打了一下log。这会导致断开的链接发生没断干净的情况。这种情况下面再讨论。

编写网络程序时需要注意的问题:(引)

是要正确区分长、短连接。所谓的长连接是指一经建立就永久保持。短连接的情况是,准备数据—>建立连接—>发送数据—>关闭连接。很多的程序员写了多年的网络程序,居然不知道什么是长连接,什么是短连接。

是对长连接的维护。所谓维护包括两个方面,首先是检测对方的主动断连(即调用 Socket的close方法),其次是检测对方的宕机、异常退出及网络不通。这是一个健壮的通信程序必须具备的。检测对方的主动断连很简单,主要一方主动断连,另一方如果在进行读操作,则此时的返回值只-1,一旦检测到对方断连,则应该主动关闭本端的连接(调用Socket的close方法)。而检测对方的宕机、异常退出及网络不通,常用方法是用“心跳”,也就是双方周期性的发送数据给对方,同时也从对方接收“心跳”,如果连续几个周期都没有收到对方心跳,则可以判断对方宕机、异常退出或者网络不通,此时也需要主动关闭本端连接,如果是客户端可在延迟一定时间后重新发起连接。虽然Socket有一个keep alive选项来维护连接,如果用该选项,一般需要两个小时才能发现对方的宕机、异常退出及网络不通。

是处理效率问题。不管是客户端还是服务器,如果是长连接一个程序至少需要两个线程,一个用于接收数据,一个用于发送心跳,写数据不需要专门的线程,当然另外还需要一类线程(俗称Worker线程)用于进行消息的处理,也就是说接收线程仅仅负责接收数据,然后再分发给Worker进行数据的处理。如果是短连接,则不需要发送心跳的线程,如果是服务器还需要一个专门的线程负责进行连接请求的监听。这些是一个通信程序的整体要求,具体到你的程序中,就看你如何对程序进行优化了。

openfire自带了心跳机制,是通过在服务端设置Client Connections –> Idle Connections Policy中的:

Disconnect client after they have been idle for [*] seconds
mina框架提供了空闲检测功能,这项功能可检测客户端口建立了TCP/IP连接、却不发送任何消息的情况。

当我们设置了选项时长,openfire会调用mina的session.setIdleTime()方法,在客户端口连接经过指定时长未发送任何消息的情况下触发sessionIdle事件,由sessionIdle()方法处理。

Openfire can send an XMPP Ping request to clients that are idle, before they are disconnected. Clients must respond to such a request, which allows Openfire to determine if the client connection has indeed been lost. The XMPP specification requires all clients to respond to request. If a client does not support the XMPP Ping request, it must return an error (which in itself is a response too).
选项中 Send an XMPP Ping request to idle clients对ConnectionHandler进行了再一次封装,在第一次触发sessionIdle时发送一次ping 消息,迫使客户端进行响应。

在ConnectionHandler 的sessionIdle()方法中判断当前的idle次数大于1次时将关闭客户端连接。我们设置了idle Time 之后这个idle的检测发生在达到一半时间和达到指定时间,每次检测都会将idle 的次数加1 。 例如我们设置了120s,则mina会在这个时长的一半时间内,60s,发送一个ping包,等待客户端响应,然后在到达指定时长后,客户端仍未发送消息时再发一次。如果两次都没有收到回复,则认为连接断掉了,触发sessionIdle()事件,关闭连接。

但是两次心跳中间的消息包就会丢失,并且不会抛出异常,因此选择通过消息回执的方式,保证消息的稳定性XEP-0184。

考虑解决方案:

在发生Exception后主动关闭连接。
心跳机制的设置与优化
由于服务端没有断开异常连接,会产生大量的CLOSE_WAIT和FIN_WAIT2状态。可以通过

1
netstat -na|grep 9090|grep FIN_WAIT2|wc -l

或者:

1
netstat -n | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}'

命令查看。

返回结果:

1
2
3
4
5
TIME_WAIT 1
CLOSE_WAIT 87
FIN_WAIT1 4
ESTABLISHED 377
FIN_WAIT2 3305

解决方案:
为 FIN_WAIT2 增加超时机制
CLOSE_WAIT缩短keepAlive时间
修改linux系统内核参数:

1
vi /etc/sysctl.conf

加入以下数据:

1
2
3
4
net.ipv4.tcp_keepalive_time = 30
net.ipv4.tcp_keepalive_probes = 2
net.ipv4.tcp_keepalive_intvl = 2
net.ipv4.tcp_fin_timeout = 30

设置fin的连接超时时间为30s

xmpp通信过程分析

XMPP消息格式

Jabber/XMPP系统使用XML流在不同实体之间相互传输数据。在两个实体的连接期间,XML流将从一个实体传送到另一个实体。在实体间,有三个顶层的XML元素:,和。每一个都包含属性和子节点。下面将分别描述这些元素。

1)消息(message)元素:

一个即时消息系统最基本的功能就是能够在两个用户之间实时交换消息,元素就提供了这个功能。每条消息都有一个或多个属性和子元素。属性“from”和“to”分别表示了消息发送者和接收者的地址。也可以包含一个“type”属性,这给接收者一个提示,这个消息是什么样的消息。表3-1给出了“type”属性的可能取值。中也可以包含“id”属性,用来唯一的标识一个输出消息的响应。

2)状态(presence)元素:

元素用来传递一个用户的存在状态的感知信息。用户可以是“available”,要么是“unavailable”,“Hide”等。当用户连接到即时消息服务器后,好友发给他的消息就立即被传递。如果用户没有连接到服务器,好友发给他的消息将被服务器存储起来直到用户连接到服务器。用户通过即时消息客户端自己控制可用性。但是,如果用户断开了同服务器的连接,服务器将发送给订阅了这个用户的存在信息的用户通知他们用户已经不可用。还包含了两个子元素:和。包含了一个对的文本描述。

3)IQ(Info<Query)元素

IQ元素是Jabber/XMPP消息协议的第三个顶层元素。IQ代表”Info/Query”,用来发送和获取实体之间的信息。IQ消息是通过“请求/响应”机制在实体间进行交换的。IQ元素用于不同的目的,它们之间通过不同的命名空间来加以区分。在Jabber/XMPP消息协议里有许多的命名空间,但最常用的命名空间是:”jabber:iq:register”,”jabber:iq:auth”,”jabber:iq:roster”。

上面描述了Jabber协议的三个顶层节点。通过这种格式Jabber消息不仅可以是简单的文本(text),而且可以携带复杂的数据和各种格式的文件,也就是说Jabber不仅可以用在人与人之间的交流,而且可以实现软件与软件或软件与人之间的交流。Jabber的这种功能大大扩展了即时通信的应用范围。

XMPP通信过程

XMPP被设计为能够异步并能快速交换短文的协议,为此客户端和服务器之间存在两条XML流,用于异步通信,流传输的是XML文档。TCP是XMPP的默认承载协议,在客户端到服务器的通信模型下,需要一条TCP链路;在服务器到服务器的通信模式下,需要两条TCP链路以传输两个对端数据。

XMPP流以标记开始,以结束。XMPP流可以视为一个well-form的XML文档,每次传输的是文档的片段,而片段则是well-form的XML标签。

stream标记的属性如下:
to只能用于从客户端到服务器的XML流中。
from只能用于从服务器到客户端的XML流中。
id只能用于从接收实体到发送实体的XML流中,id必须唯一,用于标记会话。
xml:lang只能用于发起方,用于约定语言。如发起方没有携带xml:lang属性,接收方应使用默认语言。
version至少在“1.0”以上。

可以看到客户端和服务器双方都以标记开始会话,即双方都在传输一个完整的XML文档。以发起会话后,双方建立TLS安全链路,然后用SASL(稍后说明)认证对方身份,最后绑定资源并开始传输消息报文。

TLS是SSL的后继者,TLS1.0和SSL3.0非常相似。TLS建立在传输层上,通信双方先用不对称加密算法传输对称加密算法的密钥,然后用对称加密算法加密传输内容。

TLS

第1步: 客户端发起连接:

1
<stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' to='maimaijun.com' version='1.0'>

第2步: 服务器向客户端返回一个标记:

1
<stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' id='c2s_123' from='maimaijun.com' version='1.0'>

第3步: 服务器向客户端发送STARTTLS扩展,并携带认证机制和流特性:

1
<stream:features><starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'><required/></starttls><mechanisms xmlns='urn:ietf:params:xml:ns:xmpp-sasl'><mechanism>DIGEST-MD5</mechanism><mechanism>PLAIN</mechanism></mechanisms></stream:features>

第4步: 客户端发送STARTTLS给服务器:

1
<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>

第5步: 服务器提示客户端可以继续:

1
<proceed xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>

第6步: 客户端和服务器尝试在已有的TCP链路上完成TLS握手过程。
第7步: 如果TLS握手成功,客户端向服务器发起一个新流:

1
<stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' to='maimaijun.com' version='1.0'>

第8步: 服务器以一个stream头响应,同时携带可能的流特性:

1
<stream:stream xmlns:stream="http://etherx.jabber.org/streams" xmlns="jabber:client" from="maimaijun.com" id="3f857b69" xml:lang="en" version="1.0"><stream:features><starttls xmlns="urn:ietf:params:xml:ns:xmpp-tls"></starttls><mechanisms xmlns="urn:ietf:params:xml:ns:xmpp-sasl"><mechanism>DIGEST-MD5</mechanism><mechanism>PLAIN</mechanism><mechanism>ANONYMOUS</mechanism><mechanism>CRAM-MD5</mechanism></mechanisms><compression xmlns="http://jabber.org/features/compress"><method>zlib</method></compression><auth xmlns="http://jabber.org/features/iq-auth"/><register xmlns="http://jabber.org/features/iq-register"/></stream:features>

第9步: 客户端继续SASL握手。

SASL(Simple Authentication and Security Layer protocol)

第1步: 客户端向服务器发起流请求:

1
<stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' to='maimaijun.com' version='1.0'>

第2步: 服务器向客户端响应stream标记:

1
<stream:stream xmlns:stream="http://etherx.jabber.org/streams" xmlns="jabber:client" from="maimaijun.com" id="9a7eced8" xml:lang="en" version="1.0">

第3步: 服务器向客户端提示可用的认证方法:

1
<stream:features><starttls xmlns="urn:ietf:params:xml:ns:xmpp-tls"></starttls><mechanisms xmlns="urn:ietf:params:xml:ns:xmpp-sasl"><mechanism>DIGEST-MD5</mechanism><mechanism>PLAIN</mechanism><mechanism>ANONYMOUS</mechanism><mechanism>CRAM-MD5</mechanism></mechanisms><compression xmlns="http://jabber.org/features/compress"><method>zlib</method></compression><auth xmlns="http://jabber.org/features/iq-auth"/><register xmlns="http://jabber.org/features/iq-register"/></stream:features>

第4步: 客户端选择一种认证方法:

1
<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='DIGEST-MD5'/>

第5步: 服务器发送以BASE64编码的验证码给客户端:

1
2
3
4
<challenge xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>cmVhbG09InNvbWVyZWFsbSIsbm9uY2U9Ik9BNk1HOXRFUUdtMmhoIixxb3A9ImF1dGgiLGNoYXJzZXQ9dXRmLTgsYWxnb3JpdGhtPW1kNS1zZXNzCg==</challenge>
验证码解码后如下:
realm="somerealm",nonce="OA6MG9tEQGm2hh",qop="auth",charset=utf-8,algorithm=md5-sess

第6步: 客户端发送以BASE64编码的响应码:

1
2
3
4
<response xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>dXNlcm5hbWU9InNvbWVub2RlIixyZWFsbT0ic29tZXJlYWxtIixub25jZT0iT0E2TUc5dEVRR20yaGgiLGNub25jZT0iT0E2TUhYaDZWcVRyUmsiLG5jPTAwMDAwMDAxLHFvcD1hdXRoLGRpZ2VzdC11cmk9InhtcHAvZXhhbXBsZS5jb20iLHJlc3BvbnNlPWQzODhkYWQ5MGQ0YmJkNzYwYTE1MjMyMWYyMTQzYWY3LGNoYXJzZXQ9dXRmLTgK</response>
解码后的响应码如下:
username="somenode",realm="somerealm",nonce="OA6MG9tEQGm2hh",cnonce="OA6MHXh6VqTrRk",nc=00000001,qop=auth,digest-uri="xmpp/example.com",response=d388dad90d4bbd760a152321f2143af7,charset=utf-8

第7步: 服务器发送另一个以BASE64编码的验证码给客户端:

1
2
3
4
<challenge xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>cnNwYXV0aD1lYTQwZjYwMzM1YzQyN2I1NTI3Yjg0ZGJhYmNkZmZmZAo=</challenge>
解码后的验证码:
rspauth=ea40f60335c427b5527b84dbabcdfffd

第8步: 客户端响应验证码:

1
<response xmlns='urn:ietf:params:xml:ns:xmpp-sasl'/>

第9步: 服务器提示客户端认证通过:

1
<success xmlns='urn:ietf:params:xml:ns:xmpp-sasl'/>

第10步: 客户端向服务器发起新连接:

1
2
3
4
5
<stream:stream
xmlns='jabber:client'
xmlns:stream='http://etherx.jabber.org/streams'
to='example.com'
version='1.0'>

第11步: 服务器响应一个stream头,可能携带特性:

1
2
3
4
5
6
7
8
9
<stream:stream
xmlns='jabber:client'
xmlns:stream='http://etherx.jabber.org/streams'
id='c2s_345'
from='example.com'
version='1.0'> <stream:features>
<bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'/>
<session xmlns='urn:ietf:params:xml:ns:xmpp-session'/>
</stream:features>

绑定资源

客户端登陆
SENT:

1
<iq id="1a58416" type="get"><query xmlns="jabber:iq:auth"><username>mbed</username></query></iq>

RECV:

1
<iq id='5573507c' type='result'><query xmlns='jabber:iq:auth'><username>mbed</username><digest/><password/><resource/></query></iq>

注意中间的ID,这个ID是服务器端返回给客户端的验证信息,验证信息一般是以该ID号+用户密码通过SHA1(RFC3174)算法进行操作的。也就是说客户端得到该ID和密码经过SHA1算法加密后返回给服务器。

SENT(客户端提交字段内容进行验证):
文本格式,非加密模式

1
2
3
4
5
6
7
<iq type='set' id='auth2'>
<query xmlns='jabber:iq:auth'>
<username>mbed</username>
<password>mirror</password>
<resource>flash</resource>
</query>
</iq>

加密模式

1
2
3
<iq id="ee17dc3f" type="set"><query xmlns="jabber:iq:auth"><username>mbed</username><resource>javaScript</resource><digest>459154dc8ea4a74ba5f692f79393e1c7e9de9fda</digest></query></iq>
或者
<iq id="ee17dc3f" type="set"><query xmlns="jabber:iq:auth"><username>mbed</username><resource>flash</resource><digest>459154dc8ea4a74ba5f692f79393e1c7e9de9fda</digest></query></iq>

这个digest就是上面经过SHA1算法得出的结果字段;
如果客户端发送的字段包括了用户名和IQ-GET的字段,服务器不应该返回错误消息(因为需要服务器判断当前用户名是否在使用),如果服务器不支持可插入的简单认证及密码模块,那么必须返回一个的错误;如果客户端企图使用SASL认证但是失败,服务器必须返回错误信息
在认证过程中,jabber:iq:auth命名、用户名和资源是必须要求客户端提供的,而服务器返回的XML流中也必须提供这2个元素。

RECV:

1
2
3
4
<iq id='jcl_104' type='result'/>
<presence><c node="http://exodus.jabberstudio.org/caps" ver="0.9.1.0" xmlns="http://jabber.org/protocol/caps"/><status>Available</status><priority>1</priority></presence>
<iq type='set' id='1a58416'><query xmlns='jabber:iq:auth'><username>mbed</username><password>mirror</password><resource>flash_as3</resource></query></iq>
<iq xmlns='jabber:client' type="get" to="muc.maimaijun.com"><query xmlns='jabber:iq:mucAnonyRoom' actionType='getRoomIndex'><item><customerIndex>1</customerIndex></item></query></iq>

登录结果
成功

1
<iq type='result' id='auth2'/>

失败 – 认证失败,可能是用户名密码不匹配或数字验证错误

1
2
3
4
5
<iq type='error' id='auth2'>
<error code='401' type='auth'>
<not-authorized xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>
</error>
</iq>

失败 – 资源冲突/错误

1
2
3
4
5
<iq type='error' id='auth2'>
<error code='409' type='cancel'>
<conflict xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>
</error>
</iq>

失败 – 没有提供需要验证的字段

1
2
3
4
5
<iq type='error' id='auth2'>
<error code='406' type='modify'>
<not-acceptable xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/>
</error>
</iq>

客户端连接成功后使用iq(Info Query)查询服务器资源。

1
2
3
4
<iq type='set' id='bind_2'> <bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'> <resource>someresource</resource> </bind> </iq>
服务器确认客户端要绑定的资源后,必须返回一个iq标签。
<iq type='result' id='bind_2'> <bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'> <jid>somenode@example.com/someresource</jid> </bind> </iq>

XML短语(Stanzas)

有三种短语——(消息)、(在线状态)和(信息查询),三种短语都有共同属性,如下。

  1. Message语义
    message短语可视为一种推送机制——某个实体向另一个实体推送信息。所有message短语必须携带to属性,以标明接收者。服务器收到message短语后,或路由或投递给接收者。
  2. Presence语义
    presence可被视为一个基本的广播或“发布-订阅”机制,多个实体接收他们订阅的关于某个实体的信息。
  3. IQ语义
    Info/Query,或IQ,是一种”请求-响应“机制,与HTTP类似。IQ使一个实体能够向另一个实体发起请求,请求/响应报文以id属性标示。IQ交互通常以get/result和set/result模式执行。
  4. 基于XMPP的即时信使扩展
    XMPP标准由XMPP Core(RFC3920)、XMPP IM(RFC3921)、XMPP CPIM(RFC3922)(映射XMPP到IETF的CPIM规范)、XMPP E2E(RFC3922)(端到端信号和对象加密)、XMPP URN(RFC4854)(基于XMPP扩展的Uniform Resource Name树)、XMPP ENUM(RFC4979)(在IANA注册的枚举服务)、XMPP URI(RFC5122)(对RFC4622的勘误)。本文只覆盖了XMPP Core和XMPP IM,其中XMPP IM由XMPP Core扩展而得。