Go的標準包encoding/json對JSON的編解碼提供了完整的支持。 json
在編碼過程當中,json包會將Go的類型轉換爲JSON類型,轉換規則以下:
bool 轉換爲JSON boolean
浮點數, 整數, Number 轉換爲:JSON number
string轉換爲:JSON string
數組、切片 轉換爲:JSON數組
[]byte 轉換爲:base64 string
struct、map轉換爲:JSON object
func Marshal(v interface{}) ([]byte, error)
Marshal函數返回v的json編碼。Marshal函數會遞歸的處理值。若是一個值實現了Marshaler接口且非nil指針,會調用其MarshalJSON方法來生成json編碼。不然,Marshal函數使用默認編碼格式。
(1)結構體編碼
json包經過反射機制來實現編解碼,所以結構體必須導出所轉換的字段,不導出的字段不會被json包解析。數組
package main import ( "encoding/json" "fmt" ) type Person struct { Name string Age int sex string } func main() { bauer := Person{"Bauer", 25, "Man"} bytes, err := json.Marshal(bauer) if err != nil { fmt.Println("Marshal failed.") } fmt.Println(string(bytes)) // {"Name":"Bauer","Age":25} }
(2)結構體字段標籤
json包在解析結構體時,若是遇到key爲json的字段標籤,則會按照必定規則解析該標籤:第一個字段是在JSON串中使用的名字,後續字段爲其它選項,例如omitempty指定空值字段不出如今JSON中。若是整個value爲"-",則不解析該字段。緩存
package main import ( "encoding/json" "fmt" ) type Person struct { Name string `json:"name,omitempty"` Age int `json:"age"` Sex string `json:"-"` } func main() { bauer := Person{"Bauer", 25, "Man"} bytes, err := json.Marshal(bauer) if err != nil { fmt.Println("Marshal failed.") } fmt.Println(string(bytes)) // {"name":"Bauer","age":25} }
(3)匿名字段
json包在解析匿名字段時,會將匿名字段的字段當成該結構體的字段處理。服務器
package main import ( "encoding/json" "fmt" ) type Point struct{ X, Y int } type Circle struct { Point Radius int } func main() { data, err := json.Marshal(Circle{Point{50, 50}, 25}) if err == nil { fmt.Println(string(data)) } } // output: //{"X":50,"Y":50,"Radius":25}
(4)轉換接口
在調用Marshal(v interface{})函數時,Marshal函數會判斷v是否知足json.Marshaler接口或encoding.TextMarshaler接口。若是知足,則會調用json.Marshaler接口或encoding.TextMarshaler接口來進行轉換(若是兩個都知足,優先調用json.Marshaler)。json.Marshaler接口或encoding.TextMarshaler接口定義以下:網絡
type Marshaler interface { MarshalJSON() ([]byte, error) } type TextMarshaler interface { MarshalText() (text []byte, err error) }
json.Marshaler示例以下:數據結構
package main import ( "encoding/json" "fmt" ) type Point struct{ X, Y int } func (pt Point) MarshalJSON() ([]byte, error) { return []byte(fmt.Sprintf(`{"X":%d,"Y":%d}`, pt.X, pt.Y)), nil } func main() { data, err := json.Marshal(Point{50, 50}) if err == nil { fmt.Printf("%s\n", data) } } // output: // {"X":50,"Y":50}
encoding.TextMarshaler示例以下:app
package main import ( "encoding/json" "fmt" ) type Point struct{ X, Y int } func (pt Point) MarshalText() ([]byte, error) { return []byte(fmt.Sprintf("{\"X\":%d,\"Y\":%d}", pt.X, pt.Y)), nil } func main() { data, err := json.Marshal(Point{50, 50}) if err == nil { fmt.Printf("%s\n", data) } } // output: // "{\"X\":50,\"Y\":50}"
(5)編碼到輸出流func NewEncoder(w io.Writer) *Encoder
NewEncoder建立一個將數據寫入w的*Encoder
。func (enc *Encoder) Encode(v interface{}) error
Encode將v的json編碼寫入輸出流,並會寫入一個換行符。
使用示例以下:框架
package main import ( "encoding/json" "os" ) type Person struct { Name string Age int } func main() { persons := []Person{ {"Bauer", 30}, {"Bob", 20}, {"Lee", 24}, } encoder := json.NewEncoder(os.Stdout) for _, person := range persons { encoder.Encode(person) } } // output: // {"Name":"Bauer","Age":30} // {"Name":"Bob","Age":20} // {"Name":"Lee","Age":24}
解碼將JSON轉換爲Go數據類型。在解碼過程當中,json包會將JSON類型轉換爲Go類型,轉換規則以下:
JSON boolean 轉換爲 bool
JSON number 轉換爲 float64
JSON string 轉換爲 string
JSON數組 轉換爲 []interface{}
JSON object 轉換爲 map
null 轉換爲 nilfunc Unmarshal(data []byte, v interface{}) error
Unmarshal函數解析json編碼的數據data並將結果存入v指向的值,v一般傳入指針,不然解析雖不報錯,但數據沒法賦值到接受體中。
要將json數據解碼寫入一個指針對象,Unmarshal函數首先處理json數據中json字面值null的狀況。此時,函數將指針設爲nil;不然,函數將json數據解碼寫入指針指向的值;若是指針自己是nil,函數會先申請一個值並使指針指向它。
要將json數據解碼寫入一個結構體,函數會匹配輸入對象的鍵和Marshal使用的鍵(結構體字段名或者字段標籤指定的鍵名),優先選擇精確的匹配,但也接受大小寫不敏感的匹配。
若是一個JSON值不匹配給出的目標類型,或者若是一個json數字寫入目標類型時溢出,Unmarshal函數會跳過該字段並儘可能完成其他的解碼操做。若是沒有出現更加嚴重的錯誤,函數會返回一個描述第一個此類錯誤的詳細信息的UnmarshalTypeError。
JSON的null值解碼爲go的接口、指針、切片時會將其值設爲nil,null在json通常表示「不存在」。解碼json的null值到go類型時,不會形成任何改變,也不會產生錯誤。
當解碼字符串時,不合法的utf-8或utf-16字符不視爲錯誤,而是將非法字符替換爲unicode字符。
(1)JSON轉結構體
JSON能夠轉換成結構體。json包經過反射機制來實現解碼,所以結構體必須導出所轉換的字段,不導出的字段不會被json包解析,另外解析時不區分大小寫:異步
package main import ( "encoding/json" "fmt" ) type Person struct { Name string Age int sex string } func main() { data := []byte(`{"Name":"Bauer","Age":25,"sex":"Man"}`) var bauer Person json.Unmarshal(data, &bauer) fmt.Printf("Name:%s Age:%d sex:%s\n", bauer.Name, bauer.Age, bauer.sex) } // output: // Name:Bauer Age:25 sex:
(2)結構體字段標籤
解碼時依然支持結構體字段標籤,規則和編碼相同。tcp
package main import ( "encoding/json" "fmt" ) type Person struct { Name string `json:"name,omitempty"` Age int `json:"age"` Sex string `json:"-"` } func main() { data := []byte(`{"name":"Bauer","age":25,"Sex":"Man"}`) var bauer Person json.Unmarshal(data, &bauer) fmt.Printf("Name:%s, Age:%d, Sex:%s\n", bauer.Name, bauer.Age, bauer.Sex) } // output: // Name:Bauer, Age:25, Sex:
(3)匿名字段
在解碼JSON時,若是找不到字段,則查找字段的字段。
package main import ( "encoding/json" "fmt" ) type Point struct { X, Y int } type Circle struct { Point Radius int } func main() { data := []byte(`{"X":80,"Y":80,"Radius":40}`) var c Circle json.Unmarshal(data, &c) fmt.Printf("X:%d,Y:%d,Radius:%d\n", c.X, c.Y, c.Radius) } // output: // X:80,Y:80,Radius:40
(4)轉換接口
解碼時根據參數類型是否知足json.Unmarshaler和encoding.TextUnmarshaler來調用相應函數(若兩個函數都存在,則優先調用json.Unmarshaler)。json.Unmarshaler和encoding.TextUnmarshaler接口定義以下:
type Unmarshaler interface { UnmarshalJSON([]byte) error } type TextUnmarshaler interface { UnmarshalText(text []byte) error }
json.Unmarshaler接口示例:
package main import ( "encoding/json" "fmt" ) type Point struct{ X, Y int } func (pt Point) UnmarshalJSON(data []byte) error { fmt.Println(string(data)) return nil } func main() { data := []byte(`{"X":50,"Y":50}`) var point Point json.Unmarshal(data, &point) } // output: // {"X":50,"Y":50}
encoding.TextUnmarshaler接口示例:
package main import ( "encoding/json" "fmt" ) type Point struct{ X, Y int } func (pt Point) UnmarshalText(text []byte) error { fmt.Println(string(text)) return nil } func main() { data := []byte(`"{\"X\":50,\"Y\":50}"`) var point Point json.Unmarshal(data, &point) } // output: // {"X":50,"Y":50}
(5)從輸入流解碼func NewDecoder(r io.Reader) *Decoder
NewDecoder建立一個從r讀取並解碼json對象的*Decoder
,Decoder有本身的緩衝,並可能超前讀取部分json數據。func (dec *Decoder) Buffered() io.Reader
Buffered方法返回保存在dec緩存裏數據的讀取器,該返回值在下次調用Decode方法前有效。func (dec *Decoder) UseNumber()
UseNumber方法將dec設置爲當接收端是interface{}接口時將json數字解碼爲Number類型而不是float64類型。func (dec *Decoder) Decode(v interface{}) error
Decode從輸入流讀取下一個json編碼值並保存在v指向的值裏
package main import ( "encoding/json" "fmt" "io" "strings" ) type Person struct { Name string Age int } func main() { const dataStream = ` { "Name" : "Bauer" , "Age" : 30} { "Name" : "Bob" , "Age" : 24 } { "Name" : "Lee" , "Age": 20} ` dec := json.NewDecoder(strings.NewReader(dataStream)) for { var person Person if err := dec.Decode(&person); err == io.EOF { break } fmt.Printf("Name: %s, Age: %d\n", person.Name, person.Age) } } // output: // Name: Bauer, Age: 30 // Name: Bob, Age: 24 // Name: Lee, Age: 20
Go的標準庫encoding/xml提供了對XML的操做。xml包提供了兩種方式來操做XML,一種是高階的方式,一種是低階的方式。高階的方式提供了Marshal和Unmarshal兩個函數分別來編碼(將Go數據結構轉換成XML)和解碼(將XML轉換成Go數據結構)。低階的方法則基於token來進行編碼和解碼。
低階方法是以Token爲單位操縱XML,Token有四種類型:StartElement用來表示XML開始節點;EndElement用來表示XML結束節點;CharData即爲XML的原始文本(raw text);Comment表示註釋。低階方法一般用在解析XML中的若干節點場景。
<!-- comment --> <action application="answer">raw text</action>
上述xml文件中, < action application="answer" > 爲StartElement, < /action > 爲EndElement,raw text爲CharData, < !-- -- > 爲Comment。
Go語言xml包對Token的數據結構進行了封裝,代碼以下:
type Name struct { Space, Local string } type Attr struct { Name Name Value string } type Token interface{} type StartElement struct { Name Name Attr []Attr } func (e StartElement) Copy() StartElement { attrs := make([]Attr, len(e.Attr)) copy(attrs, e.Attr) e.Attr = attrs return e } func (e StartElement) End() EndElement { return EndElement{e.Name} } type EndElement struct { Name Name } type CharData []byte func (c CharData) Copy() CharData { return CharData(makeCopy(c)) } type Comment []byte
xml包提供對xml文件的編碼解碼經常使用方法以下:
type TokenReader interface { Token() (Token, error) } type Decoder struct { Strict bool AutoClose []string Entity map[string]string CharsetReader func(charset string, input io.Reader) (io.Reader, error) DefaultSpace string r io.ByteReader t TokenReader buf bytes.Buffer saved *bytes.Buffer stk *stack free *stack needClose bool toClose Name nextToken Token nextByte int ns map[string]string err error line int offset int64 unmarshalDepth int } func NewDecoder(r io.Reader) *Decoder
NewDecoder從io.Reader對象讀取xml數據,建立一個Decoderfunc NewTokenDecoder(t TokenReader) *Decoder
NewTokenDecoder使用底層Token流建立一個XML解析器func (d *Decoder) Token() (Token, error)
Token返回解析器的下一個Token,解析結束返回io.EOF
type Encoder struct { p printer } func NewEncoder(w io.Writer) *Encoder
建立編碼器,參數爲io.Writerfunc (enc *Encoder) EncodeToken(t Token) error
編碼Tokenfunc (enc *Encoder) Flush() error
刷新緩衝區,將已經編碼內容寫入io.Writerfunc (enc *Encoder) Indent(prefix, indent string)
縮進
示例以下:
package main import ( "bytes" "encoding/xml" "fmt" "io" ) var file string = `<person id="13"><name><first>John</first><last>Doe</last></name><age>42</age><Married>false</Married><City>Hanga Roa</City><State>Easter Island</State><!-- Need more details. --></person>` func parseXMLFromToken(xmlFile string) { // 建立一個io.Reader reader := bytes.NewReader([]byte(xmlFile)) // 建立××× dec := xml.NewDecoder(reader) // 開始遍歷解碼 indent := "" // 控制縮進 sep := " " // 每層的縮進量爲四個空格 for { tok, err := dec.Token() // 返回下一個Token // 錯誤處理 if err == io.EOF { // 若是讀到結尾,則退出循環 break } switch tok := tok.(type) { // Type switch case xml.StartElement: // 開始節點,打印名字和屬性 fmt.Print(indent) fmt.Printf("<%s ", tok.Name.Local) s := "" for _, v := range tok.Attr { fmt.Printf(`%s%s="%s"`, s, v.Name.Local, v.Value) s = " " } fmt.Println(">") indent += sep // 遇到開始節點,則增長縮進量 case xml.EndElement: // 結束節點,打印名字 indent = indent[:len(indent)-len(sep)] // 遇到結束節點,則減小縮進量 fmt.Printf("%s</%s>\n", indent, tok.Name.Local) case xml.CharData: // 原始字符串,直接打印 fmt.Printf("%s%s\n", indent, tok) case xml.Comment: // 註釋,直接打印 fmt.Printf("%s<!-- %s -->\n", indent, tok) } } } type AttrMap map[string]string // 屬性的鍵值對容器 // start()用來構建開始節點 func start(tag string, attrs AttrMap) xml.StartElement { var a []xml.Attr for k, v := range attrs { a = append(a, xml.Attr{xml.Name{"", k}, v}) } return xml.StartElement{xml.Name{"", tag}, a} } func generateXMLFile() { // 建立編碼器 buffer := new(bytes.Buffer) enc := xml.NewEncoder(buffer) // 開始生成XML startPerson := start("person", AttrMap{"id": "13"}) enc.EncodeToken(startPerson) startName := start("name", AttrMap{}) enc.EncodeToken(startName) startFirstName := start("first", AttrMap{}) enc.EncodeToken(startFirstName) enc.EncodeToken(xml.CharData("John")) enc.EncodeToken(startFirstName.End()) starLastName := start("last", AttrMap{}) enc.EncodeToken(starLastName) enc.EncodeToken(xml.CharData("Doe")) enc.EncodeToken(starLastName.End()) enc.EncodeToken(startName.End()) startAge := start("age", AttrMap{}) enc.EncodeToken(startAge) enc.EncodeToken(xml.CharData("42")) enc.EncodeToken(startAge.End()) startMarried := start("Married", AttrMap{}) enc.EncodeToken(startMarried) enc.EncodeToken(xml.CharData("false")) enc.EncodeToken(startMarried.End()) startCity := start("City", AttrMap{}) enc.EncodeToken(startCity) enc.EncodeToken(xml.CharData("Hanga Roa")) enc.EncodeToken(startCity.End()) startState := start("State", AttrMap{}) enc.EncodeToken(startState) enc.EncodeToken(xml.CharData("Easter Island")) enc.EncodeToken(startState.End()) enc.EncodeToken(xml.Comment("Need more details.")) enc.EncodeToken(startPerson.End()) // 寫入XML enc.Flush() // 打印結果 fmt.Println(buffer) } func main() { fmt.Println("Decode XML:") parseXMLFromToken(file) fmt.Println("Encode XML:") generateXMLFile() }
xml包以反射機制實現的編解碼,所以自定義的結構體必須導出所要轉換的字段。xml包定義告終構體和XML數據的轉換規則。xml包根據字段的命名,字段的標籤來映射XML元素,轉換規則以下:
一、xml:"value,value,..."結構體標籤爲xml包所解析,第一個value對應XML中的名字(節點名、屬性名)。
二、字段與XML節點名對應關係:
A、若是存在名爲XMLName的字段,而且標籤中存在名字值,則該名字值爲節點名稱,不然
B、若是存在名爲XMLName的字段,而且類型爲xml.Name,則該字段的值爲節點名稱,不然
C、結構體名稱。
三、字段標籤的解析
A、"-"忽略該字段
B、"name,attr"字段映射爲XML屬性,name爲屬性名
C、",attr"字段映射爲XML屬性,字段名爲屬性名
D、",chardata"字段映射爲原始字符串
E、"omitempty"若包含此標籤則在字段值爲0值時忽略此字段
四、視匿名字段的字段爲結構體的字段
xml高階方式經常使用方法以下:func Marshal(v interface{}) ([]byte, error)
接收一個interface{},遍歷其結構,編碼爲XML
type Marshaler interface { MarshalXML(e *Encoder, start StartElement) error } func MarshalIndent(v interface{}, prefix, indent string) ([]byte, error)
接收一個interface{},遍歷其結構,編碼爲XML,增長縮進func Unmarshal(data []byte, v interface{}) error
將data解碼爲v,v一般爲結構體
高階方法適用於須要編碼和解碼整個XML而且須要以結構化的數據操縱XML的場景。高階方法必須導出結構體,會破壞封裝。
建立一個test.xml,內容以下:
<?xml version="1.0" encoding="UTF-8"?> <person id="13"> <name> <first>John</first> <last>Doe</last> </name> <age>42</age> <Married>false</Married> <City>Hanga Roa</City> <State>Easter Island</State> </person>
示例以下:
package main import ( "encoding/xml" "fmt" "io/ioutil" "os" ) type Person struct { XMLName string `xml:"person"` ID string `xml:"id,attr"` Name Name Age Age Married Married City City State State } type Name struct { XMLName string `xml:"name"` First FirstName Last LastName } type FirstName struct { XMLName string `xml:"first"` Data string `xml:",chardata"` } type LastName struct { XMLName string `xml:"last"` Data string `xml:",chardata"` } type Age struct { XMLName string `xml:"age"` Data int `xml:",chardata"` } type Married struct { XMLName string `xml:"Married"` Data bool `xml:",chardata"` } type City struct { XMLName string `xml:"City"` Data string `xml:",chardata"` } type State struct { XMLName string `xml:"State"` Data string `xml:",chardata"` } func generateXMLFile(xmlFile string) { first := FirstName{"first", "John"} last := LastName{"last", "Doe"} name := Name{"name", first, last} age := Age{"age", 42} married := Married{"Married", false} city := City{"City", "Hanga Roa"} state := State{"State", "Easter Island"} person := Person{"person", "13", name, age, married, city, state} data, _ := xml.MarshalIndent(person, "", " ") headerBytes := []byte(xml.Header) //加入XML頭 outputData := append(headerBytes, data...) ioutil.WriteFile("test.xml", outputData, os.ModeAppend) } func parseXMLFile(xmlFile string) { bytes, err := ioutil.ReadFile(xmlFile) if err != nil { fmt.Println(err) } var person Person xml.Unmarshal(bytes, &person) fmt.Println("FirstName: ", person.Name.First.Data) fmt.Println("LastName: ", person.Name.Last.Data) fmt.Println("ID: ", person.ID) fmt.Println("Married: ", person.Married.Data) fmt.Println("Age: ", person.Age.Data) fmt.Println("City: ", person.City.Data) fmt.Println("State: ", person.State.Data) } func main() { generateXMLFile("test.xml") parseXMLFile("test.xml") }
Base64是網絡上最多見的用於傳輸8Bit字節代碼的編碼方式之一。Base64編碼可用於在HTTP環境下傳遞較長的標識信息。在Java Persistence系統Hibernate中,就採用了Base64來將一個較長的惟一標識符(通常爲128-bit的UUID)編碼爲一個字符串,用做HTTP表單和HTTP GET URL中的參數。採用Base64編碼具備不可讀性,即所編碼的數據不會被人用肉眼所直接看到。
Go語言中encoding/base64提供了對base64編解碼支持,encoding/base64定義了一個Encoding結構體,表示Base64的Encoding。而且導出了四個經常使用的Encoding對象:StdEncoding、URLEncoding、RawStdEncoding、RawURLEncoding。StdEncoding表示標準的Encoding,URLEncoding用於對URL編解碼,編解碼過程當中會將Base64編碼中的特殊標記+和/替換爲-和_
,RawStdEncoding和RawURLEncoding是StdEncoding和URLEncoding的非padding版本。
type Encoding struct { encode [64]byte decodeMap [256]byte padChar rune strict bool } // 四個導出的編碼/××× var StdEncoding = NewEncoding(encodeStd) var URLEncoding = NewEncoding(encodeURL) var RawStdEncoding = StdEncoding.WithPadding(NoPadding) var RawURLEncoding = URLEncoding.WithPadding(NoPadding)
func (enc *Encoding) Encode(dst, src []byte)
將src編碼爲dstfunc (enc *Encoding) EncodeToString(src []byte) string
將src編碼,返回stringfunc (enc *Encoding) Decode(dst, src []byte) (n int, err error)
將src解碼並寫入dst,成功返回寫入的字節數和errorfunc (enc *Encoding) DecodeString(s string) ([]byte, error)
將字符串s解碼並返回[]bytefunc (enc Encoding) WithPadding(padding rune) *Encoding
設置enc的padding,返回Encoding指針,NoPadding表示不進行padding操做func NewDecoder(enc *Encoding, r io.Reader) io.Reader
建立一個base64的輸入流×××func NewEncoder(enc *Encoding, w io.Writer) io.WriteCloser
建立一個base64的輸出流編碼器
package main import ( "encoding/base64" "fmt" "io" "os" "strings" ) func StdEncodingExample() { data := "Hello world!" encoded := base64.StdEncoding.EncodeToString([]byte(data)) fmt.Println(encoded) decoded, err := base64.StdEncoding.DecodeString(encoded) if err == nil { fmt.Println(string(decoded)) } // Output: // SGVsbG8gd29ybGQh // Hello world! } func URLEncodingExample() { url := []byte("https://blog.51cto.com/9291927") encoded := base64.URLEncoding.EncodeToString(url) fmt.Println(encoded) decoded, err := base64.URLEncoding.DecodeString(encoded) if err == nil { fmt.Println(string(decoded)) } // Output: // aHR0cDovL2Jsb2cuNTFjdG8uY29tLzkyOTE5Mjc= // https://blog.51cto.com/9291927 } func ExampleStream() { data := []byte("Hello Hyperledger Fabric") encoder := base64.NewEncoder(base64.StdEncoding, os.Stdout) encoder.Write(data) encoder.Close() fmt.Println() input := "SGVsbG8gSHlwZXJsZWRnZXIgRmFicmlj" reader := strings.NewReader(input) decoder := base64.NewDecoder(base64.StdEncoding, reader) io.Copy(os.Stdout, decoder) // output: // SGVsbG8gSHlwZXJsZWRnZXIgRmFicmlj // Hello Hyperledger Fabric } func main() { StdEncodingExample() URLEncodingExample() ExampleStream() }
utf8實現了函數和常量來支持UTF-8編碼的文本。
const ( RuneError = '\uFFFD' // 錯誤的 Rune 或 Unicode 代理字符 RuneSelf = 0x80 // ASCII 字符範圍 MaxRune = '\U0010FFFF' // Unicode 碼點的最大值 UTFMax = 4 // 一個字符編碼的最大長度 ) func EncodeRune(p []byte, r rune) int
將r轉換爲UTF-8編碼寫入p中(p必須足夠長,一般爲4個字節)
若是r是無效的Unicode字符,則寫入RuneError
返回寫入的字節數func DecodeRune(p []byte) (r rune, size int)
解碼p中的第一個字符,返回解碼後的字符和p中被解碼的字節數
若是p爲空,則返回(RuneError, 0)
若是p中的編碼無效,則返回(RuneError, 1)
無效編碼:UTF-8 編碼不正確(好比長度不夠)、結果超出Unicode範圍、編碼不是最短的。func DecodeRuneInString(s string) (r rune, size int)
解碼s中的第一個字符,返回解碼後的字符和p中被解碼的字節數func DecodeLastRune(p []byte) (r rune, size int)
解碼p中的最後一個字符,返回解碼後的字符和p中被解碼的字節數
若是p爲空,則返回(RuneError, 0)
若是p中的編碼無效,則返回(RuneError, 1)func DecodeLastRuneInString(s string) (r rune, size int)
解碼p中的最後一個字符,返回解碼後的字符和p中被解碼的字節數func FullRune(p []byte) bool
FullRune檢測p中第一個字符的UTF-8編碼是否完整(完整並不表示有效)。
一個無效的編碼也被認爲是完整字符,將被轉換爲一個RuneError字符。func FullRuneInString(s string) bool
FullRune檢測s中第一個字符的UTF-8編碼是否完整(完整並不表示有效)。func RuneCount(p []byte) int
返回p中的字符個數
錯誤的UTF8編碼和長度不足的UTF8編碼將被看成單字節的RuneError處理func RuneCountInString(s string) (n int)
返回s中的字符個數func RuneLen(r rune) int
RuneLen返回須要多少字節來編碼字符r,若是r是無效的字符,則返回-1func RuneStart(b byte) bool
判斷b是否爲UTF8字符的首字節編碼,最高位(bit)是否是10的字節就是首字節。func Valid(p []byte) bool
Valid判斷p是否爲完整有效的UTF8編碼序列。func ValidString(s string) bool
Valid判斷s是否爲完整有效的UTF8編碼序列。func ValidRune(r rune) bool
ValidRune判斷r可否被正確的轉換爲UTF8編碼。
超出Unicode範圍的碼點或UTF-16代理區中的碼點不能轉換。
package main import ( "fmt" "unicode/utf8" ) func ExampleDecodeLastRune() { b := []byte("Hello, 世界") for len(b) > 0 { r, size := utf8.DecodeLastRune(b) fmt.Printf("%c %v\n", r, size) b = b[:len(b)-size] } // Output: // 界 3 // 世 3 // 1 // , 1 // o 1 // l 1 // l 1 // e 1 // H 1 } func ExampleDecodeLastRuneInString() { str := "Hello, 世界" for len(str) > 0 { r, size := utf8.DecodeLastRuneInString(str) fmt.Printf("%c %v\n", r, size) str = str[:len(str)-size] } // Output: // 界 3 // 世 3 // 1 // , 1 // o 1 // l 1 // l 1 // e 1 // H 1 } func ExampleDecodeRune() { b := []byte("Hello, 世界") for len(b) > 0 { r, size := utf8.DecodeRune(b) fmt.Printf("%c %v\n", r, size) b = b[size:] } // Output: // H 1 // e 1 // l 1 // l 1 // o 1 // , 1 // 1 // 世 3 // 界 3 } func ExampleDecodeRuneInString() { str := "Hello, 世界" for len(str) > 0 { r, size := utf8.DecodeRuneInString(str) fmt.Printf("%c %v\n", r, size) str = str[size:] } // Output: // H 1 // e 1 // l 1 // l 1 // o 1 // , 1 // 1 // 世 3 // 界 3 } func ExampleEncodeRune() { r := '世' buf := make([]byte, 3) n := utf8.EncodeRune(buf, r) fmt.Println(buf) fmt.Println(n) // Output: // [228 184 150] // 3 } func ExampleFullRune() { buf := []byte{228, 184, 150} // 世 fmt.Println(utf8.FullRune(buf)) fmt.Println(utf8.FullRune(buf[:2])) // Output: // true // false } func ExampleFullRuneInString() { str := "世" fmt.Println(utf8.FullRuneInString(str)) fmt.Println(utf8.FullRuneInString(str[:2])) // Output: // true // false } func ExampleRuneCount() { buf := []byte("Hello, 世界") fmt.Println("bytes =", len(buf)) fmt.Println("runes =", utf8.RuneCount(buf)) // Output: // bytes = 13 // runes = 9 } func ExampleRuneCountInString() { str := "Hello, 世界" fmt.Println("bytes =", len(str)) fmt.Println("runes =", utf8.RuneCountInString(str)) // Output: // bytes = 13 // runes = 9 } func ExampleRuneLen() { fmt.Println(utf8.RuneLen('a')) fmt.Println(utf8.RuneLen('界')) // Output: // 1 // 3 } func ExampleRuneStart() { buf := []byte("a界") fmt.Println(utf8.RuneStart(buf[0])) fmt.Println(utf8.RuneStart(buf[1])) fmt.Println(utf8.RuneStart(buf[2])) // Output: // true // true // false } func ExampleValid() { valid := []byte("Hello, 世界") invalid := []byte{0xff, 0xfe, 0xfd} fmt.Println(utf8.Valid(valid)) fmt.Println(utf8.Valid(invalid)) // Output: // true // false } func ExampleValidRune() { valid := 'a' invalid := rune(0xfffffff) fmt.Println(utf8.ValidRune(valid)) fmt.Println(utf8.ValidRune(invalid)) // Output: // true // false } func ExampleValidString() { valid := "Hello, 世界" invalid := string([]byte{0xff, 0xfe, 0xfd}) fmt.Println(utf8.ValidString(valid)) fmt.Println(utf8.ValidString(invalid)) // Output: // true // false } func main() { ExampleDecodeLastRune() ExampleDecodeLastRuneInString() ExampleDecodeRune() ExampleDecodeRuneInString() ExampleEncodeRune() ExampleFullRune() ExampleFullRuneInString() ExampleRuneCount() ExampleRuneCountInString() ExampleRuneLen() ExampleRuneStart() ExampleValid() ExampleValidRune() ExampleValidString() }
RPC(Remote Procedure Call,遠程過程調用)是一種經過網絡從遠程計算機程序上請求服務,而不須要了解底層網絡細節的應用程序通訊協議。在OSI網絡通訊模型中,RPC跨越了傳輸層和應用層。RPC使得開發包括網絡分佈式多程序在內的應用程序更加容易。
RPC採用客戶機/服務器模式。請求程序是一個客戶機,而服務提供程序是一個服務器。首先,客戶機調用進程發送一個有進程參數的調用信息到服務進程,而後等待應答信息。在服務器端,進程保持睡眠狀態直到調用信息到達爲止。當一個調用信息到達,服務器得到進程參數,計算結果,發送答覆信息,而後等待下一個調用信息,最後,客戶端調用進程接收答覆信息,得到進程結果,而後調用執行繼續進行。
RPC調用過程以下:
一、調用客戶端句柄;執行傳送參數
二、調用本地系統內核發送網絡消息
三、消息傳送到遠程主機
四、服務器句柄獲得消息並取得參數
五、執行遠程過程
六、執行的過程將結果返回服務器句柄
七、服務器句柄返回結果,調用遠程系統內核
八、消息傳回本地主機
九、客戶句柄由內核接收消息
十、客戶接收句柄返回的數據
Go的rpc支持三個級別的RPC:TCP、HTTP、JSONRPC。但Go的RPC包是獨一無二的RPC,與傳統的RPC系統不一樣,只支持Go開發的服務器與客戶端之間的交互,由於內部採用Gob編碼。Gob是Golang包自帶的一個數據結構序列化的編碼/解碼工具,編碼使用Encoder,解碼使用Decoder,其典型應用場景就是RPC。
Go RPC的函數只有符合下面的條件才能被遠程訪問,否則會被忽略,詳細的要求以下:
(1)函數必須是導出的(首字母大寫)
(2)必須有兩個導出類型的參數,第一個參數是接收的參數,第二個參數是返回給客戶端的參數,第二個參數必須是指針類型的。
(3)函數還要有一個返回值errorfunc (t *T) MethodName(argType T1, replyType *T2) error
T、T1和T2類型必須能被encoding/gob包編解碼。
net/rpc定義了一個缺省的DefaultServer,實現一個簡單的Server,能夠直接調用Server的不少方法。
var DefaultServer = NewServer() func HandleHTTP() { DefaultServer.HandleHTTP(DefaultRPCPath, DefaultDebugPath) }
若是須要配置不一樣的Server,如不一樣的監聽地址或端口,須要本身建立Server。func NewServer() *Server
Server的監聽方式以下:
func (server *Server) Accept(lis net.Listener) func (server *Server) HandleHTTP(rpcPath, debugPath string) func (server *Server) ServeCodec(codec ServerCodec) func (server *Server) ServeConn(conn io.ReadWriteCloser) func (server *Server) ServeHTTP(w http.ResponseWriter, req *http.Request) func (server *Server) ServeRequest(codec ServerCodec) error
ServeHTTP 用於處理http請求的業務邏輯,首先處理http的 CONNECT請求,經過http.Hijacker建立鏈接conn, 而後調用ServeConn處理鏈接上 客戶端的請求。
func (server *Server) ServeHTTP(w http.ResponseWriter, req *http.Request) { if req.Method != "CONNECT" { w.Header().Set("Content-Type", "text/plain; charset=utf-8") w.WriteHeader(http.StatusMethodNotAllowed) io.WriteString(w, "405 must CONNECT\n") return } conn, _, err := w.(http.Hijacker).Hijack() if err != nil { log.Print("rpc hijacking ", req.RemoteAddr, ": ", err.Error()) return } io.WriteString(conn, "HTTP/1.0 "+connected+"\n\n") server.ServeConn(conn) }
Server.HandleHTTP用於設置rpc的上下文路徑,rpc.HandleHTTP使用默認的上下文路徑。
當使用http.ListenAndServe啓動一個http server的時,HandleHTTP設置的上下文將用做RPC傳輸,上下文的請求由ServeHTTP來處理。
func (server *Server) HandleHTTP(rpcPath, debugPath string) { http.Handle(rpcPath, server) http.Handle(debugPath, debugHTTP{server}) }
Accept用來處理一個監聽器,監聽客戶端的鏈接,一旦監聽器接收了一個鏈接,則交給ServeConn在另一個goroutine中處理。
func (server *Server) Accept(lis net.Listener) { for { conn, err := lis.Accept() if err != nil { log.Print("rpc.Serve: accept:", err.Error()) return } go server.ServeConn(conn) } } func (server *Server) ServeConn(conn io.ReadWriteCloser) { buf := bufio.NewWriter(conn) srv := &gobServerCodec{ rwc: conn, dec: gob.NewDecoder(conn), enc: gob.NewEncoder(buf), encBuf: buf, } server.ServeCodec(srv) }
鏈接最終由ServerCodec處理,默認使用gobServerCodec處理,可使用其它的Coder。
客戶端創建和服務器的鏈接
func Dial(network, address string) (*Client, error) func DialHTTP(network, address string) (*Client, error) func DialHTTPPath(network, address, path string) (*Client, error) func NewClient(conn io.ReadWriteCloser) *Client func NewClientWithCodec(codec ClientCodec) *Client
DialHTTP 和 DialHTTPPath經過HTTP的方式和服務器創建鏈接。
func DialHTTPPath(network, address, path string) (*Client, error) { var err error conn, err := net.Dial(network, address) if err != nil { return nil, err } io.WriteString(conn, "CONNECT "+path+" HTTP/1.0\n\n") // Require successful HTTP response // before switching to RPC protocol. resp, err := http.ReadResponse(bufio.NewReader(conn), &http.Request{Method: "CONNECT"}) if err == nil && resp.Status == connected { return NewClient(conn), nil } if err == nil { err = errors.New("unexpected HTTP response: " + resp.Status) } conn.Close() return nil, &net.OpError{ Op: "dial-http", Net: network + " " + address, Addr: nil, Err: err, } }
首先發送CONNECT請求,若是鏈接成功則通NewClient(conn)建立client。
Dial經過TCP與服務器創建鏈接。
func Dial(network, address string) (*Client, error) { conn, err := net.Dial(network, address) if err != nil { return nil, err } return NewClient(conn), nil }
NewClient建立一個缺省codec爲glob序列化庫的客戶端。
func NewClient(conn io.ReadWriteCloser) *Client { encBuf := bufio.NewWriter(conn) client := &gobClientCodec{conn, gob.NewDecoder(conn), gob.NewEncoder(encBuf), encBuf} return NewClientWithCodec(client) }
NewClientWithCodec建立一個codec序列化庫的客戶端。
func NewClientWithCodec(codec ClientCodec) *Client { client := &Client{ codec: codec, pending: make(map[uint64]*Call), } go client.input() return client }
客戶端的調用RPC服務方法有兩個方法: Go 和 Call。 Go方法是異步的,返回一個Call指針對象, 它的Done是一個channel,若是服務返回,Done就能夠獲得返回的對象(實際是Call對象,包含Reply和error信息)。 Go是同步的方式調用,它實際是調用Call實現的
func (client *Client) Call(serviceMethod string, args interface{}, reply interface{}) error { call := <-client.Go(serviceMethod, args, reply, make(chan *Call, 1)).Done return call.Error }
rpc框架默認使用gob序列化庫,爲了追求更好的效率或者追求更通用的序列化格式,能夠採用其它序列化方式,如protobuf,,json,,xml等。
gob序列化庫要求註冊接口類型的具體實現類型。
func (server *Server) Register(rcvr interface{}) error func (server *Server) RegisterName(name string, rcvr interface{}) error
Server.go:
package main import ( "log" "net/http" "net/rpc" ) type Args struct { Width int Height int } type Rect struct{} func (r *Rect) GetArea(p Args, ret *int) error { *ret = p.Width * p.Height return nil } func (r *Rect) GetPerimeter(p Args, ret *int) error { *ret = (p.Width + p.Height) * 2 return nil } func main() { rect := new(Rect) //註冊一個rect服務 rpc.Register(rect) //綁定服務到HTTP協議 rpc.HandleHTTP() err := http.ListenAndServe(":8081", nil) if err != nil { log.Fatal(err) } }
Client.go:
package main import ( "fmt" "log" "net/rpc" ) type Args struct { Width int Height int } func main() { //鏈接遠程RPC服務 rpc, err := rpc.DialHTTP("tcp", "127.0.0.1:8081") if err != nil { log.Fatal(err) } ret := 0 //調用服務方法 err = rpc.Call("Rect.GetArea", Args{50, 100}, &ret) if err != nil { log.Fatal(err) } fmt.Println(ret) // 調用服務方法 err = rpc.Call("Rect.GetPerimeter", Args{50, 100}, &ret) if err != nil { log.Fatal(err) } fmt.Println(ret) } // output: // 5000 // 300
Server.go:
package main import ( "log" "net" "net/rpc" ) type Args struct { Width int Height int } type Rect struct{} func (r *Rect) GetArea(p Args, ret *int) error { *ret = p.Width * p.Height return nil } func (r *Rect) GetPerimeter(p Args, ret *int) error { *ret = (p.Width + p.Height) * 2 return nil } func errorHandler(err error) { if err != nil { log.Fatal(err) } } func main() { rect := new(Rect) //註冊RPC服務 rpc.Register(rect) tcpADDR, err := net.ResolveTCPAddr("tcp", "127.0.0.1:8081") errorHandler(err) //監聽端口 tcpListen, err := net.ListenTCP("tcp", tcpADDR) errorHandler(err) // 處理RPC鏈接請求 for { conn, err := tcpListen.Accept() if err != nil { continue } // goroutine處理RPC鏈接請求 go rpc.ServeConn(conn) } }
Client.go:
package main import ( "fmt" "log" "net/rpc" ) type Args struct { Width int Height int } func main() { //鏈接遠程RPC服務 rpc, err := rpc.Dial("tcp", "127.0.0.1:8081") if err != nil { log.Fatal(err) } ret := 0 //調用服務方法 err = rpc.Call("Rect.GetArea", Args{50, 100}, &ret) if err != nil { log.Fatal(err) } fmt.Println(ret) err = rpc.Call("Rect.GetPerimeter", Args{50, 100}, &ret) if err != nil { log.Fatal(err) } fmt.Println(ret) } // output: // 5000 // 300
JSON RPC方式使用json進行數據編解碼,而不是gob編碼。
Server.go:
package main import ( "log" "net" "net/rpc" "net/rpc/jsonrpc" ) type Args struct { Width int Height int } type Rect struct{} func (r *Rect) GetArea(p Args, ret *int) error { *ret = p.Width * p.Height return nil } func (r *Rect) GetPerimeter(p Args, ret *int) error { *ret = (p.Width + p.Height) * 2 return nil } func errorHandler(err error) { if err != nil { log.Fatal(err) } } func main() { rect := new(Rect) //註冊RPC服務 rpc.Register(rect) tcpAddr, err := net.ResolveTCPAddr("tcp", "127.0.0.1:8081") errorHandler(err) //監聽端口 tcpListen, err := net.ListenTCP("tcp", tcpAddr) errorHandler(err) for { conn, err := tcpListen.Accept() if err != nil { continue } //處理RPC鏈接請求 go jsonrpc.ServeConn(conn) } }
Client.go:
package main import ( "fmt" "log" "net/rpc/jsonrpc" ) type Args struct { Width int Height int } func main() { //鏈接遠程RPC服務 rpc, err := jsonrpc.Dial("tcp", "127.0.0.1:8081") if err != nil { log.Fatal(err) } ret := 0 //調用服務方法 err = rpc.Call("Rect.GetArea", Args{50, 100}, &ret) if err != nil { log.Fatal(err) } fmt.Println(ret) err = rpc.Call("Rect.GetPerimeter", Args{50, 100}, &ret) if err != nil { log.Fatal(err) } fmt.Println(ret) } // output: // 5000 // 300