客戶端:服務調用發起方,又稱之爲服務消費者 服務器:遠端計算機上運行的程序,其中包含客戶端要調用和訪問的方法 客戶端存根:存放服務器端的地址,端口消息,將客戶端的請求參數打包成網絡消息,發送給服務器方。接收服務器方返回的數據包。該段程序運行在客戶端。 服務端存根:接收客戶端發送的數據包,解析數據包,調用數據包,調用具體的服務方法,將調用結果打包發送給客戶端一方。該段程序運行在服務端。編程
工做過程:json
一、客戶端想要發起一個遠程過程調用,首先經過調用本地客戶端Stub程序的方式調用想要使用的功能方法名;服務器
二、客戶端Stub程序接收到了客戶端的功能調用請求,將客戶端請求調用的方法名,攜帶的參數等信息作序列化操做,並打包成數據包。網絡
三、客戶端Stub查找到遠程服務器程序的IP地址,調用Socket通訊協議,經過網絡發送給服務端。異步
四、服務端Stub程序接收到客戶端發送的數據包信息,並經過約定好的協議將數據進行反序列化,獲得請求的方法名和請求參數等信息。tcp
五、服務端Stub程序準備相關數據,調用本地Server對應的功能方法進行,並傳入相應的參數,進行業務處理。ide
六、服務端程序根據已有業務邏輯執行調用過程,待業務執行結束,將執行結果返回給服務端Stub程序。編碼
七、服務端Stub程序將程序調用結果按照約定的協議進行序列化,並經過網絡發送回客戶端Stub程序。代理
八、客戶端Stub程序接收到服務端Stub發送的返回數據,對數據進行反序列化操做,並將調用返回的數據傳遞給客戶端請求發起者。指針
九、客戶端請求發起者獲得調用結果,整個RPC調用過程結束。
RPC涉及到的相關技術 經過上文一系列的文字描述和講解,咱們已經瞭解了RPC的由來和RPC整個調用過程。咱們能夠看到RPC是一系列操做的集合,其中涉及到不少對數據的操做,以及網絡通訊。所以,咱們對RPC中涉及到的技術作一個總結和分析:
一、動態代理技術: 上文中咱們提到的Client Stub和Sever Stub程序,在具體的編碼和開發實踐過程當中,都是使用動態代理技術自動生成的一段程序。
二、序列化和反序列化: 在RPC調用的過程當中,咱們能夠看到數據須要在一臺機器上傳輸到另一臺機器上。在互聯網上,全部的數據都是以字節的形式進行傳輸的。而咱們在編程的過程當中,每每都是使用數據對象,所以想要在網絡上將數據對象和相關變量進行傳輸,就須要對數據對象作序列化和反序列化的操做。
序列化:把對象轉換爲字節序列的過程稱爲對象的序列化,也就是編碼的過程。
反序列化:把字節序列恢復爲對象的過程稱爲對象的反序列化,也就是解碼的過程。
服務定義及暴漏:
func (t *T) MethodName(request T1,response *T2) error 上述代碼是go語言官方給出的對外暴露的服務方法的定義標準,其中包含了主要的幾條規則,分別是: 一、對外暴露的方法有且只能有兩個參數,這個兩個參數只能是輸出類型或內建類型,兩種類型中的一種。 二、方法的第二個參數必須是指針類型。 三、方法的返回類型爲error。 四、方法的類型是可輸出的。 * 五、方法自己也是可輸出的。
type MathUtil struct{}//該方法向外暴露:提供計算圓形面積的服務 func (mu *MathUtil) CalculateCircleArea(req float32, resp *float32) error { *resp = math.Pi * req * req //圓形的面積 s = π * r * r return nil //返回類型 }
代碼講解: 在上述的案例中,咱們能夠看到: 一、Calculate方法是服務對象MathUtil向外提供的服務方法,該方法用於接收傳入的圓形半徑數據,計算圓形面積並返回。 二、第一個參數req表明的是調用者(client)傳遞提供的參數。 三、第二個參數resp表明要返回給調用者的計算結果,必須是指針類型。 四、正常狀況下,方法的返回值爲是error,爲nil。若是遇到異常或特殊狀況,則error將做爲一個字符串返回給調用者,此時,resp參數就不會再返回給調用者。
客戶端鏈接服務端:
client, err := rpc.DialHTTP("tcp", "localhost:8081") if err != nil { panic(err.Error()) }
遠端方法調用 客戶端成功鏈接服務端之後,就能夠經過方法調用調用服務端的方法,具體調用方法以下:
var req float32 //請求值 req = 3 var resp float32 //返回值 err = client.Call("MathUtil.CalculateCircleArea", req, &resp) if err != nil { panic(err.Error()) } fmt.Println(resp)
上述的調用方法核心在於client.Call方法的調用,該方法有三個參數,第一個參數表示要調用的遠端服務的方法名,第二個參數是調用時要傳入的參數,第三個參數是調用要接收的返回值。 上述的Call方法調用實現的方式是同步的調用,除此以外,還有一種異步的方式能夠實現調用。異步調用代碼實現以下:
var respSync *float32 //異步的調用方式 syncCall := client.Go("MathUtil.CalculateCircleArea", req, &respSync, nil) replayDone := <-syncCall.Done fmt.Println(replayDone) fmt.Println(*respSync)
代碼演示:
Go HTTPRPC
//server package main import ( "errors" "fmt" "log" "net/http" "net/rpc" ) //使用 rpc http實現簡單的 rpc操做 type Args struct { A, B int } type Math int func (m *Math) Multiply(args *Args, reply *int) error { *reply = args.A * args.B return nil } type Quotient struct { Quo, Rem int } func (m *Math) Divide(args *Args, quo *Quotient) error { if args.B == 0 { return errors.New("divide by zero") } quo.Quo = args.A / args.B quo.Rem = args.A % args.B return nil } func main() { math := new(Math) rpc.Register(math) //註冊rpc rpc.HandleHTTP() //使用http rpc fmt.Println("rpc http server runing ....") err := http.ListenAndServe(":1234", nil) if err != nil { log.Println(err.Error()) } } //client package main import ( "fmt" "log" "net/rpc" ) type Args struct { A, B int } type Quotient struct { Quo, Rem int } func main() { // fmt.Println(os.Args) // if len(os.Args) != 2 { // fmt.Println("usage:", os.Args[0], "server") // os.Exit(1) // } serverAddr := "127.0.0.1" client, err := rpc.DialHTTP("tcp", serverAddr+":1234") if err != nil { log.Println("dial err is ", err) } var reply int args := Args{1, 2} err = client.Call("Math.Multiply", args, &reply) //server method方法名要與server一致,寫錯後,將會提示服務不存在 if err != nil { log.Println("call err ", err) } fmt.Printf("Math Multiply: %d * %d =%d \n", args.A, args.B, reply) var quo Quotient err = client.Call("Math.Divide", args, &quo) if err != nil { log.Println("divide err ", err) } fmt.Printf("Math Divide: %d / %d =%d remainder %d \n", args.A, args.B, quo.Quo, quo.Rem) }
Go TcpRPC
//server package main import ( "errors" "log" "net" "net/rpc" ) type Math int type Args struct { A, B int } func (m *Math) Multiply(args *Args, reply *int) error { *reply = args.A * args.B return nil } type Quotient struct { Quo, Rem int } func (m *Math) Divide(args *Args, quo *Quotient) error { if args.B == 0 { return errors.New("divide by zero") } quo.Quo = args.A / args.B quo.Rem = args.A % args.B return nil } func main() { math := new(Math) rpc.Register(math) tcpAddr, err := net.ResolveTCPAddr("tcp", ":1234") if err != nil { log.Println("Resolve Ip addr err ", err) return } listen, err := net.ListenTCP("tcp", tcpAddr) if err != nil { log.Println("listen err is ", err) return } for { server, err := listen.Accept() if err != nil { log.Println("accept err is ", err) continue } rpc.ServeConn(server) } } //client package main import ( "fmt" "log" "net/rpc" ) type Args struct { A, B int } type Quotient struct { Quo, Rem int } func main() { client, err := rpc.Dial("tcp", "127.0.0.1"+":1234") if err != nil { log.Println("rpc dial err :", err) return } args := Args{1, 2} var reply int err = client.Call("Math.Multiply", args, &reply) if err != nil { log.Println("multiply err ", err) return } fmt.Printf("multiply %d * %d = %d \n", args.A, args.B, reply) var quo Quotient err = client.Call("Math.Divide", args, &quo) if err != nil { log.Println("Divite err is ", err) return } fmt.Printf("Divide %d / %d =%d ,rem %d", args.A, args.B, quo.Quo, quo.Rem) }
Go JSONRPC
//server package main import ( "net/rpc" "net/rpc/jsonrpc" "log" "net" "errors" ) type Math int type Args struct { A, B int } type Quotient struct { Quo, Rem int } func (m *Math)Multiply(args Args,reply *int) error{ *reply=args.A*args.B return nil } func (m *Math)Divide(args Args,quo *Quotient) error{ if args.B==0{ return errors.New("divide by zreo") } quo.Quo=args.A/args.B quo.Rem=args.A%args.B return nil } func main(){ math:=new(Math) rpc.Register(math) listenAddr,err:=net.ResolveTCPAddr("tcp",":1234") if err!=nil{ log.Println("resove ip addr err is ",err) return } listen,err:=net.ListenTCP("tcp",listenAddr) if err!=nil{ log.Println("listenTcp err ",err) return } for{ conn,err:=listen.Accept() if err!=nil{ log.Println("accept err is ",err) return } jsonrpc.ServeConn(conn) } } //client package main import ( "fmt" "log" "net/rpc/jsonrpc" ) type Args struct { A, B int } type Quotient struct { Quo, Rem int } func main() { client, err := jsonrpc.Dial("tcp", "127.0.0.1"+":1234") if err != nil { log.Println("rpc dial err :", err) return } args := Args{1, 2} var reply int err = client.Call("Math.Multiply", args, &reply) if err != nil { log.Println("multiply err ", err) return } fmt.Printf("multiply %d * %d = %d \n", args.A, args.B, reply) var quo Quotient err = client.Call("Math.Divide", args, &quo) if err != nil { log.Println("Divite err is ", err) return } fmt.Printf("Divide %d / %d =%d ,rem %d", args.A, args.B, quo.Quo, quo.Rem) }