RPC(Remote Procedure Call Protocol)——遠程程序呼叫協議,是一種通過網路從遠程計算機程式上請求服務,而不需要了解底層網路技術的協議,它假定某些傳輸協議的存在,如TCP或UDP,以便為通信程式之間攜帶資訊資料,通過它可以使函式呼叫模式網路化,在OSI網路通信模型中,RPC跨越了傳輸層和應用層,RPC使得開發包括網路分布式多程式在內的應用程式更加容易,
RPC作業原理
運行時,一次客戶機對服務器的RPC呼叫,其內部操作大致有如下十步:
- 1.呼叫客戶端句柄;執行傳送引數
- 2.呼叫本地系統內核發送網路訊息
- 3.訊息傳送到遠程主機
- 4.服務器句柄得到訊息并取得引數
- 5.執行遠程程序
- 6.執行的程序將結果回傳服務器句柄
- 7.服務器句柄回傳結果,呼叫遠程系統內核
- 8.訊息傳回本地主機
- 9.客戶句柄由內核接收訊息
- 10.客戶接收句柄回傳的資料
Go RPC
Go標準包中已經提供了對RPC的支持,而且支持三個級別的RPC:TCP、HTTP、JSONRPC,但Go的RPC包是獨一無二的RPC,它和傳統的RPC系統不同,它只支持Go開發的服務器與客戶端之間的互動,因為在內部,它們采用了Gob來編碼,
Go RPC的函式只有符合下面的條件才能被遠程訪問,不然會被忽略,詳細的要求如下:
- 函式必須是匯出的(首字母大寫)
- 必須有兩個匯出型別的引數,
- 第一個引數是接收的引數,第二個引數是回傳給客戶端的引數,第二個引數必須是指標型別的
- 函式還要有一個回傳值error
舉個例子,正確的RPC函式格式如下:
func (t *T) MethodName(argType T1, replyType *T2) error
T、T1和T2型別必須能被encoding/gob包編解碼,
任何的RPC都需要通過網路來傳遞資料,Go RPC可以利用HTTP和TCP來傳遞資料,利用HTTP的好處是可以直接復用net/http里面的一些函式,詳細的例子請看下面的實作
HTTP RPC
http的服務端代碼實作如下:
package main
import (
"errors"
"fmt"
"net/http"
"net/rpc"
)
type Args struct {
A, B int
}
type Quotient struct {
Quo, Rem int
}
type Arith int
func (t *Arith) Multiply(args *Args, reply *int) error {
*reply = args.A * args.B
return nil
}
func (t *Arith) 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() {
arith := new(Arith)
rpc.Register(arith)
rpc.HandleHTTP()
err := http.ListenAndServe(":1234", nil)
if err != nil {
fmt.Println(err.Error())
}
}
通過上面的例子可以看到,我們注冊了一個Arith的RPC服務,然后通過rpc.HandleHTTP函式把該服務注冊到了HTTP協議上,然后我們就可以利用http的方式來傳遞資料了,
請看下面的客戶端代碼:
package main
import (
"fmt"
"log"
"net/rpc"
"os"
)
type Args struct {
A, B int
}
type Quotient struct {
Quo, Rem int
}
func main() {
if len(os.Args) != 2 {
fmt.Println("Usage: ", os.Args[0], "server")
os.Exit(1)
}
serverAddress := os.Args[1]
client, err := rpc.DialHTTP("tcp", serverAddress+":1234")
if err != nil {
log.Fatal("dialing:", err)
}
// Synchronous call
args := Args{17, 8}
var reply int
err = client.Call("Arith.Multiply", args, &reply)
if err != nil {
log.Fatal("arith error:", err)
}
fmt.Printf("Arith: %d*%d=%d\n", args.A, args.B, reply)
var quot Quotient
err = client.Call("Arith.Divide", args, ")
if err != nil {
log.Fatal("arith error:", err)
}
fmt.Printf("Arith: %d/%d=%d remainder %d\n", args.A, args.B, quot.Quo, quot.Rem)
}
我們把上面的服務端和客戶端的代碼分別編譯,然后先把服務端開啟,然后開啟客戶端,輸入代碼,就會輸出如下資訊:
$ ./http_c localhost
Arith: 17*8=136
Arith: 17/8=2 remainder 1
通過上面的呼叫可以看到引數和回傳值是我們定義的struct型別,在服務端我們把它們當做呼叫函式的引數的型別,在客戶端作為client.Call的第2,3兩個引數的型別,客戶端最重要的就是這個Call函式,它有3個引數,第1個要呼叫的函式的名字,第2個是要傳遞的引數,第3個要回傳的引數(注意是指標型別),通過上面的代碼例子我們可以發現,使用Go的RPC實作相當的簡單,方便,
TCP RPC
上面我們實作了基于HTTP協議的RPC,接下來我們要實作基于TCP協議的RPC,服務端的實作代碼如下所示:
package main
import (
"errors"
"fmt"
"net"
"net/rpc"
"os"
)
type Args struct {
A, B int
}
type Quotient struct {
Quo, Rem int
}
type Arith int
func (t *Arith) Multiply(args *Args, reply *int) error {
*reply = args.A * args.B
return nil
}
func (t *Arith) 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() {
arith := new(Arith)
rpc.Register(arith)
tcpAddr, err := net.ResolveTCPAddr("tcp", ":1234")
checkError(err)
listener, err := net.ListenTCP("tcp", tcpAddr)
checkError(err)
for {
conn, err := listener.Accept()
if err != nil {
continue
}
rpc.ServeConn(conn)
}
}
func checkError(err error) {
if err != nil {
fmt.Println("Fatal error ", err.Error())
os.Exit(1)
}
}
上面這個代碼和http的服務器相比,不同在于:在此處我們采用了TCP協議,然后需要自己控制連接,當有客戶端連接上來后,我們需要把這個連接交給rpc來處理,
如果你留心了,你會發現這它是一個阻塞型的單用戶的程式,如果想要實作多并發,那么可以使用goroutine來實作,我們前面在socket小節的時候已經介紹過如何處理goroutine, 下面展現了TCP實作的RPC客戶端:
package main
import (
"fmt"
"log"
"net/rpc"
"os"
)
type Args struct {
A, B int
}
type Quotient struct {
Quo, Rem int
}
func main() {
if len(os.Args) != 2 {
fmt.Println("Usage: ", os.Args[0], "server:port")
os.Exit(1)
}
service := os.Args[1]
client, err := rpc.Dial("tcp", service)
if err != nil {
log.Fatal("dialing:", err)
}
// Synchronous call
args := Args{17, 8}
var reply int
err = client.Call("Arith.Multiply", args, &reply)
if err != nil {
log.Fatal("arith error:", err)
}
fmt.Printf("Arith: %d*%d=%d\n", args.A, args.B, reply)
var quot Quotient
err = client.Call("Arith.Divide", args, ")
if err != nil {
log.Fatal("arith error:", err)
}
fmt.Printf("Arith: %d/%d=%d remainder %d\n", args.A, args.B, quot.Quo, quot.Rem)
}
這個客戶端代碼和http的客戶端代碼對比,唯一的區別一個是DialHTTP,一個是Dial(tcp),其他處理一模一樣,
JSON RPC
JSON RPC是資料編碼采用了JSON,而不是gob編碼,其他和上面介紹的RPC概念一模一樣,下面我們來演示一下,如何使用Go提供的json-rpc標準包,請看服務端代碼的實作:
package main
import (
"errors"
"fmt"
"net"
"net/rpc"
"net/rpc/jsonrpc"
"os"
)
type Args struct {
A, B int
}
type Quotient struct {
Quo, Rem int
}
type Arith int
func (t *Arith) Multiply(args *Args, reply *int) error {
*reply = args.A * args.B
return nil
}
func (t *Arith) 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() {
arith := new(Arith)
rpc.Register(arith)
tcpAddr, err := net.ResolveTCPAddr("tcp", ":1234")
checkError(err)
listener, err := net.ListenTCP("tcp", tcpAddr)
checkError(err)
for {
conn, err := listener.Accept()
if err != nil {
continue
}
jsonrpc.ServeConn(conn)
}
}
func checkError(err error) {
if err != nil {
fmt.Println("Fatal error ", err.Error())
os.Exit(1)
}
}
通過示例我們可以看出 json-rpc是基于TCP協議實作的,目前它還不支持HTTP方式,
請看客戶端的實作代碼:
package main
import (
"fmt"
"log"
"net/rpc/jsonrpc"
"os"
)
type Args struct {
A, B int
}
type Quotient struct {
Quo, Rem int
}
func main() {
if len(os.Args) != 2 {
fmt.Println("Usage: ", os.Args[0], "server:port")
log.Fatal(1)
}
service := os.Args[1]
client, err := jsonrpc.Dial("tcp", service)
if err != nil {
log.Fatal("dialing:", err)
}
// Synchronous call
args := Args{17, 8}
var reply int
err = client.Call("Arith.Multiply", args, &reply)
if err != nil {
log.Fatal("arith error:", err)
}
fmt.Printf("Arith: %d*%d=%d\n", args.A, args.B, reply)
var quot Quotient
err = client.Call("Arith.Divide", args, ")
if err != nil {
log.Fatal("arith error:", err)
}
fmt.Printf("Arith: %d/%d=%d remainder %d\n", args.A, args.B, quot.Quo, quot.Rem)
}
轉載請註明出處,本文鏈接:https://www.uj5u.com/qukuanlian/258486.html
標籤:區塊鏈
下一篇:C/C++陣列與指標
