Go標準包——net/rpc包的使用

bytecc發表於2021-10-20

rpc原理很簡單,客戶端把請求方法,引數等資訊編碼後傳給服務端,服務端反解碼後,找到對應的方法執行,並且把結果編碼後返回給客戶端。

服務端

重要結構
type Server struct {
  serviceMap sync.Map   // map[string]*service 註冊的服務存放
  reqLock    sync.Mutex // protects freeReq
  freeReq    *Request
  respLock   sync.Mutex // protects freeResp
  freeResp   *Response
}
type gobServerCodec struct { //使用gob實現編碼器
  rwc    io.ReadWriteCloser
  dec    *gob.Decoder
  enc    *gob.Encoder
  encBuf *bufio.Writer
  closed bool
}
type ServerCodec interface { //編碼器介面
  ReadRequestHeader(*Request) error
  ReadRequestBody(interface{}) error
  WriteResponse(*Response, interface{}) error
  // Close can be called multiple times and must be idempotent.
  Close() error
}

整體來說,Server處理ServerCodec,其他編解碼封裝conn實現ServerCodec,也是一樣的。

server.go

type HelloService struct{}
func (p *HelloService) Hello(request common.Request, response *common.Response) error {
  return nil
}func main() {
 //1.註冊服務,寫到serviceMap中
  rpc.RegisterName("HelloService", new(HelloService))//把服務註冊到rpc.Server.serviceMap
  listener, _ := net.Listen("tcp", ":1234")
  for {
  conn, err := listener.Accept()
  if err != nil {
      log.Fatal("Accept error:", err)
  }
  go rpc.ServeConn(conn)
  }
}
1.封裝ServerCodec

conn會用gob包封裝,標準庫gob是golang提供的“私有”的編解碼方式,它的效率會比json,xml等更高,特別適合在Go語言程式間傳遞資料。

func (server *Server) ServeConn(conn io.ReadWriteCloser) {
  buf := bufio.NewWriter(conn)
  srv := &gobServerCodec{
  rwc:    conn,
  dec:    gob.NewDecoder(conn),//封裝成解碼器,從conn讀取資料並解碼
  enc:    gob.NewEncoder(buf),//封裝成編碼器,編碼後寫入conn
  encBuf: buf,
  }
  server.ServeCodec(srv)
}
2.讀取請求並呼叫對應的service
func (server *Server) ServeCodec(codec ServerCodec) {
  sending := new(sync.Mutex)
  wg := new(sync.WaitGroup)
  for {
  service, mtype, req, argv, replyv, keepReading, err := server.readRequest(codec)//讀取request,service
  wg.Add(1)
  go service.call(server, sending, wg, mtype, req, argv, replyv, codec)//呼叫service
  }
  wg.Wait()
  codec.Close()
}

客戶端

重要結構
type Client struct {
  codec ClientCodec //編解碼封裝的conn
  reqMutex sync.Mutex // protects following
  request  Request
  mutex    sync.Mutex // protects following
  seq      uint64
  pending  map[uint64]*Call
  closing  bool // user has called Close
  shutdown bool // server has told us to stop
}
type ClientCodec interface {
  WriteRequest(*Request, interface{}) error
  ReadResponseHeader(*Response) error
  ReadResponseBody(interface{}) error
  Close() error
}
type gobClientCodec struct { //gob實現的ClientCodec
  rwc    io.ReadWriteCloser
  dec    *gob.Decoder
  enc    *gob.Encoder
  encBuf *bufio.Writer
}

client.go

func main() {
  rpcClient, err := rpc.Dial("tcp", "localhost:1234")
  if err != nil {
  fmt.Println(err)
  }
  var reply common.Response
  var req common.Request
  req.UserId = 11
 //向服務端傳送"HelloService.Hello",req, 從服務端讀到reply
  err = rpcClient.Call("HelloService.Hello", req, &reply)
  if err != nil {
  fmt.Println(err)
  }
  fmt.Print(reply)
}
1.生成client

使用conn即可生成client,所有如果使用tls連線,可以自定義client

func Dial(network, address string) (*Client, error) {
conn, err := net.Dial(network, address) 
  if err != nil {
  return nil, err
  }
  return NewClient(conn), nil
}
func NewClient(conn io.ReadWriteCloser) *Client {
  encBuf := bufio.NewWriter(conn)
  client := &gobClientCodec{conn, gob.NewDecoder(conn), gob.NewEncoder(encBuf), encBuf}//使用gob編解碼
  return NewClientWithCodec(client)
}
2.發起呼叫
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
}
func (client *Client) Go(serviceMethod string, args interface{}, reply interface{}, done chan *Call) *Call {
  call := new(Call)
  call.ServiceMethod = serviceMethod
  call.Args = args
  call.Reply = reply
 done = make(chan *Call, 10) // buffered.
  call.Done = done
  client.send(call)
  return call
}

編解碼

官方庫提供了encoding/gob,encoding/json兩種編碼模式,把io.ReadWrite分別封裝為編碼器,解碼器。

//gob包,需要一個io.ReadWrite引數,分別包裝一層實現編碼後寫入,解碼後讀出

enc := gob.NewEncoder(conn)
enc.Encode(data) //相當於編碼後conn.write(data)
​
dec := gob.NewDecoder(conn)
dec.Decode(&m) //解碼到變數m//也可以使用json編解碼
enc := json.NewEncoder(conn)
json.Encode(data) //相當於編碼後conn.write(data)
dec := json.NewDecoder(conn)
json.Decode(&m) //解碼到變數m, 從conn讀取資料後解碼到變數m
gob包使用案例

底層使用了go的反射功能,所以這種方式編解碼只能在go語言中使用

func main() {
  info := map[string]string{
  "name":    "C語言中文網",
  "website": "http://c.biancheng.net/golang/",
  }
  name := "demo.gob"
  EncodeToByte(name, info)
  DecodeFromFile(name)
}
func EncodeToByte(name string, data interface{}) {
  fd, _ := os.OpenFile(name, os.O_RDWR|os.O_CREATE, 0777)
  defer fd.Close()
  enc := gob.NewEncoder(fd)//生成編碼器
  if err := enc.Encode(data); err != nil {//把資料編碼後寫入到檔案中
  fmt.Println(err)
  }
}
func DecodeFromFile(name string) {
  var m map[string]string
  fd, _ := os.OpenFile(name, os.O_RDWR|os.O_CREATE, 0777)
  defer fd.Close()
  D := gob.NewDecoder(fd) //生成解碼器
  D.Decode(&m)//從檔案讀取資料,解碼到變數中
  fmt.Println(m)
}
使用json做編解碼

服務端

使用的也是go標準庫net/rpc/jsonrpc

func jsonServer() {
  rpc.RegisterName("HelloService", new(HelloService)) //把服務註冊到rpc.Server.serviceMap
  listener, _ := net.Listen("tcp", ":1234")
  for {
  conn, err := listener.Accept()
  if err != nil {
      log.Fatal("Accept error:", err)
  }
  go rpc.ServeCodec(jsonrpc.NewServerCodec(conn))
  }
}

客戶端

func jsonClient() {
  conn, err := net.Dial("tcp", "localhost:1234")
  if err != nil {
  log.Fatal("net.Dial:", err)
  }
  client := rpc.NewClientWithCodec(jsonrpc.NewClientCodec(conn))
  var reply string
  err = client.Call("HelloService.Hello", "hello", &reply)
  if err != nil {
  log.Fatal(err)
  }
  fmt.Println(reply)
}

http協議實現rpc

service.go

func HttpRpcServer() {
  rpc.RegisterName("HelloService", new(HelloService)) //把服務註冊到rpc.Server.serviceMap
  rpc.HandleHTTP()//會註冊一個預設路徑到http,Handle註冊為rpcServer
  if err := http.ListenAndServe(":1234", nil); err != nil {
  log.Fatal("Error serving: ", err)
  }
}

client.go

func HttpRpcClient() {
 rpcClient, _ := rpc.DialHTTP("tcp", ":1234") //訪問服務端註冊的預設路徑
  var reply common.Response
  var req common.Request
  req.UserId = 11
  rpcClient.Call("HelloService.Hello", req, &reply)  
  fmt.Print(reply)
}

客戶端rpc非同步呼叫

client.go

func HttpRpcClient() {
  rpcClient, err := rpc.DialHTTP("tcp", ":1234")
  if err != nil {
  panic(err)
  }
  var reply common.Response
  var req common.Request
  req.UserId = 11  
  async := rpcClient.Go("HelloService.Hello", req, &reply, nil)
  <-async.Done //等待非同步返回結果,可以放到一個單獨協程等待,不用阻塞當前協程
  fmt.Print(reply)
}

有位大佬用go重新實現了net/rpc並增加了功能
7天用Go從零實現RPC框架GeeRPC

本作品採用《CC 協議》,轉載必須註明作者和本文連結
用過哪些工具?為啥用這個工具(速度快,支援高併發...)?底層如何實現的?

相關文章