<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ??一站式輕松地調用各大LLM模型接口,支持GPT4、智譜、豆包、星火、月之暗面及文生圖、文生視頻 廣告
                [Protobuf2 語法指南](http://colobu.com/2015/01/07/Protobuf-language-guide/) [TOC] 英文:[Proto Buffers Language Guide](https://developers.google.com/protocol-buffers/docs/proto) 本指南描述了怎樣使用protocol buffer 語法來構造你的protocol buffer數據,包括.proto文件語法以及怎樣生成.proto文件的數據訪問類。 (本文只針對proto2的語法) 本文是一個參考指南——如果要查看如何使用本文中描述的多個特性的循序漸進的例子,請在[http://code.google.com/intl/zh-CN/apis/protocolbuffers/docs/tutorials.html](http://code.google.com/intl/zh-CN/apis/protocolbuffers/docs/tutorials.html)中查找需要的語言的教程。 ## 定義一個消息類型 先來看一個非常簡單的例子。假設你想定義一個“搜索請求”的消息格式,每一個請求含有一個查詢字符串、你感興趣的查詢結果所在的頁數,以及每一頁多少條查詢結果。可以采用如下的方式來定義消息類型的.proto文件了: ``` message SearchRequest { required string query = 1; optional int32 page_number = 2; optional int32 result_per_page = 3; } ``` SearchRequest消息格式有3個字段,在消息中承載的數據分別對應于每一個字段。其中每個字段都有一個名字和一種類型。 ### 指定字段類型 在上面的例子中,所有字段都是[標量類型](https://developers.google.com/protocol-buffers/docs/proto#scalar):兩個整型(page\_number和result\_per\_page),一個string類型(query)。當然,你也可以為字段指定其他的合成類型,包括[枚舉](https://developers.google.com/protocol-buffers/docs/proto#enum)(enumerations)或其他消息類型。 ### 分配標識號 正如上述文件格式,在消息定義中,每個字段都有唯一的一個**數字標識符**。這些標識符是用來在消息的[二進制格式](https://developers.google.com/protocol-buffers/docs/encoding)中識別各個字段的,一旦開始使用就不能夠再改變。注:\[1,15\]之內的標識號在編碼的時候會占用一個字節。\[16,2047\]之內的標識號則占用2個字節。所以應該為那些頻繁出現的消息元素保留 \[1,15\]之內的標識號。切記:要為將來有可能添加的、頻繁出現的標識號預留一些標識號。 最小的標識號可以從1開始,最大到2^29 - 1, or 536,870,911。不可以使用其中的\[19000-19999\]的標識號, Protobuf協議實現中對這些進行了預留。如果非要在.proto文件中使用這些預留標識號,編譯時就會報警。 ### 指定字段規則 所指定的消息字段修飾符必須是如下之一: * required:一個格式良好的消息一定要含有1個這種字段。表示該值是必須要設置的; * optional:消息格式中該字段可以有0個或1個值(不超過1個)。 * repeated:在一個格式良好的消息中,這種字段可以重復任意多次(包括0次)。重復的值的順序會被保留。表示該值可以重復,相當于java中的List。 由于一些歷史原因,基本數值類型的repeated的字段并沒有被盡可能地高效編碼。在新的代碼中,用戶應該使用特殊選項\[packed=true\]來保證更高效的編碼。如: ``` repeated int32 samples = 4 [packed=true]; ``` > required是永久性的:在將一個字段標識為required的時候,應該特別小心。如果在某些情況下不想寫入或者發送一個required的字段,將原始該字段修飾符更改為optional可能會遇到問題——舊版本的使用者會認為不含該字段的消息是不完整的,從而可能會無目的的拒絕解析。在這種情況下,你應該考慮編寫特別針對于應用程序的、自定義的消息校驗函數。Google的一些工程師得出了一個結論:使用required弊多于利;他們更 愿意使用optional和repeated而不是required。當然,這個觀點并不具有普遍性。 ### 添加更多消息類型 在一個.proto文件中可以定義多個消息類型。在定義多個相關的消息的時候,這一點特別有用——例如,如果想定義與SearchResponse消息類型對應的回復消息格式的話,你可以將它添加到相同的.proto文件中,如: ``` message SearchRequest{ required string query = 1; optional int32 page_number = 2; optional int32 result_per_page = 3; } message SearchResponse{ ... } ``` ### 添加注釋 向.proto文件添加注釋,可以使用C/C++/java風格的雙斜杠(//) 語法格式,如: ``` message SearchRequest{ required string query = 1; optional int32 page_number = 2;// Which page number do we want? optional int32 result_per_page = 3;// Number of results to return per page. } ``` ### 從.proto文件生成了什么? 當用protocolbuffer編譯器來運行.proto文件時,編譯器將生成所選擇語言的代碼,這些代碼可以操作在.proto文件中定義的消息類型,包括獲取、設置字段值,將消息序列化到一個輸出流中,以及從一個輸入流中解析消息。 * 對C++來說,編譯器會為每個.proto文件生成一個.h文件和一個.cc文件,.proto文件中的每一個消息有一個對應的類。 * 對Java來說,編譯器為每一個消息類型生成了一個.java文件,以及一個特殊的Builder類(該類是用來創建消息類接口的)。 * 對Python來說,有點不太一樣——Python編譯器為.proto文件中的每個消息類型生成一個含有靜態描述符的模塊,,該模塊與一個元類(metaclass)在運行時(runtime)被用來創建所需的Python數據訪問類。 你可以從如下的文檔鏈接中獲取每種語言更多API。[http://code.google.com/intl/zh-CN/apis/protocolbuffers/docs/reference/overview.html](http://code.google.com/intl/zh-CN/apis/protocolbuffers/docs/reference/overview.html) ### 標量數值類型 一個標量消息字段可以含有一個如下的類型——該表格展示了定義于.proto文件中的類型,以及與之對應的、在自動生成的訪問類中定義的類型: | .proto類型 | Java 類型| C++類型 | 備注 | | --- | --- |--- |--- | | double | double | double | | | float | float | float | int32 | int | int32 | | uint32 | int[1] | uint32 | Uses variable-length encoding. | | uint64 | long[1] | uint64 | Uses variable-length encoding.| | sint32| int | int32 | 使用可變長編碼方式。有符號的整型值。編碼時比通常的int32高效。 | sint64 | long | int64 | 使用可變長編碼方式。有符號的整型值。編碼時比通常的int64高效。 | fixed32 | int[1] | uint32 |總是4個字節。如果數值總是比總是比228大的話,這個類型會比uint32高效。 | fixed64 | long[1] | uint64 | 總是8個字節。如果數值總是比總是比256大的話,這個類型會比uint64高效。 | sfixed32 | int | int32 | 總是4個字節。 | sfixed64 | long| int64| 總是8個字節。 | bool| boolean| bool |string| String | string | 一個字符串必須是UTF-8編碼或者7-bit ASCII編碼的文本。 | bytes| ByteString| string | 可能包含任意順序的字節數據。| 你可以在文章[http://code.google.com/apis/protocolbuffers/docs/encoding.html](http://code.google.com/apis/protocolbuffers/docs/encoding.html)中,找到更多“序列化消息時各種類型如何編碼”的信息。 ### Optional的字段和默認值 如上所述,消息描述中的一個元素可以被標記為“可選的”(optional)。一個格式良好的消息可以包含0個或一個optional的元素。當解 析消息時,如果它不包含optional的元素值,那么解析出來的對象中的對應字段就被置為默認值。默認值可以在消息描述文件中指定。例如,要為*SearchRequest*消息的*result\_per\_page*字段指定默認值10,在定義消息格式時如下所示: ``` optional int32 result_per_page = 3 [default = 10]; ``` 如果沒有為optional的元素指定默認值,就會使用與特定類型相關的默認值:對string來說,默認值是空字符串。對bool來說,默認值是false。對數值類型來說,默認值是0。對枚舉來說,默認值是枚舉類型定義中的第一個值。 ### 枚舉 當需要定義一個消息類型的時候,可能想為一個字段指定某“預定義值序列”中的一個值。例如,假設要為每一個SearchRequest消息添加一個 corpus字段,而corpus的值可能是UNIVERSAL,WEB,IMAGES,LOCAL,NEWS,PRODUCTS或VIDEO中的一個。 其實可以很容易地實現這一點:通過向消息定義中添加一個枚舉(enum)就可以了。一個enum類型的字段只能用指定的常量集中的一個值作為其值(如果嘗 試指定不同的值,解析器就會把它當作一個未知的字段來對待)。在下面的例子中,在消息格式中添加了一個叫做Corpus的枚舉類型——它含有所有可能的值 ——以及一個類型為Corpus的字段: ``` message SearchRequest { required string query = 1; optional int32 page_number = 2; optional int32 result_per_page = 3 [default = 10]; enum Corpus { UNIVERSAL = 0; WEB = 1; IMAGES = 2; LOCAL = 3; NEWS = 4; PRODUCTS = 5; VIDEO = 6; } optional Corpus corpus = 4 [default = UNIVERSAL]; } ``` 你可以為枚舉常量定義別名。 需要設置allow\_alias option 為 true, 否則 protocol編譯器會產生錯誤信息。 ``` enum EnumAllowingAlias { option allow_alias = true; UNKNOWN = 0; STARTED = 1; RUNNING = 1; } enum EnumNotAllowingAlias { UNKNOWN = 0; STARTED = 1; // RUNNING = 1; // Uncommenting this line will cause a compile error inside Google and a warning message outside. } ``` 枚舉常量必須在32位整型值的范圍內。因為enum值是使用可變編碼方式的,對負數不夠高效,因此不推薦在enum中使用負數。如上例所示,可以在 一個消息定義的內部或外部定義枚舉——這些枚舉可以在.proto文件中的任何消息定義里重用。當然也可以在一個消息中聲明一個枚舉類型,而在另一個不同 的消息中使用它——采用MessageType.EnumType的語法格式。 當對一個使用了枚舉的.proto文件運行protocol buffer編譯器的時候,生成的代碼中將有一個對應的enum(對Java或C++來說),或者一個特殊的EnumDescriptor類(對 Python來說),它被用來在運行時生成的類中創建一系列的整型值符號常量(symbolic constants)。 關于如何在你的應用程序的消息中使用枚舉的更多信息,請查看所選擇的語言[http://code.google.com/intl/zh-CN/apis/protocolbuffers/docs/reference/overview.html。](http://code.google.com/intl/zh-CN/apis/protocolbuffers/docs/reference/overview.html%E3%80%82) ## 使用其他消息類型 你可以將其他消息類型用作字段類型。例如,假設在每一個SearchResponse消息中包含Result消息,此時可以在相同的.proto文件中定義一個Result消息類型,然后在SearchResponse消息中指定一個Result類型的字段,如: ``` message SearchResponse{ repeated Result result = 1; } message Result{ required string url = 1; optional string title = 2; repeated string snippets = 3; } ``` ### 導入定義 在上面的例子中,Result消息類型與SearchResponse是定義在同一文件中的。如果想要使用的消息類型已經在其他.proto文件中已經定義過了呢? 你可以通過導入(importing)其他.proto文件中的定義來使用它們。要導入其他.proto文件的定義,你需要在你的文件中添加一個導入聲明,如: ``` import "myproject/other_protos.proto"; ``` 默認情況下你只能使用直接導入的.proto文件中的定義. 然而, 有時候你需要移動一個.proto文件到一個新的位置, 可以不直接移動.proto文件, 只需放入一個dummy .proto 文件在老的位置, 然后使用import轉向新的位置: ``` // new.proto // All definitions are moved here // old.proto // This is the proto that all clients are importing.import public "new.proto";import "other.proto"; ``` // client.proto ``` import "old.proto"; // You use definitions from old.proto and new.proto, but not other.proto ``` protocol編譯器就會在一系列目錄中查找需要被導入的文件,這些目錄通過protocol編譯器的命令行參數-I/–import\_path指定。如果不提供參數,編譯器就在其調用目錄下查找。 ### 嵌套類型 你可以在其他消息類型中定義、使用消息類型,在下面的例子中,Result消息就定義在SearchResponse消息內,如: ``` message SearchResponse{ message Result{ string url = 1; string title = 2; repeated string snippets = 3; } repeated Result results = 1; } ``` 如果你想在它的父消息類型的外部重用這個消息類型,你需要以Parent.Type的形式使用它,如: ``` message SomeOtherMessage { SearchResponse.Result result = 1; } ``` 當然,你也可以將消息嵌套任意多層,如: ``` message Outer{ // Level 0 message MiddleAA{ // Level 1 message Inner{ // Level 2 int64 ival = 1; bool booly = 2; } } message MiddleBB{ // Level 1 message Inner{ // Level 2 int32 ival = 1; bool booly = 2; } } } ``` ## 更新一個消息類型 如果一個已有的消息格式已無法滿足新的需求——如,要在消息中添加一個額外的字段——但是同時舊版本寫的代碼仍然可用。不用擔心!更新消息而不破壞已有代碼是非常簡單的。在更新時只要記住以下的規則即可。 * 不要更改任何已有的字段的數值標識。 * 如果你增加新的字段,使用舊格式的字段仍然可以被你新產生的代碼所解析。你應該記住這些元素的默認值這樣你的新代碼就可以以適當的方式和舊代碼產生的數據交互。相似的,通過新代碼產生的消息也可以被舊代碼解析:只不過新的字段會被忽視掉。注意,未被識別的字段會在反序列化的過程中丟棄掉,所以如果消息再被傳遞給新的代碼,新的字段依然是不可用的(這和proto2中的行為是不同的,在proto2中未定義的域依然會隨著消息被序列化) * 非required的字段可以移除——只要它們的標識號在新的消息類型中不再使用(更好的做法可能是重命名那個字段,例如在字段前添加“OBSOLETE\_”前綴,那樣的話,使用的.proto文件的用戶將來就不會無意中重新使用了那些不該使用的標識號)。 * int32, uint32, int64, uint64,和bool是全部兼容的,這意味著可以將這些類型中的一個轉換為另外一個,而不會破壞向前、 向后的兼容性。如果解析出來的數字與對應的類型不相符,那么結果就像在C++中對它進行了強制類型轉換一樣(例如,如果把一個64位數字當作int32來 讀取,那么它就會被截斷為32位的數字)。 * sint32和sint64是互相兼容的,但是它們與其他整數類型不兼容。 * string和bytes是兼容的——只要bytes是有效的UTF-8編碼。 * 嵌套消息與bytes是兼容的——只要bytes包含該消息的一個編碼過的版本。 * fixed32與sfixed32是兼容的,fixed64與sfixed64是兼容的。 * 枚舉類型與int32,uint32,int64和uint64相兼容(注意如果值不相兼容則會被截斷),然而在客戶端反序列化之后他們可能會有不同的處理方式,例如,未識別的proto3枚舉類型會被保留在消息中,但是他的表示方式會依照語言而定。int類型的字段總會保留他們的 ## Any Any類型消息允許你在沒有指定他們的.proto定義的情況下使用消息作為一個嵌套類型。一個Any類型包括一個可以被序列化bytes類型的任意消息,以及一個URL作為一個全局標識符和解析消息類型。為了使用Any類型,你需要導入`import google/protobuf/any.proto`。 ``` import "google/protobuf/any.proto"; message ErrorStatus{ string message = 1; repeated google.protobuf.Any details = 2; } ``` 對于給定的消息類型的默認類型URL是type.googleapis.com/packagename.messagename。 不同語言的實現會支持動態庫以線程安全的方式去幫助封裝或者解封裝Any值。例如在java中,Any類型會有特殊的`pack()`和`unpack()`訪問器,在C++中會有`PackFrom()`和`UnpackTo()`方法。 ``` // Storing an arbitrary message type in Any. NetworkErrorDetails details = ...; ErrorStatus status; status.add_details()->PackFrom(details); // Reading an arbitrary message from Any. ErrorStatus status = ...; for (const Any& detail : status.details()) { if (detail.Is<NetworkErrorDetails>()) { NetworkErrorDetails network_error; detail.UnpackTo(&network_error); ... processing network_error ... } } ``` 目前,用于Any類型的動態庫仍在開發之中 如果你已經很熟悉[proto2語法](https://developers.google.com/protocol-buffers/docs/proto),使用Any替換[擴展](https://developers.google.com/protocol-buffers/docs/proto#extensions)。 1. ## Oneof 如果你的消息中有很多可選字段, 并且同時至多一個字段會被設置, 你可以加強這個行為,使用oneof特性節省內存. Oneof字段就像可選字段, 除了它們會共享內存, 至多一個字段會被設置。 設置其中一個字段會清除其它字段。 你可以使用`case()`或者`WhichOneof()`方法檢查哪個oneof字段被設置, 看你使用什么語言了. ### 使用Oneof 為了在.proto定義Oneof字段, 你需要在名字前面加上oneof關鍵字, 比如下面例子的test\_oneof: ``` message SampleMessage{ oneof test_oneof{ string name = 4; SubMessage sub_message = 9; } } ``` 然后你可以增加oneof字段到 oneof 定義中. 你可以增加任意類型的字段, 但是不能使用repeated 關鍵字. 在產生的代碼中, oneof字段擁有同樣的 getters 和setters, 就像正常的可選字段一樣. 也有一個特殊的方法來檢查到底那個字段被設置. 你可以在相應的語言[API指南](https://developers.google.com/protocol-buffers/docs/reference/overview)中找到oneof API介紹. 2. ### Oneof 特性 * 設置oneof會自動清楚其它oneof字段的值. 所以設置多次后,只有最后一次設置的字段有值. ``` SampleMessage message; message.set_name("name"); CHECK(message.has_name()); message.mutable_sub_message(); // Will clear name field. CHECK(!message.has_name()); ``` * 如果解析器遇到同一個oneof中有多個成員,只有最會一個會被解析成消息。 * oneof不支持repeated. * 反射API對oneof 字段有效. * 如果使用C++,需確保代碼不會導致內存泄漏. 下面的代碼會崩潰, 因為sub\_message 已經通過set\_name()刪除了 ``` SampleMessage message;SubMessage* sub_message = message.mutable_sub_message(); message.set_name("name"); // Will delete sub_message sub_message->set_... // Crashes here ``` * 在C++中,如果你使用Swap()兩個oneof消息,每個消息,兩個消息將擁有對方的值,例如在下面的例子中,msg1會擁有sub\_message并且msg2會有name。 ``` SampleMessage msg1; msg1.set_name("name"); SampleMessage msg2; msg2.mutable_sub_message(); msg1.swap(&msg2); CHECK(msg1.has_sub_message()); CHECK(msg2.has_name()); ``` 3. ### 向后兼容性問題 當增加或者刪除oneof字段時一定要小心. 如果檢查oneof的值返回None/NOT\_SET, 它意味著oneof字段沒有被賦值或者在一個不同的版本中賦值了。 你不會知道是哪種情況,因為沒有辦法判斷如果未識別的字段是一個oneof字段。 Tag 重用問題: * **將字段移入或移除oneof**:在消息被序列號或者解析后,你也許會失去一些信息(有些字段也許會被清除) * **刪除一個字段或者加入一個字段**:在消息被序列號或者解析后,這也許會清除你現在設置的oneof字段 * **分離或者融合oneof**:行為與移動常規字段相似。 ## Map 如果你希望創建一個關聯映射,protocol buffer提供了一種快捷的語法: ``` map<string, Project> projects = 3; ``` * Map的字段可以是repeated。 * 序列化后的順序和map迭代器的順序是不確定的,所以你不要期望以固定順序處理Map * 當為.proto文件產生生成文本格式的時候,map會按照key 的順序排序,數值化的key會按照數值排序。 * 從序列化中解析或者融合時,如果有重復的key則后一個key不會被使用,當從文本格式中解析map時,如果存在重復的key。 生成map的API現在對于所有proto3支持的語言都可用了,你可以從[API指南](https://developers.google.com/protocol-buffers/docs/reference/overview)找到更多信息。 1. ### 向后兼容性問題 map語法序列化后等同于如下內容,因此即使是不支持map語法的protocol buffer實現也是可以處理你的數據的: ``` message MapFieldEntry { key_type key = 1; value_type value = 2; } repeated MapFieldEntry map_field = N; ``` ## Package 當然可以為.proto文件新增一個可選的package聲明符,用來防止不同的消息類型有命名沖突。如: ``` package foo.bar; message Open { ... } ``` 在其他的消息格式定義中可以使用包名+消息名的方式來定義域的類型,如: ``` message Foo { ... required foo.bar.Open open = 1; ... } ``` 包的聲明符會根據使用語言的不同影響生成的代碼。 * 對于C++,產生的類會被包裝在C++的命名空間中,如上例中的Open會被封裝在 foo::bar空間中; - 對于Java,包聲明符會變為java的一個包,除非在.proto文件中提供了一個明確有java\_package; * 對于 Python,這個包聲明符是被忽略的,因為Python模塊是按照其在文件系統中的位置進行組織的。 * 對于Go,包可以被用做Go包名稱,除非你顯式的提供一個option go\_package在你的.proto文件中。 * 對于Ruby,生成的類可以被包裝在內置的Ruby名稱空間中,轉換成Ruby所需的大小寫樣式 (首字母大寫;如果第一個符號不是一個字母,則使用PB\_前綴),例如Open會在Foo::Bar名稱空間中。 * 對于javaNano包會使用Java包,除非你在你的文件中顯式的提供一個option java\_package。 * 對于C#包可以轉換為PascalCase后作為名稱空間,除非你在你的文件中顯式的提供一個option csharp\_namespace,例如,Open會在Foo.Bar名稱空間中 ### 包及名稱的解析 Protocol buffer語言中類型名稱的解析與C++是一致的:首先從最內部開始查找,依次向外進行,每個包會被看作是其父類包的內部類。當然對于 (foo.bar.Baz)這樣以“.”分隔的意味著是從最外圍開始的。 ProtocolBuffer編譯器會解析.proto文件中定義的所有類型名。 對于不同語言的代碼生成器會知道如何來指向每個具體的類型,即使它們使用了不同的規則。 ## 定義服務(Service) 如果想要將消息類型用在RPC(遠程方法調用)系統中,可以在.proto文件中定義一個RPC服務接口,protocol buffer編譯器將會根據所選擇的不同語言生成服務接口代碼及存根。如,想要定義一個RPC服務并具有一個方法,該方法能夠接收 SearchRequest并返回一個SearchResponse,此時可以在.proto文件中進行如下定義: ``` service SearchService { rpc Search (SearchRequest) returns (SearchResponse); } ``` 最直觀的使用protocol buffer的RPC系統是[gRPC](https://github.com/grpc/grpc-experiments),一個由谷歌開發的語言和平臺中的開源的PRC系統,gRPC在使用protocl buffer時非常有效,如果使用特殊的protocol buffer插件可以直接為您從.proto文件中產生相關的RPC代碼。 如果你不想使用gRPC,也可以使用protocol buffer用于自己的RPC實現,你可以從[proto2語言指南](https://developers.google.com/protocol-buffers/docs/proto#services)中找到更多信息 還有一些第三方開發的PRC實現使用Protocol Buffer。參考[第三方插件wiki](https://github.com/google/protobuf/blob/master/docs/third_party.md)查看這些實現的列表。 ## JSON 映射 Proto3 支持JSON的編碼規范,使他更容易在不同系統之間共享數據,在下表中逐個描述類型。 如果JSON編碼的數據丟失或者其本身就是null,這個數據會在解析成protocol buffer的時候被表示成默認值。如果一個字段在protocol buffer中表示為默認值,體會在轉化成JSON的時候編碼的時候忽略掉以節省空間。具體實現可以提供在JSON編碼中可選的默認值。 | proto3 | JSON | JSON示例 | 注意 | | --- | --- | --- | --- | | messag| eobject | {“fBar”: v, “g”: null, …} | 產生JSON對象,消息字段名可以被映射成lowerCamelCase形式,并且成為JSON對象鍵,null被接受并成為對應字段的默認值
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看