国产亚洲精品福利在线无卡一,国产精久久一区二区三区,亚洲精品无码国模,精品久久久久久无码专区不卡

當前位置: 首頁 > news >正文

做外國網(wǎng)站買域名推廣賺錢一個2元

做外國網(wǎng)站買域名,推廣賺錢一個2元,學做面包網(wǎng)站,電商美工培訓機構文章目錄 消息的序列化與反序列化通信過程服務端的實現(xiàn)main 函數(shù)(一個簡易的客戶端) 本文代碼地址: 本文是7天用Go從零實現(xiàn)RPC框架GeeRPC的第一篇。 使用 encoding/gob 實現(xiàn)消息的編解碼(序列化與反序列化)實現(xiàn)一個簡易的服務端,僅接受消息&#xff0c…

文章目錄

  • 消息的序列化與反序列化
  • 通信過程
  • 服務端的實現(xiàn)
  • main 函數(shù)(一個簡易的客戶端)

本文代碼地址:

本文是7天用Go從零實現(xiàn)RPC框架GeeRPC的第一篇。

  • 使用 encoding/gob 實現(xiàn)消息的編解碼(序列化與反序列化)
  • 實現(xiàn)一個簡易的服務端,僅接受消息,不處理,代碼約 200

消息的序列化與反序列化

一個典型的 RPC 調(diào)用如下:

err = client.Call("Arith.Multiply", args, &reply)

客戶端發(fā)送的請求包括服務名 Arith,方法名 Multiply,參數(shù) args 三個,服務端的響應包括錯誤 error,返回值 reply 2 個。我們將請求和響應中的參數(shù)和返回值抽象為 body,剩余的信息放在 header 中,那么就可以抽象出數(shù)據(jù)結構 Header

day1-codec/codec/codec.go

package codecimport "io"type Header struct {ServiceMethod string // format "Service.Method"Seq           uint64 // sequence number chosen by clientError         string
}
  • ServiceMethod 是服務名和方法名,通常與 Go 語言中的結構體和方法相映射。
  • Seq 是請求的序號,也可以認為是某個請求的 ID,用來區(qū)分不同的請求。
  • Error 是錯誤信息,客戶端置為空,服務端如果如果發(fā)生錯誤,將錯誤信息置于 Error 中。

我們將和消息編解碼相關的代碼都放到 codec 子目錄中,在此之前,還需要在根目錄下使用 go mod init geerpc 初始化項目,方便后續(xù)子 package 之間的引用。

進一步,抽象出對消息體進行編解碼的接口 Codec,抽象出接口是為了實現(xiàn)不同的 Codec 實例:

type Codec interface {io.CloserReadHeader(*Header) errorReadBody(interface{}) errorWrite(*Header, interface{}) error
}

緊接著,抽象出 Codec 的構造函數(shù),客戶端和服務端可以通過 CodecType 得到構造函數(shù),從而創(chuàng)建 Codec 實例。這部分代碼和工廠模式類似,與工廠模式不同的是,返回的是構造函數(shù),而非實例。

type NewCodecFunc func(io.ReadWriteCloser) Codectype Type stringconst (GobType  Type = "application/gob"JsonType Type = "application/json" // not implemented
)var NewCodecFuncMap map[Type]NewCodecFuncfunc init() {NewCodecFuncMap = make(map[Type]NewCodecFunc)NewCodecFuncMap[GobType] = NewGobCodec
}

我們定義了 2CodecGobJson,但是實際代碼中只實現(xiàn)了 Gob 一種,事實上,2 者的實現(xiàn)非常接近,甚至只需要把 gob 換成 json 即可。

首先定義 GobCodec 結構體,這個結構體由四部分構成,conn 是由構建函數(shù)傳入,通常是通過 TCP 或者 Unix 建立 socket 時得到的鏈接實例,dec enc 對應 gobDecoderEncoderbuf 是為了防止阻塞而創(chuàng)建的帶緩沖的 Writer,一般這么做能提升性能。

day1-codec/codec/gob.go

package codecimport ("bufio""encoding/gob""io""log"
)type GobCodec struct {conn io.ReadWriteCloserbuf  *bufio.Writerdec  *gob.Decoderenc  *gob.Encoder
}var _ Codec = (*GobCodec)(nil)func NewGobCodec(conn io.ReadWriteCloser) Codec {buf := bufio.NewWriter(conn)return &GobCodec{conn: conn,buf:  buf,dec:  gob.NewDecoder(conn),enc:  gob.NewEncoder(buf),}
}

Go 語言中,json.NewDecoder json.Unmarshal 都用于將 JSON 數(shù)據(jù)解析為 Go中的數(shù)據(jù)結構,但它們有一些區(qū)別:

  • json.NewDecoder 是通過創(chuàng)建一個 Decoder 對象,從一個 io.Reader(如os.Stdin、文件、網(wǎng)絡連接等)中讀取 JSON 數(shù)據(jù)并進行解碼。
  • json.Unmarshal 則是直接將 JSON 數(shù)據(jù)(以字節(jié)切片 []byte 或者字符串的形式)解析并映射到指定的數(shù)據(jù)結構。

使用場景上,如果數(shù)據(jù)是從一個輸入流中讀取,通常使用 json.NewDecoder;如果已經(jīng)有了 JSON 數(shù)據(jù)的字節(jié)切片或字符串,使用json.Unmarshal 會更方便。json.NewEncoder json.Marshal 同理。

接著實現(xiàn) ReadHeaderReadBody、WriteClose 方法。

func (c *GobCodec) ReadHeader(h *Header) error {return c.dec.Decode(h)
}func (c *GobCodec) ReadBody(body interface{}) error {return c.dec.Decode(body)
}func (c *GobCodec) Write(h *Header, body interface{}) (err error) {defer func() {_ = c.buf.Flush()if err != nil {_ = c.Close()}}()if err := c.enc.Encode(h); err != nil {log.Println("rpc codec: gob error encoding header:", err)return err}if err := c.enc.Encode(body); err != nil {log.Println("rpc codec: gob error encoding body:", err)return err}return nil
}func (c *GobCodec) Close() error {return c.conn.Close()
}

通信過程

客戶端與服務端的通信需要協(xié)商一些內(nèi)容,例如 HTTP 報文,分為headerbody 2 部分,body 的格式和長度通過 header 中的 Content-TypeContent-Length 指定,服務端通過解析 header 就能夠知道如何從 body 中讀取需要的信息。對于 RPC 協(xié)議來說,這部分協(xié)商是需要自主設計的。為了提升性能,一般在報文的最開始會規(guī)劃固定的字節(jié),來協(xié)商相關的信息。比如第1個字節(jié)用來表示序列化方式,第2個字節(jié)表示壓縮方式,第3-6字節(jié)表示 header 的長度,7-10 字節(jié)表示 body 的長度。

對于 GeeRPC 來說,目前需要協(xié)商的唯一一項內(nèi)容是消息的編解碼方式。我們將這部分信息,放到結構體 Option 中承載。目前,已經(jīng)進入到服務端的實現(xiàn)階段了。

day1-codec/server.go

package geerpcconst MagicNumber = 0x3bef5ctype Option struct {MagicNumber int        // MagicNumber marks this's a geerpc requestCodecType   codec.Type // client may choose different Codec to encode body
}var DefaultOption = &Option{MagicNumber: MagicNumber,CodecType:   codec.GobType,
}

一般來說,涉及協(xié)議協(xié)商的這部分信息,需要設計固定的字節(jié)來傳輸?shù)?。但是為了實現(xiàn)上更簡單,GeeRPC 客戶端固定采用 JSON 編碼 Option,后續(xù)的 headerbody 的編碼方式由 Option 中的 CodeType 指定,服務端首先使用 JSON 解碼 Option,然后通過 OptionCodeType 解碼剩余的內(nèi)容。即報文將以這樣的形式發(fā)送:

| Option{MagicNumber: xxx, CodecType: xxx} | Header{ServiceMethod ...} | Body interface{} |
| <------      固定 JSON 編碼      ------>  | <-------   編碼方式由 CodeType 決定   ------->|

在一次連接中,Option 固定在報文的最開始,HeaderBody 可以有多個,即報文可能是這樣的。

| Option | Header1 | Body1 | Header2 | Body2 | ...

服務端的實現(xiàn)

通信過程已經(jīng)定義清楚了,那么服務端的實現(xiàn)就比較直接了。

day1-codec/server.go

// Server represents an RPC Server.
type Server struct{}// NewServer returns a new Server.
func NewServer() *Server {return &Server{}
}// DefaultServer is the default instance of *Server.
var DefaultServer = NewServer()// Accept accepts connections on the listener and serves requests
// for each incoming connection.
func (server *Server) Accept(lis net.Listener) {for {conn, err := lis.Accept()if err != nil {log.Println("rpc server: accept error:", err)return}go server.ServeConn(conn)}
}// Accept accepts connections on the listener and serves requests
// for each incoming connection.
func Accept(lis net.Listener) { DefaultServer.Accept(lis) }
  • 首先定義了結構體 Server,沒有任何的成員字段。
  • 實現(xiàn)了 Accept 方式,net.Listener 作為參數(shù),for 循環(huán)等待 socket 連接建立,并開啟子協(xié)程處理,處理過程交給了 ServerConn 方法。
  • DefaultServer 是一個默認的 Server 實例,主要為了用戶使用方便。

如果想啟動服務,過程是非常簡單的,傳入 listener 即可,tcp 協(xié)議和 unix 協(xié)議都支持。

lis, _ := net.Listen("tcp", ":9999")
geerpc.Accept(lis)

ServeConn 的實現(xiàn)就和之前討論的通信過程緊密相關了,首先使用 json.NewDecoder 反序列化得到 Option 實例,檢查 MagicNumber CodeType 的值是否正確。然后根據(jù) CodeType 得到對應的消息編解碼器,接下來的處理交給 serverCodec。

// ServeConn runs the server on a single connection.
// ServeConn blocks, serving the connection until the client hangs up.
func (server *Server) ServeConn(conn io.ReadWriteCloser) {defer func() { _ = conn.Close() }()var opt Optionif err := json.NewDecoder(conn).Decode(&opt); err != nil {log.Println("rpc server: options error: ", err)return}if opt.MagicNumber != MagicNumber {log.Printf("rpc server: invalid magic number %x", opt.MagicNumber)return}f := codec.NewCodecFuncMap[opt.CodecType]if f == nil {log.Printf("rpc server: invalid codec type %s", opt.CodecType)return}server.serveCodec(f(conn))
}// invalidRequest is a placeholder for response argv when error occurs
var invalidRequest = struct{}{}func (server *Server) serveCodec(cc codec.Codec) {sending := new(sync.Mutex) // make sure to send a complete responsewg := new(sync.WaitGroup)  // wait until all request are handledfor {req, err := server.readRequest(cc)if err != nil {if req == nil {break // it's not possible to recover, so close the connection}req.h.Error = err.Error()server.sendResponse(cc, req.h, invalidRequest, sending)continue}wg.Add(1)go server.handleRequest(cc, req, sending, wg)}wg.Wait()_ = cc.Close()
}

serveCodec 的過程非常簡單。主要包含三個階段

  • 讀取請求 readRequest
  • 處理請求 handleRequest
  • 回復請求 sendResponse

之前提到過,在一次連接中,允許接收多個請求,即多個 request headerrequest body,因此這里使用了for無限制地等待請求的到來,直到發(fā)生錯誤(例如連接被關閉,接收到的報文有問題等),這里需要注意的點有三個:

  • handleRequest 使用了協(xié)程并發(fā)執(zhí)行請求。
  • 處理請求是并發(fā)的,但是回復請求的報文必須是逐個發(fā)送的,并發(fā)容易導致多個回復報文交織在一起,客戶端無法解析。在這里使用鎖(sending)保證。
  • 盡力而為,只有在 header 解析失敗時,才終止循環(huán)。
// request stores all information of a call
type request struct {h            *codec.Header // header of requestargv, replyv reflect.Value // argv and replyv of request
}func (server *Server) readRequestHeader(cc codec.Codec) (*codec.Header, error) {var h codec.Headerif err := cc.ReadHeader(&h); err != nil {if err != io.EOF && err != io.ErrUnexpectedEOF {log.Println("rpc server: read header error:", err)}return nil, err}return &h, nil
}func (server *Server) readRequest(cc codec.Codec) (*request, error) {h, err := server.readRequestHeader(cc)if err != nil {return nil, err}req := &request{h: h}// TODO: now we don't know the type of request argv// day 1, just suppose it's stringreq.argv = reflect.New(reflect.TypeOf(""))if err = cc.ReadBody(req.argv.Interface()); err != nil {log.Println("rpc server: read argv err:", err)}return req, nil
}func (server *Server) sendResponse(cc codec.Codec, h *codec.Header, body interface{}, sending *sync.Mutex) {sending.Lock()defer sending.Unlock()if err := cc.Write(h, body); err != nil {log.Println("rpc server: write response error:", err)}
}func (server *Server) handleRequest(cc codec.Codec, req *request, sending *sync.Mutex, wg *sync.WaitGroup) {// TODO, should call registered rpc methods to get the right replyv// day 1, just print argv and send a hello messagedefer wg.Done()log.Println(req.h, req.argv.Elem())req.replyv = reflect.ValueOf(fmt.Sprintf("geerpc resp %d", req.h.Seq))server.sendResponse(cc, req.h, req.replyv.Interface(), sending)
}

目前還不能判斷 body 的類型,因此在readRequesthandleRequest 中,day1 body 作為字符串處理。接收到請求,打印 header,并回復 geerpc resp ${req.h.Seq}。這一部分后續(xù)再實現(xiàn)。

main 函數(shù)(一個簡易的客戶端)

day1 的內(nèi)容就到此為止了,在這里我們已經(jīng)實現(xiàn)了一個消息的編解碼器 GobCodec,并且客戶端與服務端實現(xiàn)了簡單的協(xié)議交換(protocol exchange),即允許客戶端使用不同的編碼方式。同時實現(xiàn)了服務端的雛形,建立連接,讀取、處理并回復客戶端的請求。

接下來,我們就在 main 函數(shù)中看看如何使用剛實現(xiàn)的 GeeRPC 吧。

day1-codec/main/main.go

package mainimport ("encoding/json""fmt""geerpc""geerpc/codec""log""net""time"
)func startServer(addr chan string) {// pick a free portl, err := net.Listen("tcp", ":0")if err != nil {log.Fatal("network error:", err)}log.Println("start rpc server on", l.Addr())addr <- l.Addr().String()geerpc.Accept(l)
}func main() {addr := make(chan string)go startServer(addr)// in fact, following code is like a simple geerpc clientconn, _ := net.Dial("tcp", <-addr)defer func() { _ = conn.Close() }()time.Sleep(time.Second)// send options_ = json.NewEncoder(conn).Encode(geerpc.DefaultOption)cc := codec.NewGobCodec(conn)// send request & receive responsefor i := 0; i < 5; i++ {h := &codec.Header{ServiceMethod: "Foo.Sum",Seq:           uint64(i),}_ = cc.Write(h, fmt.Sprintf("geerpc req %d", h.Seq))_ = cc.ReadHeader(h)var reply string_ = cc.ReadBody(&reply)log.Println("reply:", reply)}
}
  • startServer 中使用了信道 addr,確保服務端端口監(jiān)聽成功,客戶端再發(fā)起請求。
  • 客戶端首先發(fā)送 Option 進行協(xié)議交換,接下來發(fā)送消息頭 h := &codec.Header{},和消息體 geerpc req ${h.Seq}。
  • 最后解析服務端的響應 reply,并打印出來。

執(zhí)行結果如下:

start rpc server on [::]:63662
&{Foo.Sum 0 } geerpc req 0
reply: geerpc resp 0
&{Foo.Sum 1 } geerpc req 1
reply: geerpc resp 1
&{Foo.Sum 2 } geerpc req 2
reply: geerpc resp 2
&{Foo.Sum 3 } geerpc req 3
reply: geerpc resp 3
&{Foo.Sum 4 } geerpc req 4
reply: geerpc resp 4

原文鏈接:https://geektutu.com/post/geerpc-day1.html

http://m.aloenet.com.cn/news/32793.html

相關文章:

  • 網(wǎng)站建設后期維護小魔仙網(wǎng)絡廣告宣傳平臺
  • 企業(yè)網(wǎng)絡營銷策劃方案范文免費seo教程資源
  • wordpress 添加搜索引擎北京網(wǎng)絡seo
  • 三合一網(wǎng)站建設方案深圳市網(wǎng)絡營銷推廣服務公司
  • b2b網(wǎng)站建設開發(fā)china東莞seo
  • 網(wǎng)站的服務有哪些seo外鏈工具有用嗎
  • 南陽網(wǎng)站建設大旗電商電商網(wǎng)站訂煙
  • wordpress投訴功能qq群怎么優(yōu)化排名靠前
  • 多媒體網(wǎng)站開發(fā)實驗報告做企業(yè)網(wǎng)站建設的公司
  • 網(wǎng)頁搜索工具新站seo優(yōu)化快速上排名
  • wordpress推廣升級vipseo做什么網(wǎng)站賺錢
  • 學網(wǎng)站建設怎么樣tool站長工具
  • 網(wǎng)站的懲罰期要怎么做廣告安裝接單app
  • 國外設計網(wǎng)站dooor企業(yè)營銷策劃書模板
  • 網(wǎng)站中qq跳轉怎么做的推廣公司經(jīng)營范圍
  • 網(wǎng)站文化建設石家莊百度seo代理
  • 設計師論壇seo包年優(yōu)化
  • 做網(wǎng)站運營買什么電腦揚州seo推廣
  • 銅仁建設集團招聘信息網(wǎng)站seo快速優(yōu)化軟件網(wǎng)站
  • 做網(wǎng)站接項目seo網(wǎng)站是什么意思
  • 個人作品展示網(wǎng)站模板營銷型網(wǎng)站策劃書
  • 做網(wǎng)站服務器在哪買微商引流人脈推廣軟件
  • 做一個網(wǎng)站維護多少錢快速排名新
  • 山西省住房建設廳網(wǎng)站房屋建筑定額北京seo關鍵詞優(yōu)化外包
  • 公司網(wǎng)站怎么做備案東莞疫情最新情況
  • 建站知識互聯(lián)網(wǎng)整合營銷推廣
  • 各大網(wǎng)站圖片電商營銷策劃方案范文
  • 什么是網(wǎng)站評價上海seo推廣服務
  • 深圳58同城網(wǎng)站建設百度廣告代理商加盟
  • 最新經(jīng)濟新聞頭條新聞廈門seo怎么做