rpc 全稱 Remote Procedure Call
遠端過程呼叫,即呼叫遠端方法。我們呼叫當前程式中的方法時很簡單,但是想要呼叫不同程式,甚至不同主機、不同語言中的方法時就需要藉助 rpc 來實現,下面我一步步實現一個簡單的 rpc 呼叫。
server 端註冊函式,執行並接收客戶端請求
func main() {
srv := NewServer()
srv.Register("fn", fn)
srv.Run()
}
//為了簡單,這裡只需要接收到訊息列印出就代表執行成功
func fn(args ...interface{}) {
fmt.println(args)
}
定義請求格式
type rpcData struct {
Name string //函式名
Args []interface{} //引數
}
server 執行起來後,接收 socket 請求,解析訊息呼叫已註冊的函式
//server結構體
type server struct {
conn net.Conn //socket連線
maps map[string]reflect.Value //函式字典
}
//建構函式
func NewServer() *server {
return &server{
maps: make(map[string]reflect.Value),
}
}
//註冊函式
func (s *server) Register(fname string, fun interface{}) {
if _, ok := s.maps[fname]; !ok {
s.maps[fname] = reflect.ValueOf(fun)
}
}
//執行一個socket接收請求
func (s *server) Run() {
listen, err := net.Listen("tcp4", ":3001")
if err != nil {
panic(err)
}
for {
s.conn, err = listen.Accept()
if err != nil {
continue
}
go s.handleConnect()
}
}
處理請求時,這裡為了簡單我使用 json 解析,同時需要定義一個簡單的協議:客戶端傳送時,前4個位元組放置訊息長度,這樣服務端接收到時就能知道訊息的長度,從而正常解碼訊息
func (s *server) handleConnect() {
for {
header := make([]byte, 4)
if _, err := s.conn.Read(header); err != nil {
continue
}
bodyLen := binary.BigEndian.Uint32(header)
body := make([]byte, int(bodyLen))
if _, err := s.conn.Read(body); err != nil {
continue
}
var req rpcData
if err := json.Unmarshal(body, &req); err != nil {
continue
}
inArgs := make([]reflect.Value, len(req.Args))
for i := range req.Args {
inArgs[i] = reflect.ValueOf(req.Args[i])
}
fn := s.maps[req.Name]
fn.Call(inArgs)
}
}
client 端只需呼叫函式,通過網路傳送請求
func main() {
var req = rpcData{"fn", []interface{}{1, "aaa"}}
rpcCall(req)
}
func rpcCall(data rpcData) {
conn, err := net.Dial("tcp4", "127.0.0.1:3001")
if err != nil {
panic(err)
}
req, err := json.Marshal(data)
if err != nil {
panic(err)
}
buf := make([]byte, 4+len(req))
binary.BigEndian.PutUint32(buf[:4], uint32(len(req)))
copy(buf[4:], req)
_, err = conn.Write(buf)
if err != nil {
panic(err)
}
}
測試時,首先執行 server,然後執行 client,只要看到正確的列印就代表呼叫成功,這就是一個最簡單(簡陋)的 rpc 了。
當我們使用 grpc 這些 rpc 框架時,就可以不用自己實現訊息編碼解碼、socket連線這些細節,專注於業務邏輯,而且更為可靠。