protocal flushfilebufferss插件

玩转Protocol Buffers
发表于 日 10:09 | Hits: 3248
1. 人人都爱Protocol Buffers
1.1 Protocol Buffers(PB)是什么?
Protocol buffers are Google’s language-neutral, platform-neutral, extensible mechanism for serializing structured data – think XML, but smaller, faster, and simpler
. You define how you want your data to be structured once, then you can use special generated source code to easily write and read your structured data to and from a variety of data streams and using a variety of languages – Java, C++, or Python. You can even update your data structure without breaking deployed programs that are compiled against the “old” format.(摘自)
针对英文不太好的同学,除了强烈建议好好学一下英文外(PB的最新文档总是英文的),这里笔者按照自己的理解试着翻译一下:protocol buffers是google提供的一种将结构化数据进行序列化和反序列化的方法,其优点是语言中立,平台中立,可扩展性好,目前在google内部大量用于数据存储,通讯协议等方面。PB在功能上类似XML,但是序列化后的数据更小,解析更快,使用上更简单。用户只要按照proto语法在.proto文件中定义好数据的结构,就可以使用PB提供的工具(protoc)自动生成处理数据的代码,使用这些代码就能在程序中方便的通过各种数据流读写数据。PB目前支持Java, C++和Python3种语言。另外,PB还提供了很好的向后兼容,即旧版本的程序可以正常处理新版本的数据,新版本的程序也能正常处理旧版本的数据。
1.2 如何使用Protocol Buffers?
这里以官网的通讯簿
例子来简单介绍一下PB的常规使用方式,非常规的使用方式在后面几章逐一介绍
1.在addressbook.proto文件里定义通讯簿消息的格式,一个通讯簿(AddressBook)由可重复的Person组成,一个person由两个必需存在的name和id字段,以及一个可选的email字段,和可重复的PhoneNumber构成。PhoneNumber由number和type组成。
message Person {
required string name = 1;
required int32 id = 2;
optional string email = 3;
enum PhoneType {
MOBILE = 0;
message PhoneNumber {
required string number = 1;
optional PhoneType type = 2 [default = HOME];
repeated PhoneNumber phone = 4;
message AddressBook {
repeated Person person = 1;
2.使用PB提供的工具 protoc根据.proto文件自动生成处理消息的代码
protoc -I=$SRC_DIR --cpp_out=$DST_DIR $SRC_DIR/addressbook.proto
在$DST_DIR里生成了下面两个文件:
addressbook.pb.h,
addressbook.pb.cc
&pre&3.程序使用生成的代码来读写(序列化,反序列化)和操作(get,set)消息
//保存address book
fstream output(argv[1], ios::out | ios::trunc | ios::binary);
address_book.SerializeToOstream(&output));
1.3 为什么写这篇文章
目前网上关于PB的文章大部分只涉及到上面讲的内容, 而实际上PB的能力远不止如此,本文尝试使用PB内建的支持实现自描述消息,动态消息以及两者的结合:动态自描述消息,在此基础上给出一些性能参考和建议。本文以下部分适合对PB有一定使用经验的同学阅读,强烈建议感兴趣的同学在阅读下面章节前再去复习一下Tutorial,因为我会以Tutorial的AddressBook例子来演示自描述消息的实现。
由于笔者知识有限,本文只涉及C++语言的内容,使用Java和Python的同学可参考下文并阅读官网API Reference自己摸索,应该问题不大,笔者就是这么摸索过来的。
为了下文介绍的方便,先明确生产者,消费者两个角色。
:产生消息,填充内容,并序列化保存
:读取数据,反序列化得到消息,使用消息
在我们的例子里生产者和消费者均为为独立的程序,消息序列化后保存在文件中。网络通讯的情况类似,请自行推理?
2. 自描述消息
Tutorial介绍的使用方法要求生产者和消费者在编译时就确定消息格式(.proto文件),生产者和消费者在消息格式上紧耦合。当消息格式发生变化的时候,消费者必须重新编译才能理解新格式。有没有可能解除这种耦合,让消费者能动态的适应消息格式的变换?从原理上进行分析的话发现是可行的。即生产者把定义消息格式的.proto文件和消息作为一个完整的消息序列化保存,完整保存的消息我称之为Wrapper message,原来的消息称之为payload message。消费者把wrapper message反序列化,先得到payload message的消息类型,然后根据类型信息得到payload message,最后通过反射机制来使用该消息。通过这种方式消费者只需要了解这一种wrapper message的格式就能够适应各种payload message的格式。这也是PB官网给出的解决方案:
wrapper message的定义如下所示,第一个字段保存payload message的类型信息(由于message可以内嵌message,而.proto文件可以import 其他.proto,所以这里使用FileDescriptorSet),第二个字段是payload message的类型名字符串,第三个字段是payload message序列化后的数据。
message SelfDescribingMessage {
// Set of .proto files which define the type.
required FileDescriptorSet proto_files = 1;
// Name of the message type.
Must be defined by one of the files in proto_files.
required string type_name = 2;
// The message data.
required bytes message_data = 3;
下面通过改造tutorial例子程序,演示自描述消息的实现方式。
生产者:add_person.cc
1. 使用 protoc生成代码时加上参数–descriptor_set_out,输出类型信息(即SelfDescribingMessage的第一个字段内容)到一个文件,这里假设文件名为desc.set,
protoc –cpp_out=.
–descriptor_set_out=desc.set addressbook.proto
2. payload message使用方式不需要修改
tutorial::AddressBook address_
PromptForAddress(address_book.add_person());//这个函数不需要任何修改
3. 在保存时使用文件desc.set内容填充SelfDescribingMessage的第一个字段,使用AddressBook
AddressBook的full name填充SelfDescribingMessage的第二个字段,AddressBook序列化后的数据填充第三个字段。最后序列化SelfDescribingMessage保存到文件中。
tutorial::SelfDescribingM
fstream desc(argv[2], ios::in | ios::binary);
sdmessage. mutable_proto_files()-&ParseFromIstream(&desc);
sdmessage.set_type_name((address_book.GetDescriptor())-&full_name());
sdmessage.clear_message_data();
address_book.SerializeToString(sdmessage.mutable_message_data());
fstream output(argv[1], ios::out | ios::trunc | ios::binary);
sdmessage.SerializeToOstream(&output));
消费者:list_people.cc
List_people.cc编译时需要知道SelfDescribingMessage,不需要知道AddressBook,运行时可以正常操作AddressBook消息。
1. 首先反序列化SelfDescribingMessage
tutorial::SelfDescribingM
fstream input(argv[1], ios::in | ios::binary);
sdmessage.ParseFromIstream(&input));
2. 通过第一个字段得到FileDescriptorSet,通过第二个字段取得消息的类型名,使用DescriptorPool得到payload message的类型信息Descriptor
SimpleDescriptorD
for(int i=0;i&sdmessage.proto_files().file_size();i++)
db.Add(sdmessage.proto_files().file(i));
DescriptorPool pool(&db);
const Descriptor *descriptor = pool.FindMessageTypeByName(sdmessage.type_name());
3. 使用DynamicMessage new出这个类型的一个空对象,从第三个字段反序列化得到原来的message对象
DynamicMessageFactory factory(&pool);
Message *msg = factory.GetPrototype(descriptor)-&New();
msg-&ParseFromString(sdmessage.message_data());
4. 通过Message的reflection接口操作message的各个字段
3. 动态消息
自描述消息解放了消费者,那么生产者呢?能否在运行时确定消息格式,动态生成消息呢?从原理上分析发现也是可以的。自描述消息的消费者是从文件中读取消息格式信息,我们只要在运行时构建这样的内容就可以实现动态消息。下面以代码说明,本章节的内容由剑豪提供。
最终动态生成的消息格式定义如下所示:
message pair {
required string key = 1;
required uint32 value = 2;
1. 动态定义消息,生成类型信息
FileDescriptorProto file_
file_proto.set_name(&foo.proto&);
// create dynamic message proto names &Pair&
DescriptorProto *message_proto = file_proto.add_message_type();
message_proto-&set_name(&Pair&);
FieldDescriptorProto *field_proto = NULL;
field_proto = message_proto-&add_field();
field_proto-&set_name(&key&);
field_proto-&set_type(FieldDescriptorProto::TYPE_STRING);
field_proto-&set_number(1);
field_proto-&set_label(FieldDescriptorProto::LABEL_REQUIRED);
field_proto = message_proto-&add_field();
field_proto-&set_name(&value&);
field_proto-&set_type(FieldDescriptorProto::TYPE_UINT32);
field_proto-&set_number(2);
field_proto-&set_label(FieldDescriptorProto::LABEL_REQUIRED);
DescriptorP
const FileDescriptor *file_descriptor = pool.BuildFile(file_proto);
const Descriptor *descriptor = file_descriptor-&FindMessageTypeByName(&Pair&);
2. 根据类型信息使用DynamicMessage new出这个类型的一个空对象
// build a dynamic message by &Pair& proto
DynamicMessageFactory factory(&pool);
const Message *message = factory.GetPrototype(descriptor);
// create a real instance of &Pair&
Message *pair = message-&New();
3. 通过Message的reflection操作message的各个字段
// write the &Pair& instance by reflection
const Reflection *reflection = pair-&GetReflection();
const FieldDescriptor *field = NULL;
field = descriptor-&FindFieldByName(&key&);
reflection-&SetString(pair, field, &my key&);
field = descriptor-&FindFieldByName(&value&);
reflection-&SetUInt32(pair, field, 1234);
此时动态生成的pair对象内容为
key: &my key&
value: 1234
完整代码也不多,直接贴上:
#include &iostream&
#include &google/protobuf/descriptor.h&
#include &google/protobuf/descriptor.pb.h&
#include &google/protobuf/dynamic_message.h&
using namespace google::
int main(int argc, const char *argv[])
FileDescriptorProto file_
file_proto.set_name(&foo.proto&);
// create dynamic message proto names &Pair&
DescriptorProto *message_proto = file_proto.add_message_type();
message_proto-&set_name(&Pair&);
FieldDescriptorProto *field_proto = NULL;
field_proto = message_proto-&add_field();
field_proto-&set_name(&key&);
field_proto-&set_type(FieldDescriptorProto::TYPE_STRING);
field_proto-&set_number(1);
field_proto-&set_label(FieldDescriptorProto::LABEL_REQUIRED);
field_proto = message_proto-&add_field();
field_proto-&set_name(&value&);
field_proto-&set_type(FieldDescriptorProto::TYPE_UINT32);
field_proto-&set_number(2);
field_proto-&set_label(FieldDescriptorProto::LABEL_REQUIRED);
// add the &Pair& message proto to file proto and build it
DescriptorP
const FileDescriptor *file_descriptor = pool.BuildFile(file_proto);
const Descriptor *descriptor = file_descriptor-&FindMessageTypeByName(&Pair&);
cout && descriptor-&DebugString();
// build a dynamic message by &Pair& proto
DynamicMessageFactory factory(&pool);
const Message *message = factory.GetPrototype(descriptor);
// create a real instance of &Pair&
Message *pair = message-&New();
// write the &Pair& instance by reflection
const Reflection *reflection = pair-&GetReflection();
const FieldDescriptor *field = NULL;
field = descriptor-&FindFieldByName(&key&);
reflection-&SetString(pair, field, &my key&);
field = descriptor-&FindFieldByName(&value&);
reflection-&SetUInt32(pair, field, 1234);
cout && pair-&DebugString();
3.4 另一种实现方式:动态编译
上面是动态消息的一种方式,我们还可以使用PB 提供的 google::protobuf::compiler 包在运行时动态编译指定的.proto 文件来使用其中的 Message。这样就可以通过修改.proto文件实现动态消息,有点类似配置文件的用法。完成这个工作主要的类叫做 importer,定义在 importer.h 中。
Foo.proto内容如下:
message Pair {
required string key = 1;
required uint32 value = 2;
下面的代码实现同样的动态消息:
#include &iostream&
#include &google/protobuf/descriptor.h&
#include &google/protobuf/descriptor.pb.h&
#include &google/protobuf/dynamic_message.h&
#include &google/protobuf/compiler/importer.h&
using namespace google::
using namespace google::protobuf::
int main(int argc, const char *argv[])
DiskSourceTree sourceT
//look up .proto file in current directory
sourceTree.MapPath(&&, &./&);
Importer importer(&sourceTree, NULL);
//runtime compile foo.proto
importer.Import(&foo.proto&);
const Descriptor *descriptor = importer.pool()-&FindMessageTypeByName(&Pair&);
cout && descriptor-&DebugString();
// build a dynamic message by &Pair& proto
DynamicMessageF
const Message *message = factory.GetPrototype(descriptor);
// create a real instance of &Pair&
Message *pair = message-&New();
// write the &Pair& instance by reflection
const Reflection *reflection = pair-&GetReflection();
const FieldDescriptor *field = NULL;
field = descriptor-&FindFieldByName(&key&);
reflection-&SetString(pair, field, &my key&);
field = descriptor-&FindFieldByName(&value&);
reflection-&SetUInt32(pair, field, 1111);
cout && pair-&DebugString();
4. 动态自描述消息
好了,到此为止我们已经可以通过自描述消息解放消费者,通过动态消息解放生产者。最后介绍的大杀器是两者的结合:动态自描述消息,彻底解放生产者和消费者。
仍以上面的消息为例说明:
message pair {
required string key = 1;
required uint32 value = 2;
这次我们不使用第二章介绍的wrapper message方式,改为通过文件格式约定实现自描述,网络通信协议可参考这种方式。
生产者和消费者商定文件格式如下:
1. 动态定义消息,生成类型信息;根据类型信息生成一个空的message对象;通过Message的reflection操作message的各个字段。这些和动态消息处理一致,这里就不赘述了。
2. 使用CodedOutputStream写文件,依次保存如下信息:
a) MAGCI_NUM, 消费者可以用来验证文件格式是否一致或者格式是否错误。
b) FileDescriptorProto序列化后数据的size
c) 序列化的FileDescriptorProto数据
d) Payload message序列化后数据的size
e) 序列化的Payload message数据
代码如下:
const unsigned int MAGIC_NUM=2988;
int fd = open(&dpb.msg&, O_WRONLY|O_CREAT,0666);
ZeroCopyOutputStream* raw_output = new FileOutputStream(fd);
CodedOutputStream* coded_output = new CodedOutputStream(raw_output);
coded_output-&WriteLittleEndian32(MAGIC_NUM);
file_proto.SerializeToString(&data);
coded_output-&WriteVarint32(data.size());
coded_output-&WriteString(data);
data.clear();
pair-&SerializeToString(&data);
coded_output-&WriteVarint32(data.size());
coded_output-&WriteString(data);;
delete coded_
delete raw_
close(fd);
1. 使用CodedInputStream读取文件,先通过MAGIC_NUM判断文件格式是否正确,然后反序列化FileDescriptorProto,得到payload message的类型信息
FileDescriptorProto file_
int fd = open(&dpb.msg&, O_RDONLY);
ZeroCopyInputStream* raw_input = new FileInputStream(fd);
CodedInputStream* coded_input = new CodedInputStream(raw_input);
unsigned int magic_
coded_input-&ReadLittleEndian32(&magic_number);
if (magic_number != MAGIC_NUM) {
cerr && &File not in expected format.& &&
coded_input-&ReadVarint32(&size);
char* text = new char[size + 1];
coded_input-&ReadRaw(text, size);
text[size] = '\0';
file_proto.ParseFromString(text);
DescriptorP
const FileDescriptor *file_descriptor = pool.BuildFile(file_proto);
const Descriptor *descriptor = file_descriptor-&FindMessageTypeByName(&Pair&);
2. 使用DynamicMessage new出这个类型的一个空对象,从文件中读取messagedata反序列化得到原来的message
DynamicMessageFactory factory(&pool);
const Message *message = factory.GetPrototype(descriptor);
// create a real instance of &Pair&
Message *pair = message-&New();
coded_input-&ReadVarint32(&size);
text = new char[size + 1];
coded_input-&ReadRaw(text, size);
text[size] = '\0';
pair-&ParseFromString(text);
3. 通过Message的reflection即可操作message的各个字段
5. 天下没有免费的午餐
自描述和动态生成得到的灵活性不是免费的午餐,那么下面我们就以文中的例子来分析一下动态自描述消息相对静态消息在空间和时间上的变化。
1. 空间:由于PB主要用于数据存储和通讯协议,下面分别分析:
以Tutorial中的AddressBook为例分析数据存储的使用场景,添加如下两条记录:
Person ID: 1
Name: Peter
E-mail address:
Home phone #:
Work phone #:
Mobile phone #:
Person ID: 2
E-mail address:
Mobile phone #:
AddressBook
第二章自描述消息
FileDescriptorSet(3+302)
type_name(2+20)
message_data(2+120)
这里需要注意的是表面上看数据量增加了274%,实际上增加的是固定的329字节,即当文件越来越大的时候这部分开销是不会增加的。
以第四章动态自描述消息为例分析在通讯协议中使用PB的应用场景
pair消息内容为:
key: &jianhao&
value: 8888
动态自描述消息
FileDescriptorProto length
FileDescriptorProto
Message length
注意:在网络通讯中由于一次通讯需要传输一次完整的类型信息,所以消息越大越划算。
2. 时间:通过测试对比静态消息和动态自描述消息在日常的使用场景下的效率。
测试中的消息类型如下:
message Pair {
required string key = 1;
required uint32 value = 2;
静态消息使用方式:
pair.set_key(&my key&);
pair.set_value(i);
pair.SerializeToArray(buffer,100);
动态消息使用方式:
const Reflection *reflection = pair-&GetReflection();
const FieldDescriptor *field = NULL;
field = descriptor-&FindFieldByName(&key&);
reflection-&SetString(pair, field, &my key&);
field = descriptor-&FindFieldByName(&value&);
reflection-&SetUInt32(pair, field, i);
pair-&SerializeToArray(buffer,100);
消息使用方式
循环1M时间消耗
循环10M消耗时间
由于绝对时间和机器环境有关,所以相对值更有意义。从上面的测试可知动态消息的赋值和序列化时间是静态消息的赋值和序列化的4倍。
静态消息使用方式:
pair.ParseFromArray(buffer,100);
key=pair.key();
value=pair.value()+i;
动态自描述消息有两种使用方式:
1.仅反序列化&操作payload message,常用于数据存储
pair-&ParseFromArray(buffer,100);
const Reflection *reflection = pair-&GetReflection();
const FieldDescriptor *field = NULL;
field = descriptor-&FindFieldByName(&key&);
key=reflection-&GetString(*pair, field);
field = descriptor-&FindFieldByName(&value&);
value=reflection-&GetUInt32(*pair, field)+i;
2.先反序列化payload message的类型信息,然后动态生成一个空的该类型对象,然后反序列化并操作该对象,常用于通讯协议
FileDescriptorProto file_
file_proto.ParseFromArray(descbuffer,300);
DescriptorP
const FileDescriptor *file_descriptor = pool.BuildFile(file_proto);
const Descriptor *descriptor = file_descriptor-&FindMessageTypeByName(&Pair&);
// build a dynamic message by &Pair& proto
DynamicMessageF
const Message *message = factory.GetPrototype(descriptor);
Message *pair = message-&New();
pair-&ParseFromArray(buffer,100);
const Reflection *reflection = pair-&GetReflection();
const FieldDescriptor *field = NULL;
field = descriptor-&FindFieldByName(&key&);
key=reflection-&GetString(*pair, field);
field = descriptor-&FindFieldByName(&value&);
value=reflection-&GetUInt32(*pair, field)+i;
消息使用方式
循环1M时间消耗
循环10M消耗时间
动态自描述消息(存储方式)
动态自描述消息(通讯方式)
从上面的测试可知动态自描述消息的反序列化和操作时间是静态消息的反序列化和操作的4倍左右。但是如果加上对类型信息的反序列化得化则性能急剧下降到静态消息的接近60倍。
6. 参考资料
/protocol-buffers/?hl=zh-CN
/developerworks/cn/linux/l-cn-gpb/?ca=drs-tp4608
评价列表(3)
12:48:50s/Protocal/Protocol/g+0 小李 voted at
13:37:44文章写的很好, 不知道博主看到请联系我, 需要请教+5 小李 voted at
13:42:222.1节中 &FileDescriptorSet &
这个是什么意思?
有完整一点的proto文件吗?Sliverlight的客户端压解缩数据的问题
[问题点数:40分]
Sliverlight的客户端压解缩数据的问题
[问题点数:40分]
不显示删除回复
显示所有回复
显示星级回复
显示得分回复
只显示楼主
相关帖子推荐:
2014年11月 .NET技术大版内专家分月排行榜第二2014年5月 .NET技术大版内专家分月排行榜第二2014年4月 .NET技术大版内专家分月排行榜第二2012年2月 多媒体/设计/Flash/Silverlight 开发大版内专家分月排行榜第二
2014年10月 .NET技术大版内专家分月排行榜第三2014年9月 .NET技术大版内专家分月排行榜第三2014年1月 .NET技术大版内专家分月排行榜第三2013年12月 .NET技术大版内专家分月排行榜第三2013年10月 .NET技术大版内专家分月排行榜第三2013年5月 .NET技术大版内专家分月排行榜第三2011年9月 .NET技术大版内专家分月排行榜第三2011年2月 .NET技术大版内专家分月排行榜第三2010年2月 .NET技术大版内专家分月排行榜第三
2014年11月 .NET技术大版内专家分月排行榜第二2014年5月 .NET技术大版内专家分月排行榜第二2014年4月 .NET技术大版内专家分月排行榜第二2012年2月 多媒体/设计/Flash/Silverlight 开发大版内专家分月排行榜第二
2014年10月 .NET技术大版内专家分月排行榜第三2014年9月 .NET技术大版内专家分月排行榜第三2014年1月 .NET技术大版内专家分月排行榜第三2013年12月 .NET技术大版内专家分月排行榜第三2013年10月 .NET技术大版内专家分月排行榜第三2013年5月 .NET技术大版内专家分月排行榜第三2011年9月 .NET技术大版内专家分月排行榜第三2011年2月 .NET技术大版内专家分月排行榜第三2010年2月 .NET技术大版内专家分月排行榜第三
2012年9月 多媒体/设计/Flash/Silverlight 开发大版内专家分月排行榜第一2012年8月 多媒体/设计/Flash/Silverlight 开发大版内专家分月排行榜第一2012年7月 多媒体/设计/Flash/Silverlight 开发大版内专家分月排行榜第一2012年5月 多媒体/设计/Flash/Silverlight 开发大版内专家分月排行榜第一2012年3月 多媒体/设计/Flash/Silverlight 开发大版内专家分月排行榜第一2012年2月 多媒体/设计/Flash/Silverlight 开发大版内专家分月排行榜第一2012年1月 多媒体/设计/Flash/Silverlight 开发大版内专家分月排行榜第一2011年11月 多媒体/设计/Flash/Silverlight 开发大版内专家分月排行榜第一2011年8月 多媒体/设计/Flash/Silverlight 开发大版内专家分月排行榜第一2011年4月 多媒体/设计/Flash/Silverlight 开发大版内专家分月排行榜第一2011年3月 多媒体/设计/Flash/Silverlight 开发大版内专家分月排行榜第一2010年9月 多媒体/设计/Flash/Silverlight 开发大版内专家分月排行榜第一2010年8月 多媒体/设计/Flash/Silverlight 开发大版内专家分月排行榜第一2010年7月 多媒体/设计/Flash/Silverlight 开发大版内专家分月排行榜第一2010年6月 多媒体/设计/Flash/Silverlight 开发大版内专家分月排行榜第一2010年5月 多媒体/设计/Flash/Silverlight 开发大版内专家分月排行榜第一2010年3月 多媒体/设计/Flash/Silverlight 开发大版内专家分月排行榜第一2010年2月 多媒体/设计/Flash/Silverlight 开发大版内专家分月排行榜第一2009年12月 多媒体/设计/Flash/Silverlight 开发大版内专家分月排行榜第一2009年11月 多媒体/设计/Flash/Silverlight 开发大版内专家分月排行榜第一2009年10月 多媒体/设计/Flash/Silverlight 开发大版内专家分月排行榜第一2009年6月 多媒体/设计/Flash/Silverlight 开发大版内专家分月排行榜第一
2012年6月 多媒体/设计/Flash/Silverlight 开发大版内专家分月排行榜第二2012年4月 多媒体/设计/Flash/Silverlight 开发大版内专家分月排行榜第二2011年12月 多媒体/设计/Flash/Silverlight 开发大版内专家分月排行榜第二2011年9月 多媒体/设计/Flash/Silverlight 开发大版内专家分月排行榜第二2011年7月 多媒体/设计/Flash/Silverlight 开发大版内专家分月排行榜第二2011年6月 多媒体/设计/Flash/Silverlight 开发大版内专家分月排行榜第二2011年5月 多媒体/设计/Flash/Silverlight 开发大版内专家分月排行榜第二2010年11月 多媒体/设计/Flash/Silverlight 开发大版内专家分月排行榜第二2010年10月 多媒体/设计/Flash/Silverlight 开发大版内专家分月排行榜第二2010年4月 多媒体/设计/Flash/Silverlight 开发大版内专家分月排行榜第二2010年1月 多媒体/设计/Flash/Silverlight 开发大版内专家分月排行榜第二2009年9月 多媒体/设计/Flash/Silverlight 开发大版内专家分月排行榜第二2009年8月 多媒体/设计/Flash/Silverlight 开发大版内专家分月排行榜第二2009年7月 多媒体/设计/Flash/Silverlight 开发大版内专家分月排行榜第二
匿名用户不能发表回复!|
每天回帖即可获得10分可用分!小技巧:
你还可以输入10000个字符
(Ctrl+Enter)
请遵守CSDN,不得违反国家法律法规。
转载文章请注明出自“CSDN(www.csdn.net)”。如是商业用途请联系原作者。}

我要回帖

更多关于 flushfilebuffers 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信