ProtoBuf源码简析
ProtoBuf项⽬描述:
Google Protocol Buffer简称protobuf,为⾼效的⼆进制序列化/反序列化协议(⼀般为google内部使⽤),不同于xml、json等,其更⼩巧、⾼效;avro、thrift等;
其可⽤于⽹络协议、数据存储等语⾔⽆关、平台⽆关、可扩展的序列化结构数据格式。只要按照特定条件可⽀持向前、向后兼容;⽬前提供了C++、Java、Python
三种语⾔的 API,这样各语⾔可以相互序列化和反序列化数据信息(事实上也可以⾃定义实现其他语⾔的API接⼝)。
在使⽤中,⽤户可根据⾃定义或引⼊数据结构(Message)⽂件*.proto;此后通过编译器编译该描述⽂件为指定语⾔的操作接⼝,⽽后将产⽣的操作接⼝⽂件和libprotobuf.lib添加⼊项⽬中进⾏数据序列化和反序列化操作即可,产⽣的序列化后的信息可读性很差,此外反序列化也必须知道对应的数据结构描述⽂件*.proto,
否则⽆法正确地反序列化,也不再有意义,相对xml,json⽆法直接插⼊或修改数据信息内容;
以下仅对C++相关进⾏分析;
项⽬⼯程:
gtest:google ⽩盒测试开源项⽬,主要⽤于单元测试,后⾯的gtest_main、tests项⽬;
gtest_main:简单的对main函数以及testing::InitGoogleTest(&argc, argv);RUN_ALL_TESTS();封装的lib,这样其他测试⼯程只需要包含引⼊该库并集中精⼒在测试⽤例上;
libprotobuf:protobuf基础⼯程库,主要实现;
libprotobuf-lite:
libprotobufc:对应protobuf的编译器封装为库,以⽀持c++、java、python语⾔对*.proto⽂件的编译为相应的接⼝API;
lite-test:
protoc:简单的控制台实现的protoc编译器,该编译器通过命令⾏参数传递对应的*.proto⽂件和编译输出接⼝API选项;依赖于libprotobuf、libprotobufc;
test_plugin:
tests:
*.proto数据描述⽂件说明:
protoc编译器命令⾏参数说明和⽣成⽂件API⽂件名说明:
*.proto ⽂件编译后为*.pb.h以及*.pb.cc;
项⽬内容简析:
libprotobuf:protobuf基础⼯程库,内部主要实现编码和解码等相关操作。
config.h:配置相关,事实上为声明宏hash_map、hash_set头⽂件以及编译宏HAVE_HASH_SET、HAVE_HASH_MAP;
template_util.h :模板元编程相关的⼯具,模板、函数;主要包括:
integral_constant、if_、type_equals_、and_、or_、true_、false_;
type_traits.h :在template_util.h的基础上,定义了⼀系列类型特征萃取相关的模板类或函数如:is_xxx,has_xxx,remove_xxx;
common.h:内部使⽤公共函数、基本类型,全局常量、⽤于当前库的⼯具模板辅助函数;
GOOGLE_DISALLOW_EVIL_CONSTRUCTORS:不允许赋值拷贝宏;
LIBPROTOBUF_EXPORT/LIBPROTOC_EXPORT:导⼊导出宏;
⼀系列标识库版本的宏、常量,格式:major * 10^6 + minor * 10^3 + micro,如:2005000;
VersionString:整形转化为版本字符串;格式:major.minor.micro,如:2.5.0
VerifyVersion:版本兼容性检定函数;
重声明基本类型,便于统⼀处理以及定义了⼏种类型静态变量最⼤最⼩值;
GOOGLE_ARRAYSIZE:安全的获取数组元素个数(若未对齐,则会提⽰除0警告);
implicit_cast:隐式转换模板;类似于static_cast、const_cast的安全版本;
down_cast:向下转换,由⽗类指针对象转型为某个⼦类,内部使⽤static_cast转型,在调试模式下时使⽤dynamic_cast进⾏验证;
CompileAssert:编译时断⾔,结合GOOGLE_COMPILE_ASSERT宏,内部通过msg[-1或1]确定是否可通过编译,可能对浮点数0.0之类的存在歧义;
scoped_ptr/scoped_array:指针包装,智能指针;
LogLevel:⽇志级别,提供INFO、WARN、ERROR、FATAL⼏种严重级别;
LogMessage:消息⽇志,记录、操作消息;
LogMessage:构造函数传参为⽇志级别、⽂件名称、所在⾏;
重载多个版本operator<<;
Finish:完成操作,内部若为⾮FATAL⽇志,则将调⽤InitLogSilencerCountOnce,此外若⽆静默者·则调⽤log_handler_处理该⽇志,默认调⽤接⼝为DefaultLogHandler(默认向stderr输出,格式“[libprotobuf level filename:line] message ”),此外若为FATAL⽇志,
则根据是否使⽤异常抛出FatalException类型对象异常或调⽤abort终⽌进程;
level_:⽇志级别;
filename_:⽂件名称;
line_:所在⾏;
message_:消息内容;
LogFinisher:log⽇志完成者,实现了operator=,内部调⽤LogMessage的Finish函数;
GOOGLE_LOG:log宏,封装LogFinisher作为⼯具封装;
GOOGLE_LOG_IF:条件log宏;
GOOGLE_CHECK_XXX:⼀系列检测宏,内部调⽤GOOGLE_LOG_IF(FATAL,...);
CheckNotNull:⾮NULL检测以及宏GOOGLE_CHECK_NOTNULL;
debug模式下的GOOGLE_DCHECK_XXX调试宏;
SetLogHandler:设置⽇志控制器log_handler_,若参数为NULL则设置为NullLogHandler,其不处理⽇志信息,
LogSilencer:⽇志静默者,跳过⾮FATAL⽇志,使得log_handler_控制器不处理⽇志;使⽤时可直接在某个函数点创建临时对象即可,在作⽤域内,产⽣的⽇志信息将会被“吸收”;作⽤域外则恢复到正常的⽇志处理;
Closure:称为关闭、终⽌的基类,内部只提供了纯虚函数Run;
FunctionClosureXXX/MethodClosureXXX:继承于Closure类,实现Run内部调⽤传⼊的函数并根据参数删除释放当前本类对象;前者主要传⼊普通或类的静态成员函数,后者主要处理类对象以及相应类的成员函数;⽬前仅实现了⽀持0,1,2个参数的继承类;
NewCallback/NewPermanentCallback:分别重载了对应不同参数类型的模板帮助函数,内部通过new创建FunctionClosureXXX/MethodClosureXXX;
前者创建了调⽤Run会⾃动删除对象;后者为永久对象,⽤户可根据需要某个时刻delete该对象;
DoNothing:空函数操作;
Mutex:锁,通过IMPL技巧实现,内部利⽤临界区实现锁机制,此外AssertHeld函数⽤以在调试模式下检测当前线程下是否为获取到锁的线程;
MutexLock:⾃动锁,对Mutex的封装,此外重声明锁ReaderMutexLock、WriterMutexLock;
MutexLockMaybe:同MutexLock,但更安全;
IsStructurallyValidUTF8:检测是否包含UTF-8的字符编码;
ghtonl:主机字节序转化为⽹络字节序,同htonl;
字符串函数strip的作用ShutdownProtobufLibrary:关闭protobuf库,主要为执⾏关闭操作函数集vector<void (*)()>的shutdown_functions内容,释放shutdown_functions、
shutdown_functions_mutex锁对象;
OnShutdown:注册关闭库时被调⽤的函数,添加⾄shutdown_functions;
FatalException:继承于std::exception,构造函数提供⽂件名称、所在⾏、消息内容,并添加filename、line、message操作⽅便获取相应成员变量值;
platform_macros.h:跨平台相关的宏定义;
atomicops.h:提供跨平台的原⼦操作,内部实现根据相应平台编译相应接⼝的实现,以下以X86 msvc编译器为例,将采⽤atomicops_internals_x86_msvc的实现;
Atomic32/AtomicWord:原⼦类型重声明;
NoBarrier_CompareAndSwap:内部调⽤InterlockedCompareExchange,实现原⼦的⽐较交换操作;
NoBarrier_AtomicExchange:内部调⽤InterlockedExchange,实现原⼦指定值操作;
NoBarrier_AtomicIncrement:内部调⽤InterlockedExchangeAdd,实现原⼦加操作,累加的数值为参数的2倍值;
Barrier_AtomicIncrement:同NoBarrier_AtomicIncrement;、
Acquire_CompareAndSwap:事实上内部调⽤NoBarrier_CompareAndSwap;
Release_CompareAndSwap:事实上内部调⽤NoBarrier_CompareAndSwap;
MemoryBarrier:内部调⽤MemoryBarrier,实现硬件防护,以防⽌CPU或编译器乱序执⾏;
NoBarrier_Store:⾮防护设置,参数指针对象ptr设置值为value;
Acquire_Store:内部调⽤NoBarrier_AtomicExchange;
Release_Store:同NoBarrier_Store;
NoBarrier_Load:获取当前参数值;
Acquire_Load:获取临时参数值;
Release_Load:获取MemoryBarrier防护后的当前参数值;
once.h:主要封装初始化⼀次的操作;
通过定义GOOGLE_PROTOBUF_DECLARE_ONCE宏原⼦变量以及初始化函数,当调⽤GoogleOnceInit时实现执⾏⼀次初始化函数;
⽰例:void Init();GOOGLE_PROTOBUF_DECLARE_ONCE(once_init);void InitOnce(){GoogleOnceInit(&once_init,&Init);};任何多次调⽤InitOnce函数只会最终
调⽤Init初始化函数⼀次;
内部定义了三种原⼦状态:未初始化ONCE_STATE_UNINITIALIZED、正在执⾏初始化ONCE_STATE_EXECUTING_CLOSURE、已完成初始化ONCE_STATE_DONE; GoogleOnceInit:初始化⼀次函数,参数分别为原⼦、初始化函数;提供⽆参版本和重载模板⼀个参数的版本;函数内部调⽤Acquire_Load获取原⼦状态,此后根据状态处理创建FunctionClosure0执⾏GoogleOnceInitImpl;
GoogleOnceInitImpl:内部处理原⼦状态并处理多线程下执⾏流程;若未初始化则设置原⼦为正在初始化并执⾏FunctionClosure0的Run函数,执⾏完成后设置原⼦为已完成初始化状态;若其他线程进⼊时,若此时仍为正在执⾏初始化,则循环查询原⼦状态并调⽤SchedYield放弃CPU时间⽚;
strutil.h:字符、字符串⼯具函数;
ascii_isalnum:ASCII码是否为a~z或A~Z或0~9的字符;
ascii_isdigit:ASCII码是否为数字字符;
HasPrefixString:原始字符串是否有指定的前缀字符串;
StripPrefixString:获取剥去原始字符串中的指定前缀的字符串,否则为原始字符串;
HasSuffixString:原始字符串是否有指定的后缀字符串;
StripSuffixString:获取剥去原始字符串中的指定后缀的字符串,否则为原始字符串;
StripString:查指定字符串s中被匹配的模式字符串remove中出现的位置,并替换为字符replacewith;内部调⽤strpbrk实现查匹配;
LowerString:字符串⼩写化;
UpperString:字符串⼤写化;
StringReplace:相对StripString,其实现字符串分⽚替换为新的字符串,若参数replace_all为true,则全部替换,否则只替换第⼀次出现的;
SplitStringUsing:对给定的模式字符串delim,匹配原始字符串full,并拆分⾄vector<string>* res中;
SplitStringAllowEmpty:相对SplitStringUsing,其允许空原始字符串和传回空字符串;
JoinStrings:提供两个版本,合并字符串和分隔符字符串合并,字符串间为分隔符字符串组合为返回字符串;
UnescapeCEscapeSequences:转化字符串各字符为相应的ASCII码;
UnescapeCEscapeString:同UnescapeCEscapeSequences;
CEscapeString/CEscape:转化字符串,保存部分标识(⽬前\n, \r, \t, ", ', \以及不可打印的字符将被隐藏(保护));
字符串与数值间的相互转化;
strto32/strtou32/strto64/strtou64:字符串转数值(内部调⽤strtoXXX系列函数);
FastXXXToBuffer:系列函数,数值转化为字符串,字符串右对齐;
FastXXXToBufferLeft:系列函数,数值转化为字符串,字符串左对齐;
SimpleItoa:重载版本,整型数值转字符串,参数为有符号时内部调⽤FastXXXToBuffer,⽆符号时调⽤FastXXXToBufferLeft;
SimpleDtoa/SimpleFtoa:分别为双精度、单精度浮点转字符串;
DoubleToBuffer/FloatToBuffer:双精度、单精度浮点转字符串;
⽬前控制字符串转化最⼤长度为kDoubleToBufferSize(32位),kFloatToBufferSize(24位);
NoLocaleStrtod:⽆区域化字符串转双精度浮点数;类似于strtod;
stl_util.h:STL相关的部分辅助⼯具、函数;
STLDeleteContainerPointers:模板实现,删除释放容器内指针对象资源,参数为容器收尾迭代器对象;
STLStringResizeUninitialized:重置容器⼤⼩,针对string容器,内部调⽤resize;
string_as_array:string字符串当前array使⽤,内部取string的begin地址位置;使⽤时⽰例;string str;string_as_array(&str)[i];
个⼈认为与string的operator[]等价;
STLDeleteElements:模板实现,内部调⽤STLDeleteContainerPointers,且调⽤容器对象的clear接⼝;释放容器内对象资源且清空容器;
STLDeleteValues:模板实现,⼀般⽤在map或pair对的容器参数,以释放容器value值对应的对象内容,并调⽤clear清空容器;
hash.h:封装实现内部使⽤的hash_map和hash_set;
hash<key>:模板类,继承于std::hash_compare<key>;
CstringLess:函数对象(仿函数),作为hash_compare<const char*, CstringLess>的operator<⽐较操作模板参数,其仅⽀持const char*参数(内部通过strcmp⽐较);
hash<const char*>:hash模板特化版本,继承于std::hash_compare<const char*, CstringLess>;
hash_map/hash_set:分别继承与hash_map/hash_set;对于⾮MSVC编译器,则采⽤的⾃⼰的实现,此外对hash<const char*>则使⽤result = 5 * result + *str;
循环叠加的⽅式处理;
hash<string>:hash模板特化版本;
hash<pair<First, Second> >:针对pair的版本;
streq:仿函数,实现⽐较const char*字符串相等操作,内部调⽤strcmp;
maputil.h:map或hash_map辅助⼯具、函数,以下的map代表map或hash_map;
FindWithDefault:查map容器中指定key的value值,否则返回提供的默认值value;
FindOrNull:查map容器中指定key的value值地址,否则返回NULL;
FindOrDie:查map容器中指定key的value值地址,否则抛出FatalException异常或终⽌进程;
FindPtrOrNull:查map容器中指定key的value值,否则返回0;
InsertOrUpdate:若map容器存在对应的key则更新,否则插⼊元素,返回值false表⽰更新,true为插⼊;
InsertIfNotPresent:同InsertOrUpdate,不过存在对应的key时不再更新,返回值false为插⼊失败,true插⼊成功;
stringprintf.h:字符串格式化输出、打印;
StringPrintf:格式化输出字符串(内部通过调⽤StringAppendV实现);
SStringPrintf:同StringPrintf,但是传⼊的参数dst会被清空;
StringAppendV:格式化字符串,内部vsnprintf与va结合进⾏格式化,并采取基于堆和栈的⽅式实现长短字符串的格式化(栈最⼤⼤⼩kSpaceLength(1024字节)); StringAppendF:同StringAppendV,内部不会清空参数dst,会追加格式化后的字符串;
StringPrintfVector:打印格式化vector<string>数据容器,内部调⽤StringPrintf进⾏格式化;
kStringPrintfVectorMaxArgs:StringPrintfVector中vector容器的最⼤参数(最⼤32个,若v容器⼤⼩⼤于该值将调⽤LOG(FATAL));
string_printf_empty_block:保护StringPrintfVector打印,为空块;
substitute.h:类似于stringprintf.h的字符串格式化,但是⽐stringprintf.h更加⾼效且使⽤⽅式上有所不同;
SubstituteArg:格式化参数类,提供了多个⽀持基本数据类型的构造函数,此外提供提取转化后字符串data和字符串数据长度size接⼝,对于基本数据转换利⽤ strutil.h中提供的快速转换,如FastInt32ToBuffer、FloatToBuffer等;
Substitute:提供了⽀持⾄多9个参数的格式化字符串,参数为SubstituteArg类型或可转化为SubstituteArg类型的;此外格式化使⽤“$”后跟⼀个数字,该数字值为对应SubstituteArg参数,使⽤⽰例:
string str;
strings::SubstituteAndAppend(&str,"My name is $0 $1 and I am $2 years old.",first_name,last_name,age);事实上$后的数字可以⼀样;
SubstituteAndAppend:同Substitute,但是会对格式化后的字符串数据对应参数output进⾏追加;
zero_copy_stream.h:提供最⼩化数据流拷贝操作,主要提供ZeroCopyInputStream和ZeroCopyOutputStream抽象基类操作的接⼝,具体实现类位于
zero_copy_stream_impl.h和zero_copy_stream_impl_lite.h中;
此两个抽象基类操作接⼝如下(ZeroCopyOutputStream没有提供Skip接⼝):
Next:获取数据流中的⼀块数据,参数data为指向的数据指针,size为获取的数据长度,返回值为false表⽰没有可读的数据或出现异常;
BackUp:在调⽤Next后,可以回退内部数据指针索引位置,这样下⼀次调⽤Next时可以重新获取前⼀段数据;参数值不可⼤于早期Next得到的size长度;
Skip:跳过部分数据流,返回false则表⽰已到数据流末尾或发⽣异常,为防⽌异常,可结合ByteCount和早期的Next确定是否会达到数据流末尾;
ByteCount:获取当前数据流位置;
zero_copy_stream_impl_lite.h:对zero_copy_stream.h中的抽象基类的实现,主要提供基于array、string的输⼊和输出流实现;
ArrayInputStream:类array的输⼊流,继承于ZeroCopyInputStream;
数据成员:
data_:指向uint8类型的array的数据指针;
size_:array数据的有效长度;
block_size_:每次调⽤获取Next获取的数据最⼤长度;
position_:当前数据的索引位置;
last_returned_size_:最后⼀次调⽤Next返回的数据长度;
构造函数:参数data为指向数据array的指针(该数据array应在ArrayInputStream⽣命周期内是有效的),size为数据长度,block_size为Next获取的最⼤长度; ArrayOutputStream:类array的输出流,继承于ZeroCopyInputStream,操作⼤体同ArrayInputStream;
StringOutputStream:⽀持string类型的输出流,继承于ZeroCopyInputStream;
kMinimumSize:最⼩的new size⼤⼩,在处理Next操作中,调整⽬标string⼤⼩为kMinimumSize或2 * old_size或capacity()⼤⼩;
CopyingInputStream:拷贝输⼊流;仅提供Read、Skip操作接⼝;
Read:读取流中指定数据;参数buffer指向流数据针对,size数据流⼤⼩,返回值为读取到的字节数,返回0为读取到数据流尾,返回-1表⽰异常;
Skip:跳过指定长度的数据流;返回值为跳过的实际长度;
CopyingOutputStream:拷贝输出流,仅提供Write操作接⼝;
Write:从给定buffer中读取size字节的数据⾄输出流;返回值true表⽰成功,false表⽰写⼊异常;
CopyingInputStreamAdaptor:拷贝输⼊流适配器(事实上内部并⾮为zero-copy),继承于ZeroCopyInputStream,其以CopyingInputStream作为输⼊流对象;
数据成员:
copying_stream_:CopyingInputStream类型的流对象指针;
owns_copying_stream_:是否是copying_stream_对象的拥有者,便以在释放的时候释放copying_stream_对象资源;
failed_:是否出现操作异常;
position_:当前数据流位置索引;
buffer_:读取到的数据流对象缓冲区;
buffer_size_:读取到的数据流对象缓冲区⼤⼩;
buffer_used_:调⽤Next最近⼀次的⼤⼩;
backup_bytes_:调⽤BackUp的回退字节数;
成员函数:
构造函数,参数copying_stream为CopyingInputStream类型的流对象指针,block_size为数据缓冲区⼤⼩以初始化
buffer_size_(若为<0则为默认⼤⼩kDefaultBlockSize(8192));
SetOwnsCopyingStream:设置是否为copying_stream_对象的拥有者;
AllocateBufferIfNeeded:分配为buffer_size_⼤⼩的buffer_缓冲区;
FreeBuffer:释放buffer_缓冲区资源;
此外Next、BackUp、Skip、ByteCount为ZeroCopyInputStream的接⼝实现;
CopyingOutputStreamAdaptor:拷贝输出流适配器(事实上内部并⾮为zero-copy),继承于ZeroCopyInputStream,其以CopyingOutputStream作为输出流对象;
数据成员:
copying_stream_:CopyingOutputStream类型的流对象指针;
owns_copying_stream_:是否是copying_stream_对象的拥有者,便以在释放的时候释放copying_stream_对象资源;
failed_:是否出现操作异常;
position_:当前数据流位置索引;
buffer_:读取到的数据流对象缓冲区;
buffer_size_:读取到的数据流对象缓冲区⼤⼩;
buffer_used_:调⽤Next最近⼀次的⼤⼩;
成员函数:
构造函数,参数copying_stream为CopyingOutputStream类型的流对象指针,block_size为数据缓冲区⼤⼩以初始化
buffer_size_(若为<0则为默认⼤⼩kDefaultBlockSize(8192));
SetOwnsCopyingStream:设置是否为copying_stream_对象的拥有者;
Flush:调⽤底层刷新写⼊数据⾄输出流;
AllocateBufferIfNeeded:分配为buffer_size_⼤⼩的buffer_缓冲区;
FreeBuffer:释放buffer_缓冲区资源;
WriteBuffer:写⼊当前buffer_used_⼤⼩的数据⾄输出缓冲区数据;
zero_copy_stream_impl.h:对zero_copy_stream.h中的抽象基类的实现,主要实现File、stream、Concatenating、Limiting等⼏种流实现;
FileInputStream:⽂件描述的读操作类,继承于ZeroCopyInputStream;
先介绍CopyingFileInputStream:⽂件输⼊流类,继承于CopyingInputStream,实现读取⽂件数据;
数据成员:
file_:⽂件描述符或⽂件句柄;
close_on_delete_:关闭时,是否释放、删除⽂件描述对象;
is_closed_:是否已关闭;
errno_:IO操作错误码;
previous_seek_failed_:最近⼀次seek是否失败;
成员函数:
构造函数参数为⽂件描述符或⽂件句柄
Close:关闭⽂件描述符或⽂件句柄;
SetCloseOnDelete:设置关闭操作时,⾃动释放、⽂件描述对象;
GetErrno:获取IO操作错误码;
此外Read、Skip为CopyingInputStream类的实现接⼝,实现⽂件的读取和跳过指定字节数的操作;
数据成员:
copying_input_:CopyingFileInputStream类型的输⼊流对象;
impl_:CopyingInputStreamAdaptor类型的输⼊流对象适配器,作为流输⼊对象的实现类;
成员函数:
构造函数参数file_descriptor⽂件描述初始化copying_output_,其与block_size输⼊流块⼤⼩初始化impl_;
Close:关闭⽂件描述并刷新缓冲区,内部调⽤copying_input_的Close函数;
SetCloseOnDelete:设置关闭操作时,⾃动释放、⽂件描述对象,内部调⽤copying_input_的SetCloseOnDelete函数;
GetErrno:获取IO操作错误码,内部调⽤copying_input_的GetErrno函数;
此外Next、BackUp、Skip、ByteCount均内部调⽤impl_相应接⼝实现;
FileOutputStream:⽂件描述的写操作类,继承于ZeroCopyOutputStream,接⼝实现与FileInputStream相似;
增加了Flush接⼝以刷新输出流,⽆Skip接⼝;
IstreamInputStream:⽤以对C++输⼊流操作,继承于ZeroCopyInputStream;同FileInputStream,只是操作构造函数为istream的对象;
其内部读输⼊,使⽤istream的read、gcount、fail、eof接⼝操作;
OstreamOutputStream:⽤以对C++输出流操作,继承于ZeroCopyOutputStream,同FileOutputStream,只是操作构造函数为ostream的对象;
其内部写输出,使⽤ostream的write、good接⼝操作;
ConcatenatingInputStream:处理多个ZeroCopyInputStream输⼊流的stream对象操作,继承于ZeroCopyInputStream;
构造函数streams为ZeroCopyInputStream类型的指针数组,count为数组⼤⼩;
LimitingInputStream:处理受限字节的输⼊流,,继承于ZeroCopyInputStream;
构造函数input为ZeroCopyInputStream类型的指针对象,limit为字节上限;
ConcatenatingInputStream与LimitingInputStream仅为其他stream对象的包装⽽已;
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论