Proto3 語言指南

由于工程項(xiàng)目中擬采用一種簡便高效的數(shù)據(jù)交換格式,百度了一下發(fā)現(xiàn)除了采用 xml仓技、JSON 還有 ProtoBuf(Google 出品)砖瞧,趕緊去瞄了一下∥花了一個(gè)周末的時(shí)間把它走馬觀花的學(xué)習(xí)了一下,順便將官方的指南翻譯了出來登澜。

首先申明阔挠,哥們兒英語高中水平,借助了必應(yīng)詞典勉強(qiáng)將其意譯了出來脑蠕,如果你發(fā)現(xiàn)翻譯中有紕漏购撼,請一定不要告訴我~

怕有誤人子弟之嫌,先貼上官方文檔的地址谴仙,本譯文僅供參考:
https://developers.google.com/protocol-buffers/docs/proto3

Proto3 語言指南

  • 定義消息類型
  • 標(biāo)準(zhǔn)類型
  • 默認(rèn)值
  • 枚舉
  • 使用其它消息類型
  • 嵌套
  • 更新消息類型
  • Any
  • Oneof
  • Map 類型
  • 定義服務(wù)
  • JSON 映射
  • 選項(xiàng)
  • 創(chuàng)建您的類

本指南描述如何使用 ProtoBuf 語言規(guī)范來組織你的.proto 文件迂求,以及如何編譯.proto 文件來生成相應(yīng)的操作類。它涵蓋了proto3 語法晃跺,如果你想查看老版本 proto2 的相關(guān)信息揩局,請參考《Proto2 語言指南》

這是一個(gè)參考指南---通過一個(gè)例子一步一步地介紹本文檔描述的 proto3 語言特性,請根據(jù)你選擇的編程語言參考基礎(chǔ)教程掀虎。

定義消息類型

讓我們先來看一個(gè)簡單的例子凌盯。假設(shè)你想定義一個(gè)搜索請求的消息格式,它包含一個(gè)查詢字符串烹玉、一個(gè)你感興趣的特定頁號驰怎、以及每頁結(jié)果數(shù)。下面就是這個(gè).proto 文件所定義的消息類型二打。

syntax = "proto3";


message SearchRequest {
  string query = 1;
  int32 page_number = 2;
  int32 result_per_page = 3;
}
  • 文件的第一行指定你正在使用 proto3 語法:如果你不這么做 protocol buffer 編譯器會假設(shè)您使用的是 proto2 县忌。 這一行不允許存在空白字符或注釋。
  • 這個(gè) SearchRequest 消息定義了三個(gè)字段(名稱/值對)继效,每一條 SearchRequest 消息類型的數(shù)據(jù)都包含這三個(gè)字段定義的數(shù)據(jù)症杏。每個(gè)字段包含一個(gè)名稱和類型。
指定字段類型

在上面的例子中莲趣,所有的字段都是 標(biāo)準(zhǔn) 類型:二個(gè)整形(page_number 和 resulet_per_page)和一個(gè)字符串類型(query)鸳慈。然而,你也可以用復(fù)雜類型來定義字段喧伞,包括 枚舉 和其它消息類型走芋。

指定標(biāo)簽

通過上面的例子你可以看到绩郎,這里每個(gè)字段都定義了一個(gè)唯一的數(shù)值標(biāo)簽。 這些唯一的數(shù)值標(biāo)簽用來標(biāo)識 二進(jìn)制消息 中你所定義的字段翁逞,一旦定義了編譯后就無法修改肋杖。需要特別提醒的是標(biāo)簽 1–15 標(biāo)識的字段編碼僅占用 1 個(gè)字節(jié)(包括字段類型和標(biāo)識標(biāo)簽),更多詳細(xì)的信息請參考 ProtoBuf 編碼 挖函。 數(shù)值標(biāo)簽 16–2047 標(biāo)識的字段編碼占用 2 個(gè)字節(jié)状植。因此,你應(yīng)該將標(biāo)簽 1–15 留給那些在你的消息類型中使用頻率高的字段怨喘。記得預(yù)留一些空間(標(biāo)簽 1–15)給將來可能添加的高頻率字段津畸。

最小的數(shù)值標(biāo)簽是 1, 最大值是 2 29 - 1, 即 536,870,911。 你不能使用的標(biāo)簽范圍還有:19000–19999
( FieldDescriptor::kFirstReservedNumber – FieldDescriptor::kLastReservedNumber )必怜,這些是 ProtoBuf 系統(tǒng)預(yù)留的肉拓,如果你在你的.proto 文件中使用了其中的數(shù)值標(biāo)簽,protoc 編譯器會報(bào)錯(cuò)梳庆。同樣地暖途,你不能使用保留字段中 reserved 關(guān)鍵字定義的標(biāo)簽。

定義字段的規(guī)則

消息的字段可以是一下情況之一:

  • 單數(shù)(默認(rèn)):該字段可以出現(xiàn) 0 或 1 次(不能大于 1 次)膏执。
  • 可重復(fù)(repeated):該字段可以出現(xiàn)任意次(包含 0)驻售。 可重復(fù)字段數(shù)值的順序是系統(tǒng)預(yù)定義的。
    由于一些歷史原因更米,默認(rèn)情況下欺栗,數(shù)值類型的可重復(fù)(repeated)字段的編碼性能沒有想象中的好,你應(yīng)該在其后用特殊選項(xiàng) [packed=true] 來申
    明以獲得更高效的編碼壳快。 例如:
    repeated int32 samples = 4 [packed=true];

你能夠在 ProtoBuf 編碼 中查閱更多的關(guān)于 packed 關(guān)鍵字的信息

添加更多的消息類型

同一個(gè).proto 文件中可以定義多個(gè)消息類型纸巷。這在定義多個(gè)相關(guān)的消息時(shí)非常有用。例如眶痰,如果你想針對用于搜索查詢的 SearchRequest 消息定義
一個(gè)保存查找結(jié)果的 SearchResponse 消息,你可以把它們放在同一個(gè).proto 文件中:

message SearchRequest {
  string query = 1;
  int32 page_number = 2;
  int32 result_per_page = 3;
}

message SearchResponse {
  ...
}

添加注釋

在.proto 文件中,使用 C/C++格式的注釋語法 // syntax

message SearchRequest {
  string query = 1;
  int32 page_number = 2; // Which page number do we want?
  int32 result_per_page = 3; // Number of results to return per page.
}

保留字段

如果你通過直接刪除或注釋一個(gè)字段的方式 更新 了一個(gè)消息結(jié)構(gòu)梯啤,將來別人在更新這個(gè)消息的時(shí)候可能會重復(fù)使用標(biāo)簽竖伯。如果他們以后加載舊版
本的相同的.proto 文件,可能會導(dǎo)致嚴(yán)重的問題因宇。包括數(shù)據(jù)沖突七婴、 隱秘的 bug 等等。為了保證這種情況不會發(fā)生察滑,當(dāng)你想刪除一個(gè)字段的時(shí)候打厘,
可以使用 reserved 關(guān)鍵字來申明該字段的標(biāo)簽(和/或名字,這在 JSON 序列化的時(shí)候也會產(chǎn)生問題)贺辰。 將來如果有人使用了你使用 reserved
關(guān)鍵字定義的標(biāo)簽或名字户盯,編譯器就好報(bào)錯(cuò)嵌施。

message Foo {
  reserved 2, 15, 9 to 11;
  reserved "foo", "bar";
}

注意:你不能同時(shí)在一條 reserved 語句中申明標(biāo)簽和名字。

.proto 文件編譯生成了什么莽鸭?

當(dāng)你使用 protoc 編譯器 編譯一個(gè).proto 文件的時(shí)候吗伤,編譯器會根據(jù)你選擇的語言和你在這個(gè).proto 文件定義的消息類型生成代碼,硫眨,這些代碼的
功能包括:字段值的 getter足淆,setter,消息序列化并寫入到輸出流礁阁,從輸入流接反序列化讀取消息等巧号。
對于 C++語言,編譯器會根據(jù)定義的.proto 文件編譯生成一個(gè).h 頭文件和一個(gè).cc 源碼實(shí)現(xiàn)文件姥闭。
4

  • 對于 Java 語言裂逐,編譯器會為每一個(gè)消息類型創(chuàng)建一個(gè)帶類的.java 文件,同時(shí)這個(gè) java 文件中包含用來創(chuàng)建該消息類型的實(shí)例的特殊 Builder 構(gòu)造
    類泣栈。
  • Python 語言有點(diǎn)不一樣---編譯器在你定義的.proto 文件中創(chuàng)建一個(gè)包含靜態(tài)描述符的模塊卜高,每個(gè)消息類型對應(yīng)一個(gè)靜態(tài)描述符,在 Python 程序解
    釋運(yùn)行的時(shí)候南片,會根據(jù)靜態(tài)描述符用一個(gè)元類去創(chuàng)建相應(yīng)的數(shù)據(jù)訪問類掺涛。
  • 對于 Go 語言,針對每一個(gè)定義的消息類型編譯器會創(chuàng)建一個(gè)帶類型的.pb.go 文件疼进。
  • 對于 Ruby 語言薪缆,編譯器會創(chuàng)建一個(gè)帶 Ruby 模塊的.rb 文件,其中包含了所有你定義的消息類型伞广。
  • 對于 JavaNano拣帽,編譯器會創(chuàng)建 Java 語言類似的輸出文件,但是沒有 Builder 構(gòu)造類嚼锄。
  • 對于 Ojective-C减拭,編譯器會創(chuàng)建一個(gè) pbobjc.h 和一個(gè) pbobjc.m 文件,為每一個(gè)消息類型都創(chuàng)建一個(gè)類來操作区丑。
  • 對于 C#語言拧粪,編譯器會為每一個(gè).proto 文件創(chuàng)建一個(gè).cs 文件,為每一個(gè)消息類型都創(chuàng)建一個(gè)類來操作沧侥。
    針對所選擇的不同的編程語言可霎,你能夠在后續(xù)的教程中找到更多的關(guān)于操作它們的編程接口(proto3 版本的即將推出)。 更詳細(xì)
    的針對特定編程語言的 API 操作細(xì)節(jié)宴杀,請參考 API 參考 癣朗。
標(biāo)準(zhǔn)類型

.proto文件中消息結(jié)構(gòu)里用于定義字段的標(biāo)準(zhǔn)數(shù)據(jù)類型如下表所示,后面幾列是.proto文件中定義的標(biāo)準(zhǔn)類型編譯轉(zhuǎn)換后在編程語言中的類型對照旺罢。

如果你想了解這些數(shù)據(jù)類型在序列化的時(shí)候如何編碼旷余,請參考 ProtoBuf 編碼 绢记。
[1] 在 Java 中,無符號的 32 位整形和 64 位整形都是用的相應(yīng)的有符號整數(shù)表示荣暮,最高位儲存的是符號標(biāo)志庭惜。
[2] 在任何情況下,給字段賦值都會執(zhí)行類型檢查穗酥,以確保所賦的值是有效的护赊。
5
[3] 默認(rèn)情況下 64 位整數(shù)或 32 位無符號整數(shù)通常在編碼的時(shí)候都是用 long 類型表示,但是你可以在設(shè)定字段的時(shí)候指定位 int 類型砾跃。 在任何情況
下骏啰,這個(gè)值必須匹配所設(shè)定的數(shù)據(jù)類型。參考[2]
[4] Python 中字符串通常編碼為 Unicode,但是如果給定的字符串是 ASCII 類型,可以設(shè)置位 str 類型(可能會有變化)

默認(rèn)值

當(dāng)一個(gè)消息被解析的時(shí)候,如果在編碼后的消息結(jié)構(gòu)中某字段沒有初始值,相應(yīng)的字段在被解析的對象中會被設(shè)置默認(rèn)值抽高。這些默認(rèn)值都是類型相關(guān)的判耕。

  • 字符串默認(rèn)值為空字符串。
  • 字節(jié)類型默認(rèn)值是空字節(jié)翘骂。
  • 布爾類型默認(rèn)值為 false壁熄。
  • 數(shù)值類型默認(rèn)值位 0。
  • 枚舉 類型默認(rèn)值是第一個(gè)枚舉元素碳竟,它必須為 0草丧。
  • 消息類型字段默認(rèn)值為 null。

可重復(fù)類型字段的默認(rèn)值為(相應(yīng)編程語言中的)空列表莹桅。
需要提醒的是:對于標(biāo)準(zhǔn)數(shù)據(jù)類型的字段昌执,當(dāng)消息被解析的時(shí)候你是沒有辦法顯示地設(shè)定默認(rèn)值的(例如布爾類型是否默認(rèn)設(shè)置為 false),記住當(dāng)你定義自己的消息類型的時(shí)候不要設(shè)置它的默認(rèn)值诈泼。例如懂拾,不要在你的消息類型中定義一個(gè)表示開關(guān)變量的布爾類型字段,如果你不希望它默認(rèn)初始化為 false 的話铐达。 還要注意的是岖赋,在序列化的時(shí)候,如果標(biāo)準(zhǔn)類型的字段的值等于它的默認(rèn)值娶桦,這個(gè)值是不會存儲到介質(zhì)上的贾节。

枚舉

當(dāng)你定義一個(gè)消息的時(shí)候,你可能會希望某個(gè)字段在預(yù)定的取值列表里面取值衷畦。 例如,假設(shè)你想為 SearchRequest 消息定義一個(gè) corpus字段知牌,它的取值可能是 UNIVERSAL祈争,WEB,IMAGES角寸,LOCAL菩混,NEWS忿墅,PRODUCTS 或者 VIDEO。你只需要簡單的利用 enum 關(guān)鍵字定義一個(gè)枚舉類型沮峡,它的每一個(gè)可能的取值都是常量疚脐。

在下面的例子中,我們定義了一個(gè)名為 Corpus 的枚舉類型邢疙,并用它定義了一個(gè)字corpus棍弄。

message SearchRequest {
  string query = 1;
  int32 page_number = 2;
  int32 result_per_page = 3;

  enum Corpus {
      UNIVERSAL = 0;
      WEB = 1;
      IMAGES = 2;
      LOCAL = 3;
      NEWS = 4;
      PRODUCTS = 5;
      VIDEO = 6;
  }

  Corpus corpus = 4;
}

你會發(fā)現(xiàn),這個(gè) Corpus 枚舉類型的第一個(gè)常量被設(shè)置為 0疟游,每個(gè)枚舉類型的定義中呼畸,它的第一個(gè)元素都應(yīng)該是一個(gè)等于 0 的常量。 這是因?yàn)椋?/p>

  • 只有把它的第一個(gè)元素設(shè)置為 0颁虐,我們才能為枚舉類型定義數(shù)值類型的 默認(rèn)值 蛮原。
  • 這個(gè)為 0 的元素必須是第一個(gè)元素,為了兼容 proto2 語法(proto2 中枚舉類型的第一個(gè)元素總是默認(rèn)值)另绩。

你可以通過給不同的枚舉常量賦同樣的值的方式來定義別名儒陨。 為了定義別名,你需要設(shè)置 allow_alias=true笋籽,否則編譯器會報(bào)錯(cuò)蹦漠。


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 位整數(shù)值的范圍。 由于枚舉的值采用 varient 編碼 方式干签,負(fù)數(shù)編碼效率低所以不推薦津辩。枚舉類型可以定義在消息結(jié)構(gòu)體內(nèi)部(上例所示),也可以定義在外部容劳。如果定義在了外部喘沿,同一個(gè).proto 文件中的所有消息都能重用這個(gè)枚舉類型。當(dāng)然竭贩,你也可以用
MessageType.EnumType 語法格式在一個(gè)消息結(jié)構(gòu)內(nèi)部引用其它消息結(jié)構(gòu)體內(nèi)部定義的枚舉類型來定義字段蚜印。

當(dāng)你用 protoc 編譯器編譯一個(gè)包含枚舉類型的.proto 文件時(shí),對于 Java 或 C++編譯生成的代碼中會包含相應(yīng)的枚舉類型留量,對于 Python 語言會生成
一個(gè)特殊的 EnumDescriptor 類窄赋,在 Python 運(yùn)行時(shí)會生成一系列整形的符號常量供程序使用。

在消息反序列化的時(shí)候楼熄,無法識別的枚舉類型會被保留在消息中忆绰,但是這種枚舉類型如何復(fù)現(xiàn)依賴于所使用的編程語言。對于支持開放式枚舉類型的編程語言可岂,枚舉類型取值超出特定的符號范圍错敢,例如 C++和 Go 語言,未知的枚舉值在復(fù)現(xiàn)的時(shí)候簡單地以基礎(chǔ)型整數(shù)形式存儲缕粹。對于支持封閉式枚舉類型的編程語言稚茅,例如 Java纸淮,未知的枚舉值可以通過特殊的訪問函數(shù)讀取。在任何情況下亚享,只要消息被序列化咽块,無法識別的枚舉值也會跟著被序列化。

欲詳細(xì)了解枚舉類型如何在消息類型內(nèi)工作欺税,請根據(jù)你選擇的編程語言侈沪,參考 生成代碼參考

使用其它消息類型

你可以使用其它消息類型來定義字段。 假如你想在每一個(gè) SearchResponse 消息里面定義一個(gè) Result 消息類型的字段魄衅,你只需要同一個(gè).proto文件中定義 Result 消息峭竣,并用它來定義 SearchResponse 中的一個(gè)字段即可。

message SearchResponse {
    repeated Result result = 1;
}

message Result {
    string url = 1;
    string title = 2;
    repeated string snippets = 3;
}
導(dǎo)入定義

在上面的例子中晃虫,Result 消息類型和 SearchResponse 消息類型是定義在同一個(gè)文件中的皆撩,如果你想用另外一個(gè).proto 文件中定義的消息類型來定義字段該怎么做呢?

你可以導(dǎo)入其它.proto 文件中已經(jīng)定義的消息哲银,來定義該消息類型的字段扛吞。為了導(dǎo)入其它.proto 文件中的定義,你需要在你的.proto 文件頭部申明import 語句:

import "myproject/other_protos.proto";

默認(rèn)情況下荆责,你只能使用直接導(dǎo)入的.proto 文件中的定義滥比。然而,有時(shí)候你可能需要移動一個(gè).proto 文件到一個(gè)新的位置做院。如果直接移動這個(gè).proto文件盲泛,你需要一次更新所有引用了這個(gè).proto 文件的所有調(diào)用站點(diǎn)。你可以將文件移動之后(譯注:下面例子中的 new.proto)键耕,在原來的位置創(chuàng)建一個(gè)虛擬文件(譯注:下面例子中的 old.proto)寺滚,在其中用 import public 申明指向新位置的.proto 文件(譯注:這樣就實(shí)現(xiàn)了跨文件引用,而不需要更新調(diào)用站點(diǎn)里面的代碼了)屈雄。通過 import public 申明的導(dǎo)入依賴關(guān)系可以被任何調(diào)用了包含該 import public 語句的調(diào)用者間接繼承村视。(譯注:這段話繞來繞去就是說,默認(rèn)情況下,a 中 import b酒奶,b 中 import c蚁孔,a 只能引用 b 里面的定義,而不能引用 c 里面的定義惋嚎;如果你想 a 跨文件導(dǎo)入引用 c 里面的定義杠氢,就要在 b 中申明 import public c,這樣 a 既能引用 b 里面的定義另伍,又能引用 c 里面的定義了) 例如:

// 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

protoc 編譯器通過命令行參數(shù) -I 或 --proto_path 指定導(dǎo)入文件的搜索查找目錄 修然。如果沒有指定該參數(shù),默認(rèn)在編譯器所在目錄查找质况。 通常愕宋,你需要設(shè)定 --proto_path 參數(shù)為你的項(xiàng)目根目錄,使用全名稱目錄路徑指定導(dǎo)入文件搜索路徑结榄。

使用 proto2 消息類型

你在 proto3 中可以引用 proto2 消息類型中贝,反之亦可。 然而臼朗,proto2 語法格式的枚舉類型邻寿,不可以在 proto3 中引用。

消息嵌套

你可以在一個(gè)消息結(jié)構(gòu)內(nèi)部定義另外一個(gè)消息類型视哑,如下例所示---Result 消息類型定義在 SearchResponse 消息體內(nèi)部绣否。

message SearchResponse {

    message Result {
        string url = 1;
        string title = 2;
        repeated string snippets = 3;
    }

    repeated Result result = 1;
}

如果你想在它的父消息外部復(fù)用這個(gè)內(nèi)部定義的消息類型,可以采用 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;
            }

        }
}
更新一個(gè)消息類型

如果現(xiàn)有的消息類型無法滿足你的需要---例如蒜撮,你想為這個(gè)消息添加一個(gè)字段,但是你又想沿用原來的代碼格式跪呈,不用擔(dān)心段磨!
你可以非常簡單地就能在不破壞現(xiàn)有代碼的基礎(chǔ)上更新這個(gè)消息類型。只需要遵循以下原則:

  • 不要修改現(xiàn)有字段的數(shù)值標(biāo)簽耗绿。
  • 如果你為一個(gè)消息添加了字段苹支,所有已經(jīng)序列化調(diào)者依然可以通過舊的消息格式解析新消息生成的代碼。 你需要注意的是這些元素的 默認(rèn)值 误阻,以便新代碼可以真確地與舊代碼生成的消息交互债蜜。 同理,舊代碼也可以解析新代碼生成的消息:原二進(jìn)制程序在解析新的消息時(shí)究反,會忽略新添加的字段寻定。 注意:所有未知字段在消息反序列化的時(shí)候會被自動拋棄,所以當(dāng)消息傳遞給新代碼時(shí)奴紧,新字段不可用特姐。(這和 proto2 不一樣,proto2 中位置字段也會跟消息一起序列化)
  • 字段可以被移除黍氮,只要你不再在你更新后的消息里面使用被刪除字段的數(shù)值標(biāo)簽即可唐含。 或許你想重命名一個(gè)字段,通過添加前綴"OBSOLETE_"或者通過 reserved 關(guān)鍵字申明原數(shù)值標(biāo)簽,以免將來別人重用這個(gè)數(shù)值沫浆。
  • int32, uint32, int64, uint64, 和 bool 類型是相互兼容的,這意味著你可以改變這類字段的數(shù)值類型,而不必?fù)?dān)心破壞了向前或向后兼容性捷枯。 如果數(shù)值從傳輸介質(zhì)上解析的時(shí)候,不匹配相應(yīng)的數(shù)值類型专执,其效果相當(dāng)于你在 C++里面做了類型轉(zhuǎn)換(例如淮捆,一個(gè) 64 位的整數(shù)被解析為 32 位整數(shù)時(shí),它會被轉(zhuǎn)換為 32 位整數(shù))。
  • sint32 和 sint64 互相兼容攀痊,但是它們不和其它整數(shù)類型兼容桐腌。
  • 只要 bytes 類型是有效的 UTF-8 格式,它就和 string 類型兼容苟径。
  • 內(nèi)嵌的消息類型和包含該消息類型編碼后的字節(jié)內(nèi)容的 bytes 類型兼容案站。
  • fxied32 與 sfixed32 兼容,同樣的棘街,fixed64 與 sfixed64 兼容蟆盐。
Any

Any 類型允許你在沒有某些消息類型的.proto 定義時(shí),像使用內(nèi)嵌的消息類型一樣使用它來定義消息類型的字段遭殉。一個(gè) Any 類型的消息是一個(gè)包含任意字節(jié)數(shù)的序列化消息石挂,擁有一個(gè) URL 地址作為全局唯一標(biāo)識符來解決消息的類型。為了使用 Any 類型的消息险污,你需要import google/protobuf/any.proto

import "google/protobuf/any.proto";

message ErrorStatus {
        string message = 1;
        repeated Any details = 2;
}

給定 Any 消息類型的默認(rèn) URL 是: type.googleapis.com/packagename.messagename痹愚。
不同的語言實(shí)現(xiàn)都會支持運(yùn)行庫幫助通過類型安全的方式來封包或解包 Any 類型的消息。在 Java 語言中罗心,Any 類型有專門的訪問函數(shù) pack()和unpack()里伯。在 C++中對應(yīng)的是 PackFrom()和 PackTo()方法。

// 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.IsType<NetworkErrorDetails>()) {
          NetworkErrorDetails network_error;
          detail.UnpackTo(&network_error);
          ... processing network_error ...
    }
}

當(dāng)前渤闷,Any 類型的運(yùn)行時(shí)庫還在開發(fā)中疾瓮。
如果你已經(jīng)熟悉 proto2 語法 ,Any 類型就是替代了 proto2 中的 extensions 飒箭。

Oneof

如果你的消息中定義了很多字段狼电,而且最多每次只能有一個(gè)字段被設(shè)置賦值,那么你可以利用 Oneof 特性來實(shí)現(xiàn)這種行為并能節(jié)省內(nèi)存弦蹂。

Oneof 字段除了擁有常規(guī)字段的特性之外肩碟,所有字段共享一片 oneof 內(nèi)存,而且每次最多只能有一個(gè)字段被設(shè)置賦值凸椿。設(shè)置 oneof組中的任意一個(gè)成員的值時(shí)削祈,其它成員的值被自動清除。 你可以用 case()或 WhickOneof()方法檢查 oneof 組中哪個(gè)成員的值被設(shè)置了脑漫,具體選擇哪個(gè)方法取決于你所使用的編程語言髓抑。

使用 Oneof

在.proto 文件中定義 oneof 需要用到 oneof 關(guān)鍵字,其后緊跟的是 oneof 的名字优幸。下例中的 oneof 名字是 test_oneof:

message SampleMessage {
    oneof test_oneof {
          string name = 4;
          SubMessage sub_message = 9;
      }
}

然后你就可以往 oneof 定義中添加 oneof 的字段了吨拍。 你可以使用任何類型的字段,但是不要使用 repeated 可重復(fù)字段网杆。

在編譯后生成的代碼中羹饰,oneof 字段和常規(guī)字段一樣擁有 setter 或 getter 操作函數(shù)伊滋。根據(jù)你所選擇的編程語言,你能夠找到一個(gè)特殊方法函數(shù)用來檢查是哪一個(gè) oneof 被賦值了队秩。 更多詳情請參考 API 參考 笑旺。

Oneof 特性
  • 設(shè)置 oneof 組中某一個(gè)成員的值時(shí),其它成員的值被自動清除刹碾。因此燥撞,當(dāng)你的 oneof 組中有多個(gè)成員的時(shí)候,只有最有一個(gè)被賦
    值的字段擁有自己的值迷帜。
SampleMessage message;
message.set_name("name");
CHECK(message.has_name());
message.mutable_sub_message(); // Will clear name field.
CHECK(!message.has_name());
  • 如果解析器發(fā)現(xiàn)多個(gè) oneof 組的成員被存儲在介質(zhì)上,只有最后一個(gè)成員被解析到消息中色洞。
  • oneof 字段不能是 repeated 可重復(fù)的戏锹。
  • oneof 字段可以使用反射函數(shù)。
  • 如果你正在使用 C++火诸,確認(rèn)沒有代碼造成內(nèi)存崩潰锦针。 在下面的例子中,代碼會造成內(nèi)存崩潰置蜀。因?yàn)?sub_message 在調(diào)用 set_name("name")的時(shí)候已經(jīng)被清除.
SampleMessage message;
SubMessage* sub_message = message.mutable_sub_message();
message.set_name("name"); // Will delete sub_message
sub_message->set_... // Crashes here
  • 在 C++中奈搜,如果你調(diào)用 swap()方法交換兩個(gè)擁有 oneof 的消息,被交換的消息會擁有對方的 oneof 實(shí)例:在下面的例子中盯荤,msg1 會擁有一個(gè)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());
向后兼容的問題

在添加或刪除 oneof 字段的時(shí)候秋秤,需要格外小心宏粤。 如果檢查一個(gè) oneof 值的時(shí)候返回 None 或 NOT_SET,意味著這個(gè) oneof 沒有字段被賦值過或者如果被賦值過但是使用的是其它版本的 oneof灼卢。 沒有辦法區(qū)分它們绍哎,因此沒法區(qū)分傳輸介質(zhì)上的一個(gè)未知字段是不是 oneof 成
員。(譯注:on the wire鞋真,我翻譯為傳輸介質(zhì)不知道準(zhǔn)確否崇堰?)

標(biāo)簽重用的問題
  • 將字段移進(jìn)或移出 oneof 組: 在消息序列化和解析之后,可能會丟失某些信息(某些字段會被清除)涩咖。
  • 刪除一個(gè) oneof 字段之后又添加回去: 在消息序列化和解析之后海诲,可能會清除當(dāng)前為 oneof 設(shè)置的值。
  • 分割或合并 oneof: 這種情況和移動常規(guī)字段到 oneof 組的類似抠藕。
Map

如果你想定義 map 類型的數(shù)據(jù)饿肺,ProtoBuf 提供非常便捷的語法:

map<key_type, value_type> map_field = N;

其中,key_type 可以任意整數(shù)類型或字符串類型(除浮點(diǎn)類型或 bytes 類型意外的任意 標(biāo)準(zhǔn)類型 )盾似。value_Type 可以是任意類型敬辣。
例如雪标,你可以創(chuàng)建一個(gè) map 類型的 projects,關(guān)聯(lián)一個(gè) string 類型和一個(gè) Project 消息類型(譯注:作為鍵-值對)溉跃,用 map 定義如下:

map<string, Project> projects = 3;

map 類型的字段不可重復(fù)(不能用 repeated 修飾)村刨。 需要注意的是:map 類型字段的值在傳輸介質(zhì)上的順序和迭代器的順序是未定義的,你不能指望 map 類型的字段內(nèi)容按指定順序排列撰茎。
proto3 目前支持的所有語言都 map 類型的操作 API嵌牺。 欲知詳情,請根據(jù)你所選擇的編程語言閱讀 API 參考 中相關(guān)內(nèi)容龄糊。

向后兼容性

在傳輸介質(zhì)上逆粹,下面的代碼等效于 map 語法的實(shí)現(xiàn)。因此炫惩,即使目前 ProtoBuf 不支持 map 可重復(fù)的特性依然可以用下面這種(變通的)方式來處理:

message MapFieldEntry {
    key_type key = 1;
    value_type value = 2;
}
repeated MapFieldEntry map_field = N;

你可以在.proto 文件中選擇使用 package 說明符僻弹,避免 ProtoBuf 消息類型之間的名稱沖突。(譯注:這個(gè)和 java 里面包的概念以及 C++中命名空間的作用一樣)

package foo.bar;
message Open { ...}

你可以在消息類型內(nèi)部使用包描述符的引用來定義字段:

message Foo {
...
foo.bar.Open open = 1;
...
}

包描述符對編譯后生成的代碼的影響依賴于你所選擇的編程語言:

  • 在 C++中他嚷,生成的類被包裝在以包描述符命名的命名空間中蹋绽。 例如,Open 類將會出現(xiàn)在命名空間 foo::bar 中筋蓖。
  • 在 Java 里面卸耘,除非你在.proto 文件中顯示聲明了 option java_package,否則這個(gè)包名會被 Java 直接采用粘咖。
  • 在 Python 里面蚣抗,包名被直接忽略了,因?yàn)?Python 模塊的組織依據(jù)的是 Python 文件的在文件系統(tǒng)中的存放位置涂炎。
  • 在 Go 中忠聚,除非你在.proto 文件中顯示聲明了 option go_package,否則這個(gè)包名會被 Go 作為包名使用唱捣。
  • 在 Ruby 里面两蟀,所生成的類被包裹在嵌套的 Ruby 命名空間中,包名被轉(zhuǎn)換為 Ruby 大寫樣式(第一個(gè)字母大寫震缭,如果第一個(gè)不是字母字符赂毯,添加PB_前綴)。例如拣宰,Open 類會出現(xiàn)在命名空間 Foo::Bar 中党涕。
  • 在 JavaNano 中,除非你在.proto 文件中顯示聲明了 option java_package巡社,否則這個(gè)包名會被作為 Java 包名使用膛堤。
包和名稱解析

ProtoBuf 解析包和名稱的方式與 C++語言類似:最內(nèi)層的最先被查找,然后是次內(nèi)層晌该,以此類推肥荔。每個(gè)包對于其父包來說都是“內(nèi)部”的绿渣。以一個(gè)'.'(英文句點(diǎn))開頭的包和名稱 (例如 .foo.bar.Baz)表示從最外層開始查找。

protoc 編譯器通過解析被導(dǎo)入的.proto 文件來解析所有的類型名稱燕耿。 任何一種被支持的語言的代碼生成器都知道如何正確地引用每一種類型中符,即使該語言具有不同的作用域規(guī)則。

定義服務(wù)

如果想在 RPC(遠(yuǎn)程過程調(diào)用)系統(tǒng)中使用自定義的消息類型誉帅,你可以在.proto 文件中定義一個(gè) RPC 服務(wù)淀散,protoc 編譯器就會根據(jù)你選擇的編程語言生成服務(wù)器接口和存根代碼。例如蚜锨,如果你想定義一個(gè) RPC 服務(wù)档插,擁有一個(gè)方法來根據(jù)你的 SearchRequest 返回SearchResponse,可以在你的.proto 文件中這樣定義:

service SearchService {
      rpc Search (SearchRequest) returns (SearchResponse);
}

與 ProtoBuf 最佳搭配的 RPC 系統(tǒng)是 gRPC :一個(gè) Google 開發(fā)的平臺無關(guān)語言無關(guān)的開源 RPC 系統(tǒng)踏志。gRPC 和 ProtoBuf 能夠非常完美的配合阀捅,你可以使用專門的 ProtoBuf 編譯插件直接從.proto 文件生成相關(guān) RPC 代碼。

如果你不想使用 gRPC针余,你也可以用自己的 RPC 來實(shí)現(xiàn)和 ProtoBuf 協(xié)作。 更多的關(guān)于RPC 的信息請參考 Proto2 語言指南 凄诞。

現(xiàn)在也有很多第三方的采用 ProtoBuf 的 RPC 項(xiàng)目在開展中圆雁。 我們已知的這類項(xiàng)目列表,請參考 第三方插件 WIKI 主頁 帆谍。

JSON 映射

Proto3 支持標(biāo)準(zhǔn)的 JSON 編碼伪朽,在不同的系統(tǒng)直接共享數(shù)據(jù)變得簡單。下表列出的是基礎(chǔ)的類型對照汛蝙。

在 JSON 編碼中烈涮,如果某個(gè)值被設(shè)置為 null 或丟失,在映射為 ProtoBuf 的時(shí)候會轉(zhuǎn)換為相應(yīng)的 默認(rèn)值 窖剑。 在 ProtoBuf 中如果一個(gè)字段是默認(rèn)值坚洽,在映射為 JSON 編碼的時(shí)候,這個(gè)默認(rèn)值會被忽略以節(jié)省空間西土⊙冉ⅲ可以通過選項(xiàng)設(shè)置,使得 JSON 編碼輸出中字段帶有默認(rèn)值需了。

選項(xiàng)

你可以在.proto 文件中可以聲明若干選項(xiàng)跳昼。 選項(xiàng)不會改變整個(gè)聲明的含意,但可能會影響在特定的上下文中它的處理方式肋乍。 完整的可用選項(xiàng)列表在文件google/protobuf/descriptor.proto 中定義鹅颊。

一些選項(xiàng)是文件級的,它們應(yīng)該聲明在最頂級的作用域范圍內(nèi)墓造,不要在消息堪伍、枚舉或服務(wù)定義中使用它們锚烦。一些選項(xiàng)是消息級的,應(yīng)該在消息結(jié)構(gòu)內(nèi)聲明杠娱。一些選項(xiàng)是字段級的挽牢,它們應(yīng)該聲明在字段定義語句中。選項(xiàng)也可以聲明在枚舉類型摊求、枚舉值禽拔、服務(wù)類型、服務(wù)方法中室叉。然而睹栖,目前沒有任何有用的選項(xiàng)采用這種方式。

下面列出最常用的一些選項(xiàng):

  • java_package (文件級選項(xiàng)):這個(gè)選項(xiàng)聲明你想生成的 Java 類擁有的包名茧痕。 如果沒有在.proto 文件中顯示地聲明 java_package 選項(xiàng)野来,默認(rèn)采用 proto 的包名(在.proto 文件中用 package 關(guān)鍵字定義)作為生成的 Java 類的包名。 然而踪旷,通常情況下 proto 包名不是好的 Java 格式的包名曼氛,proto 包名一般不會是以 Java 包名所期望的反向域名的格式命名。如果沒有生成 Java 代碼令野,這個(gè)選項(xiàng)沒有任何作用舀患。
option java_package = "com.example.foo";
  • java_outer_classname (文件級選項(xiàng)):這個(gè)選項(xiàng)定義了你想要編譯生成的 Java 輸出類的最外層類名(也是文件名)。 如果沒有在.proto文件中定義 java_outer_classname 選項(xiàng)气破,默認(rèn)將.proto 文件名轉(zhuǎn)換為駝峰格式的文件名(例如 foo_bar.proto 編譯生成 FooBar.java) 聊浅。如果沒有生成 Java 代碼,這個(gè)選項(xiàng)沒有任何作用现使。
option java_outer_classname = "Ponycopter";\
  • optimize_for (文件級選項(xiàng)): 它的取值可以是 SPEED,CODE_SIZE,或 LITE_RUNTIME低匙。 這個(gè)選項(xiàng)按如下方式影響 C++和 Java 的代碼生成
    器(也可能影響第三方的生成器):
  • SPEED --- 默認(rèn)值: protoc 編譯器會根據(jù)你定義的消息類型生成序列化、解析和執(zhí)行其它常規(guī)操作的代碼碳锈。這些代碼是高度優(yōu)化了的顽冶。
  • CODE_SIZE:protoc 編譯器會采用共享、反射技術(shù)來實(shí)現(xiàn)序列化殴胧、解析和其它操作的代碼渗稍,以生成最小的類脑融。 因此届囚,所生成的代碼比采用選項(xiàng) SPEED 的要小得多,但是操作性能降低了妇穴。類的公共成員函數(shù)依然是一樣的(用 SPEED 優(yōu)化選項(xiàng)也是如此)灸姊。 這種模式是最有用的應(yīng)用情況是:程序包含很多.proto 文件拱燃,但并不追求所有模塊都有極快的速度。
  • LITE_RUNTIME:protoc 編譯器生成的類力惯,僅依賴"lite"運(yùn)行時(shí)庫 (libprotobuf-lite , 而不是 libprotobuf)碗誉。"lite"運(yùn)行時(shí)庫比完整庫 (約一個(gè)數(shù)量級小) 小得多召嘶,但省略了某些功能,如描述符和反射哮缺。這對于運(yùn)行在像手機(jī)這樣的有空間限制的平臺上的應(yīng)用程序特別有用弄跌。 protoc 編譯器仍將為所有方法生成最快速的代碼,這和在 SPEED 模式一樣尝苇。生成的類將為每一種語言實(shí)現(xiàn) MessageLite版本的接口铛只,只提供了完整的 Message 接口的一個(gè)子集的實(shí)現(xiàn)。選項(xiàng) optimize_for = CODE_SIZE;
  • cc_enable_arenas(文件級選項(xiàng)): 為生成的 C++代碼 啟用 arena 內(nèi)存管理功能糠溜。(譯注:Arena Allocation淳玩,是一種 GC 優(yōu)化技術(shù),它可以有效地減少因內(nèi)存碎片導(dǎo)致的 Full GC非竿,從而提高系統(tǒng)的整體性能蜕着。)
  • objc_class_prefix(文件級選項(xiàng)):這個(gè)選項(xiàng)用來設(shè)置編譯器從.proto 文件生成的類和枚舉類型的名稱前綴。這個(gè)選型沒有默認(rèn)值红柱。您應(yīng)該使用 3–5 個(gè)大寫字母作為前綴來自 Apple 的建議 承匣。 請注意:所有的 2 個(gè)字母的前綴由蘋果公司保留使用。
  • packed (字段級選項(xiàng)): 這個(gè)選項(xiàng) 如果被設(shè)置為 true ,對于基本數(shù)值類型的可重復(fù)字段可以獲得更緊湊的編碼锤悄。使用此選項(xiàng),沒有負(fù)面影響悄雅。然而,請注意铁蹈,在 2.3.0 版本之前是用此選項(xiàng)解析器會忽略被打包的數(shù)據(jù)。因此众眨,更改現(xiàn)有字段為 packed握牧,會破壞數(shù)據(jù)傳輸?shù)募嫒菪浴τ?2.3.0 及以后的版本娩梨,這種改變是安全的并且解析器能夠同時(shí)接受這兩種格式的打包字段的數(shù)據(jù)沿腰,但要小心,如果你要處理舊的程序使用了舊版本的 protobuf狈定。
repeated int32 samples = 4 [packed=true];
  • deprecated (字段選項(xiàng)): 這個(gè)選項(xiàng)如果設(shè)置為 true 颂龙,表示該字段已被廢棄,你不應(yīng)該在后續(xù)的代碼中使用它纽什。 在大多數(shù)語言中這沒有任何實(shí)際的影響措嵌。在 Java 中,它會變@Deprecated 注釋芦缰。將來企巢,其他特定于語言的代碼生成器在為被標(biāo)注為 deprecated 的字段生成操作函數(shù)的時(shí)候,編譯器在嘗試使用該字段的代碼時(shí)發(fā)出警告让蕾。如果不希望將來有人使用使用這個(gè)字段浪规,請考慮用 reserved 關(guān)鍵字 聲明該字段或听。
int32 old_field = 6 [deprecated=true];
自定義選項(xiàng)

ProtoBuf 允許你使用自定義選項(xiàng)。這是一個(gè) 高級的功能笋婿,大多數(shù)人不需要誉裆。如果你認(rèn)為你需要?jiǎng)?chuàng)建自定義選項(xiàng),請參見 Proto2 語言指南 中 更詳細(xì)的信息缸濒。請注意足丢,創(chuàng)建自定義選項(xiàng)使用 extensions 關(guān)鍵字 ,只能用在 proto3 中的創(chuàng)建自定義選項(xiàng)绍填。

編譯創(chuàng)建類

要想從.proto 文件 生成 Java霎桅,Python,C++讨永、 Go滔驶、 Ruby,JavaNano卿闹,Objective-C 或 C#代碼揭糕,你需要在.proto 文件中定義自己的消息類型,并且使用 protoc 編譯器來編譯它锻霎。如果你還沒有安裝編譯器著角,請下載安裝包并按照自述文件中的說明來執(zhí)行安裝。

對于 Go 語言旋恼,還需要為編譯器安裝特殊的代碼生成器插件: 請?jiān)L問它在 Github 上的代碼倉儲 golang/protobuf 吏口,下載并按照 安裝提示操作。
編譯器的調(diào)用格式如下:

--javanano_out=DST_DIR --objc_out=DST_DIR --csharp_out=DST_DIR path/to/file.proto
  • IMPORT_PATH 指在.proto 文件中解析 import 指令時(shí)的查找目錄路徑冰更。如果省略产徊,則使用當(dāng)前目錄。通過多次傳遞參數(shù)--proto_path蜀细,可以實(shí)現(xiàn)在多個(gè)導(dǎo)入目錄中按順序查找舟铜。-I=IMPORT_PATH 是 --proto_path 的縮寫形式。
  • 您可以提供一個(gè)或多個(gè) 輸出指令 :
  • --cpp_out 在目錄 DST_DIR 中 生成 C++代碼中 奠衔。更多細(xì)節(jié)谆刨,請參考 C++代碼的生成。
  • --java_out 在目錄 DST_DIR 中 生成 Java 代碼归斤。 更多細(xì)節(jié)痊夭,請參考 Java 代碼的生成。
  • --python_out 在目錄 DST_DIR 中 生成 Python 代碼 官册。更多細(xì)節(jié)生兆,請參考 Python 代碼的生成。
  • --go_out 在目錄 DST_DIR 中 生成 Go 代碼 。 Go 代碼的生成參考文檔即將推出!
  • --ruby_out 在目錄 DST_DIR 中生成 Ruby 代碼鸦难。Ruby 代碼的生成參考文檔即將推出!
  • --javanano_out 在目錄 DST_DIR 中 生成 JavaNano 代碼 根吁。 JavaNano 的代碼生成器有很多選項(xiàng),你可以自定義編譯輸出合蔽,欲知詳情請參考代碼生成器的自述文件 击敌。 JavaNano 代碼的生成參考文檔即將推出!
  • --objc_out 在目錄 DST_DIR 中生成 Objective-C 代碼。Objective-C 代碼的生成參考文檔即將推出!
  • --csharp_out 在目錄 DST_DIR 中生成 C#代碼拴事。C# 生成代碼的參考文檔即將推出!
    額外福利沃斤,如果 DST_DIR 以.zip 或 .jar 結(jié)尾,編譯器會將代碼輸出寫入到給定名稱的單個(gè) ZIP 格式壓縮文檔中刃宵。.jar 輸出也將根據(jù) Java JAR 規(guī)范的要求提供 manifest 清單文件衡瓶。請注意,如果輸出路徑中具有與編譯輸出文件同名的文件牲证,編譯器將直接覆蓋它而不會保存為另外一個(gè)名字哮针。
  • 您必須提供一個(gè)或多個(gè).proto 文件作為編譯輸入。多個(gè).proto 文件可以同時(shí)編譯坦袍。雖然文件是相對于當(dāng)前目錄來命名的十厢,每個(gè)文件至少要在一個(gè) IMPORT_PATH 指定的路徑范圍內(nèi),這樣編譯器可以為其確定規(guī)范的名稱捂齐。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末蛮放,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子奠宜,更是在濱河造成了極大的恐慌包颁,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,013評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件压真,死亡現(xiàn)場離奇詭異徘六,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)榴都,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,205評論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來漠其,“玉大人嘴高,你說我怎么就攤上這事『褪海” “怎么了拴驮?”我有些...
    開封第一講書人閱讀 152,370評論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長柴信。 經(jīng)常有香客問我套啤,道長,這世上最難降的妖魔是什么随常? 我笑而不...
    開封第一講書人閱讀 55,168評論 1 278
  • 正文 為了忘掉前任潜沦,我火速辦了婚禮萄涯,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘唆鸡。我一直安慰自己涝影,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,153評論 5 371
  • 文/花漫 我一把揭開白布争占。 她就那樣靜靜地躺著燃逻,像睡著了一般。 火紅的嫁衣襯著肌膚如雪臂痕。 梳的紋絲不亂的頭發(fā)上伯襟,一...
    開封第一講書人閱讀 48,954評論 1 283
  • 那天,我揣著相機(jī)與錄音握童,去河邊找鬼姆怪。 笑死,一個(gè)胖子當(dāng)著我的面吹牛舆瘪,可吹牛的內(nèi)容都是我干的片效。 我是一名探鬼主播,決...
    沈念sama閱讀 38,271評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼英古,長吁一口氣:“原來是場噩夢啊……” “哼淀衣!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起召调,我...
    開封第一講書人閱讀 36,916評論 0 259
  • 序言:老撾萬榮一對情侶失蹤膨桥,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后唠叛,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體只嚣,經(jīng)...
    沈念sama閱讀 43,382評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,877評論 2 323
  • 正文 我和宋清朗相戀三年艺沼,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了册舞。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 37,989評論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡障般,死狀恐怖调鲸,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情挽荡,我是刑警寧澤藐石,帶...
    沈念sama閱讀 33,624評論 4 322
  • 正文 年R本政府宣布,位于F島的核電站定拟,受9級特大地震影響于微,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,209評論 3 307
  • 文/蒙蒙 一株依、第九天 我趴在偏房一處隱蔽的房頂上張望驱证。 院中可真熱鬧,春花似錦勺三、人聲如沸雷滚。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,199評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽祈远。三九已至,卻和暖如春商源,著一層夾襖步出監(jiān)牢的瞬間车份,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,418評論 1 260
  • 我被黑心中介騙來泰國打工牡彻, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留扫沼,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,401評論 2 352
  • 正文 我出身青樓庄吼,卻偏偏與公主長得像缎除,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子总寻,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,700評論 2 345

推薦閱讀更多精彩內(nèi)容