Go語言下RPC的開發
阿新 • • 發佈:2022-02-09
一、rpc之HelloWorld
Go語言的rpc包的路徑為net/rpc:
1、server.go
package main import ( "net" "net/rpc" ) type HelloWorldService struct { } func (s *HelloWorldService) HelloWorld(request string, response *string) error { *response = "hello" + request return nil } func main() { _ = rpc.RegisterName("HelloWorldService", &HelloWorldService{}) listener, err := net.Listen("tcp", ":8000") if err != nil { panic("監聽埠失敗!") } conn, err := listener.Accept() if err != nil { panic("建立連線失敗!") } rpc.ServeConn(conn) }
- HelloWorld方法需要滿足Go語言RPC規則,接收兩個引數,第二個引數是指標型別,並且返回一個error型別,同時必須是公開方法
- HelloWorldService型別的物件註冊到RPC服務
- rpc.register函式呼叫會將物件型別中滿足RPC規則的物件方法註冊為RPC函式,所有註冊的方法會放置在HelloWorldService服務空間下
- 建立TCP連結,通過rpc.ServerConn函式在該連結上提供RPC服務
1、client.go
package main import ( "fmt" "log" "net/rpc" ) func main() { client, err := rpc.Dial("tcp", "127.0.0.1:8000") if err != nil { log.Fatal("dial", err) } var response string err = client.Call("HelloWorldService.HelloWorld", "world", &response)if err != nil { log.Fatal("caller", err) } fmt.Println(response) }
- 通過rpc.Dial進行RPC撥號服務
- 通過client.Call呼叫具體方法
- 方法中第一個引數是RPC服務名稱和方法名稱,第二個和第三個引數是方法中傳入的實參
二、基於json實現RPC
上述RPC預設採用的是Go語言特有的gob編碼,所以其它語言呼叫Go語言實現的RPC服務相對困難。比較常見的有基於json實現的RPC服務,所以如果使用json來替換gob將會使其通用性變的更強。在Go語言中可以通過net/rpc/jsonrpc來基於json實現RPC,實現跨語言呼叫。
1、server.go
package main import ( "net" "net/rpc" "net/rpc/jsonrpc" ) type HelloWorldService struct { } func (s *HelloWorldService) HelloWorld(request string, response *string) error { *response = "hello" + request return nil } func main() { _ = rpc.RegisterName("HelloWorldService", &HelloWorldService{}) listener, err := net.Listen("tcp", ":8000") if err != nil { panic("監聽埠失敗!") } // 不斷的接收新的請求 for { conn, err := listener.Accept() if err != nil { panic("建立連線失敗!") } go rpc.ServeCodec(jsonrpc.NewServerCodec(conn)) // 啟動協程處理請求 } }
這與之前相比使用rpc.ServeCodec函式替代了rpc.ServeConn函式,傳入符合json編碼的引數。
2、client.go
package main import ( "fmt" "log" "net" "net/rpc" "net/rpc/jsonrpc" ) func main() { conn, err := net.Dial("tcp", "127.0.0.1:8000") if err != nil { log.Fatal("dial", err) } client := rpc.NewClientWithCodec(jsonrpc.NewClientCodec(conn)) var response string err = client.Call("HelloWorldService.HelloWorld", "world", &response) if err != nil { log.Fatal("caller", err) } fmt.Println(response) }
這與之前相比通過net進行撥號建立連線,然後通過NewClientWithCodec函式傳入符合json編碼的引數。
既然使用的是json進行編、解碼,那麼就具備一定的通用性,可以使用其它語言來進行呼叫,比如使用Python客戶端進行呼叫,但是Go的RPC監聽的是TCP連線,如果使用Python中的requests包是行不通的,它使用的是HTTP協議,會攜帶很多比如請求頭之類的多餘資訊,所以使用socket程式設計傳送請求:
3、client.py
import json import socket # 傳送的資料格式必須滿足這樣的 """ method: 服務名稱、方法名稱 result:返回的結果 id:隨意指定一個值,如果不指定,返回值id為None """ request = { "method": "HelloWorldService.HelloWorld", "params": ["bily"], "id": 0 } client = socket.create_connection(("127.0.0.1", 8000)) client.sendall(json.dumps(request).encode()) # 設定一次性接收的資料大小 response = client.recv(4096) response = json.loads(response.decode()) print(response) # 關閉連線 client.close()
請求結果:
{'id': 0, 'result': 'hellobily', 'error': None}
三、基於http實現RPC
1、server.go
package main import ( "io" "net/http" "net/rpc" "net/rpc/jsonrpc" ) type HelloWorldService struct { } func (s *HelloWorldService) HelloWorld(request string, response *string) error { *response = "hello" + request return nil } func main() { _ = rpc.RegisterName("HelloWorldService", new(HelloWorldService)) http.HandleFunc("/jsonrpc", func(writer http.ResponseWriter, request *http.Request) { var conn io.ReadWriteCloser = struct { io.Writer io.ReadCloser }{ ReadCloser: request.Body, Writer: writer, } _ = rpc.ServeRequest(jsonrpc.NewServerCodec(conn)) }) _ = http.ListenAndServe(":8000", nil) }
2、client.go
import requests request = { "method": "HelloWorldService.HelloWorld", "params": ["karry"], "id": 0 } res = requests.post("http://127.0.0.1:8000/jsonrpc", json=request) print(res.text)
四、封裝代理
在之前的呼叫中,存在一些明顯的問題:
- 服務端中,業務程式碼與RPC通訊底層混在一起,需要一種結構層次,將其分離
- 客戶端中,每次呼叫都需要知道業務端的服務名稱與方法名稱,將其單獨封裝
這樣就引出服務端代理Server Stub和客戶端代理Client Stub:
1、目錄結構
├─client
│ client.go
│
├─client_stub
│ client_stub.go
│
├─handler
│ handler.go
│
├─server
│ server.go
│
└─server_stub
server_stub.go
2、client.go
package main import ( "fmt" "go_rpc_project/stub_rpc/client_stub" ) func main() { // 建立連線 client := client_stub.NewHelloWorldServiceClient("tcp", "127.0.0.1:8000") // 呼叫業務函式HelloWorld var response string _ = client.HelloWorld("harry", &response) fmt.Println(response) }
3、client_stub.go
package client_stub import ( "go_rpc_project/stub_rpc/handler" "log" "net/rpc" ) type HelloWorldServiceStub struct { *rpc.Client } func NewHelloWorldServiceClient(protcol string, address string) HelloWorldServiceStub { conn, err := rpc.Dial(protcol, address) if err != nil { log.Fatal("撥號錯誤", err) } return HelloWorldServiceStub{conn} } func (c *HelloWorldServiceStub) HelloWorld(request string, response *string) error { err := c.Call(handler.HelloWorldServiceName+".HelloWorld", request, response) if err != nil { log.Fatal("呼叫服務失敗", err) } return nil }
4、handler.go
package handler const HelloWorldServiceName = "handler/HelloWorldService" type HelloWorldService struct { } func (s *HelloWorldService) HelloWorld(request string, response *string) error { *response = "hello" + request return nil }
5、server.go
package main import ( "go_rpc_project/stub_rpc/handler" "go_rpc_project/stub_rpc/server_stub" "net" "net/rpc" ) func main() { // 例項化一個server listener, _ := net.Listen("tcp", ":8000") // 註冊,將業務邏輯程式碼註冊到代理中 _ = server_stub.RegisterServicer(&handler.HelloWorldService{}) // 啟動服務 for { conn, _ := listener.Accept() go rpc.ServeConn(conn) } }
6、server_stub.go
package server_stub import ( "go_rpc_project/stub_rpc/handler" "net/rpc" ) type HelloWorldServicer interface { HelloWorld(request string, response *string) error } func RegisterServicer(srv HelloWorldServicer) error { return rpc.RegisterName(handler.HelloWorldServiceName, srv) }作者:iveBoy 出處:http://www.cnblogs.com/shenjianping/ 本文版權歸作者和部落格園共有,歡迎轉載,但未經作者同意必須在文章頁面給出原文連線,否則保留追究法律責任的權利。