0
点赞
收藏
分享

微信扫一扫

[原]Android上GTalk以及Push机制的XMPP数据选择使用protobuf格式而非XML格式


[url]http://code.google.com/p/protobuf/[/url]

[b]先介绍下什么是protobuf以及有什么好处.[/b]
[i]Protocol Buffers are a way of encoding structured data in an efficient yet extensible format. Google uses Protocol Buffers for almost all of its internal RPC protocols and file formats. [/i]

[b]protobuf在网络编程中的应用思考[/b]

protobuf是google提供的一个开源序列化框架,类似于XML,JSON这样的数据表示语言,其最大的特点是基于二进制,因此比传统的 XML表示高效短小得多。虽然是二进制数据格式,但并没有因此变得复杂,开发人员通过按照一定的语法定义结构化的消息格式,然后送给命令行工具,工具将自动生成相关的类,可以支持java、c++、python等语言环境。通过将这些类包含在项目中,可以很轻松的调用相关方法来完成业务消息的序列化与反序列化工作。

protobuf在google中是一个比较核心的基础库,作为分布式运算涉及到大量的不同业务消息的传递,如何高效简洁的表示、操作这些业务消息在google这样的大规模应用中是至关重要的。而protobuf这样的库正好是在效率、数据大小、易用性之间取得了很好的平衡.

[b]Android上GTalk以及Push机制的XMPP数据选择使用protobuf格式[/b]
Android上Google Talk客户端以及C2DM等Push机制基于XMPP协议, 而XMPP协议通信的数据格式是XML. 正如上面所介绍的, protobuf比XML高效, 节约流量. 所以, 在这里, Google选择将protobuf作为数据格式.
实现起来也不是很复杂. 对于传统的使用XML作为数据格式的XMPP协议, 发送Packets的时候将对象数据serialize为相应的XML格式的数据, 接收Packets的时候将XML格式的数据parse为对象数据; 而对于GTalk使用protobuf作为数据格式的XMPP协议, 发送Packets的时候将对象数据serialize为相应的protobuf格式的数据, 接收Packets的时候将protobuf格式的数据parse为对象数据.
[b]
1. 针对不同的XML Packet类型用不同的字节值表示.[/b]
GtalkCoreMessageTypes定义了不同的XML Packet类型

HEARTBEAT_PING
public static final ProtoBufType HEARTBEAT_ACK;
public static final ProtoBufType LOGIN_REQUEST;
public static final ProtoBufType LOGIN_RESPONSE;
public static final ProtoBufType CLOSE;
public static final ProtoBufType MESSAGE_STANZA;
public static final ProtoBufType PRESENCE_STANZA;
public static final ProtoBufType IQ_STANZA;
public static final ProtoBufType DATA_MESSAGE_STANZA;
public static final ProtoBufType BATCH_PRESENCE_STANZA;
public static final ProtoBufType STREAM_ERROR_STANZA;
public static final ProtoBufType HTTP_REQUEST;
public static final ProtoBufType HTTP_RESPONSE;




ProtoBufStreamConfiguration定义了同的XML Packet类型对应的字节值


public static final byte HEARTBEAT_PING_TAG = 0;
public static final byte HEARTBEAT_ACK_TAG = 1;
public static final byte LOGIN_REQUEST_TAG = 2;
public static final byte LOGIN_RESPONSE_TAG = 3;
public static final byte CLOSE_TAG = 4;
public static final byte MESSAGE_TAG = 5;
public static final byte PRESENCE_TAG = 6;
public static final byte IQ_TAG = 7;
public static final byte DATA_MESSAGE = 8;
public static final byte BATCH_PRESENCE = 9;
public static final byte STREAM_ERROR = 10;
public static final byte HTTP_REQUEST = 11;
public static final byte HTTP_RESPONSE = 12;




[b]2. Parse的流程[/b]


Smack中的PacketReader类是用来从XMPPConnection上读取Packets的. 它会首先判断useProtoBuf标志. 如果是true, 则使用ProtoBufStreamParser作为parser来解析数据流; 否则, 则使用XmlPullParser作为parser来解析数据流.



ProtoBufStreamParser的主要方法为:


public abstract void parse(ByteBuffer byteBuffer)
     throws ProtoBufStreamException, IOException;



ProtoBufStreamParser其实主要是分析数据流的前几个字节获得Packets的类型(message, presence等), 然后会调用ProtoBufEventHandler的handleProtoBuf()方法.


public interface ProtoBufEventHandler {
  public abstract void handleProtoBuf(ProtoBuf protoBuf, long paramLong);
}



而PacketReader实际上实现了ProtoBufEventHandler接口, 所以在PacketReader的handleProtoBuf()方法中进一步解析protobuf数据流, 根据不同的类型创建不同的Packet对象, 比如Message对象, Presence对象等.



[b]3. Serialize的流程[/b]


Smack中的PacketWriter类是用来构建Packets然后写到XMPPConnection上的. 它会首先判断useProtoBuf标志. 如果是true, 则调用serializeAsProtoBuf()来形成数据流; 否则, 调用serializeAsXml()来形成数据流.


serializeAsProtoBuf()方法会调用toProtoBuf(),就像serializeAsXml()会调用toXML().


因此每种Packets类型都需要实现toProtoBuf()和toXML().


public abstract class Packet {
    public abstract ProtoBuf toProtoBuf();
    public abstract String toXML();
}

public class Message extends Packet {
    public ProtoBuf toProtoBuf() {
        ProtoBufType protoBufType = GtalkCoreMessageTypes.MESSAGE_STANZA;
        ProtoBuf protoBuf = new ProtoBuf(protoBufType);
        protoBuf.setString(MessageStanza.ID, getPacketID());
        protoBuf.setString(MessageStanza.TO, getTo());
        protoBuf.setString(MessageStanza.FROM, getFrom());
        protoBuf.setInt(MessageStanza.TYPE, Type.CHAT);
        protoBuf.setInt(MessageStanza.SUBJECT,  this.subject);
        protoBuf.setInt(MessageStanza.BODY,  this.body);
        ......
    }

    public String toXML() {
         StringBuffer buf = new StringBuffer();
         buf.append("<message");
         buf.append(" id=\"").append(getPacketID()).append("\"");
         buf.append(" to=\"").append(getTo()).append("\"");
         buf.append(" from=\"").append(getFrom()).append("\"");
         buf.append(" type=\"").append(Type.CHAT).append("\"");
         ......
         buf.append(">");
         buf.append("<subject>").append(this.subject).append("</subject>")
         buf.append("<body>").append(this.body).append("</body>")
         ......
         return buf.toString();
    }
}

public class Presence extends Packet {
    public ProtoBuf toProtoBuf() {
        ......
    }

    public String toXML() {
         ......
    }
}




serializeAsProtoBuf()方法首先通过调用Packets的toProtoBuf()获得protobuf后, 会再调用ProtoBufStreamSerializer的serialize()方法, 在serialize()方法中会把Packets类型对应的字节值加到protobuf前面.


public interface ProtoBufStreamSerializer {
    public void serialize(ProtoBuf protoBuf, OutputStream outputStream)
      throws ProtoBufStreamException, IOException;

    public byte[] serialize(ProtoBuf protoBuf)
      throws ProtoBufStreamException, IOException;
}

举报

相关推荐

0 条评论