RPC定義,來源於百度百科
-
RPC(Remote Procedure Call)—遠端過程呼叫,它是一種通過網路從遠端計算機程式上請求服務,而不需要了解底層網路技術的協議。RPC協議假定某些傳輸協議的存在,如TCP或UDP,為通訊程式之間攜帶資訊資料。在OSI網路通訊模型中,RPC跨越了傳輸層和應用層。RPC使得開發包括網路分散式多程式在內的應用程式更加容易。
-
RPC採用客戶機/伺服器模式。請求程式就是一個客戶機,而服務提供程式就是一個伺服器。首先,客戶機呼叫程式傳送一個有程式引數的呼叫資訊到服務程式,然後等待應答資訊。在伺服器端,程式保持睡眠狀態直到呼叫資訊到達為止。當一個呼叫資訊到達,伺服器獲得程式引數,計算結果,傳送答覆資訊,然後等待下一個呼叫資訊,最後,客戶端呼叫程式接收答覆資訊,獲得程式結果,然後呼叫執行繼續進行。
-
有多種 RPC模式和執行。最初由 Sun 公司提出。IETF ONC 憲章重新修訂了 Sun 版本,使得 ONC RPC 協議成為 IETF 標準協議。現在使用最普遍的模式和執行是開放式軟體基礎的分散式計算環境(DCE)。
-
個人的理解:不用管什麼底層網路技術的協議,是一種通過網路從計算機程式上請求服務,通俗一點,我們寫程式碼,要在一個地方,比如安卓,就需要在一個工程裡面,才可以呼叫到其他的程式程式碼執行的過程。Go語言提供RPC支援使得開發網路分散式多程式在內的應用程式更加的
easy
RPC工作流程圖
- 1.呼叫客戶端控制程式碼;執行傳送引數
- 2.呼叫本地系統核心傳送網路訊息
- 3.訊息傳送到遠端主機
- 4.伺服器控制程式碼得到訊息並取得引數
- 5.執行遠端過程
- 6.執行的過程將結果返回伺服器控制程式碼
- 7.伺服器控制程式碼返回結果,呼叫遠端系統核心
- 8.訊息傳回本地主機
- 9.客戶控制程式碼由核心接收訊息
- 10.客戶接收控制程式碼返回的資料
Go語言提供對RPC的支援:HTTP、TCP、JSPNRPC
,但是在Go
中RPC
是獨一無二的,它採用了GoLang Gob
編碼,只能支援Go語言!
- GoLang Gob:是Golang包自帶的一個資料結構序列化的編碼/解碼工具。編碼使用Encoder,解碼使用Decoder。一種典型的應用場景就是RPC(remote procedure calls)。
HTTP RPC Demo
- 服務端的程式碼
package main
import (
"fmt"
"net/rpc"
"net/http"
"errors"
)
func main() {
rpcDemo()
}
type Arith int
func rpcDemo() {
arith:=new(Arith)
//arith=== 0xc04204e090
fmt.Println("arith===",arith)
rpc.Register(arith)
//HandleHTTP將RPC訊息的HTTP處理程式註冊到Debug伺服器
//DEFAUTUPCPATH和Debug除錯路徑上的除錯處理程式。
//仍然需要呼叫http.Services(),通常是在GO語句中。
rpc.HandleHTTP()
err:=http.ListenAndServe(":1234",nil)
if err != nil {
fmt.Println("err=====",err.Error())
}
}
type Args struct {
A, B int
}
type Quotient struct {
Quo, Rem int
}
//函式必須是匯出的(首字母大寫)
//必須有兩個匯出型別的引數,
//第一個引數是接收的引數,第二個引數是返回給客戶端的引數,第二個引數必須是指標型別的
//函式還要有一個返回值error
func (t *Arith) Multiply(args *Args, reply *int) error {
*reply = args.A * args.B
fmt.Println("這個方法執行了啊---嘿嘿--- Multiply ",reply)
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
fmt.Println("這個方法執行了啊---嘿嘿--- Divide quo==",quo)
return nil
}
複製程式碼
- Go RPC 的函式只有符合四個條件才能夠被遠端訪問,不然會被忽略
- 函式必須是首字母大寫(可以匯出的)
- 必須有兩個匯出型別的引數
- 第一個引數是接受的引數,第二個引數是返回給客戶端的引數,而且第二個引數是指標的型別
- 函式還要有一個返回值
error
func (t *T) MethodName(argType T1, replyType *T2) error
複製程式碼
-
T、T1和T2型別必須能被
encoding/gob
包編解碼。 -
客戶端的程式碼
package main
import (
"log"
"fmt"
"os"
"net/rpc"
"strconv"
)
type ArgsTwo struct {
A, B int
}
type QuotientTwo struct {
Quo, Rem int
}
func main() {
// 如果什麼都不輸入的話 ,就是以下的這個值
//os***************** [C:\Users\win7\AppData\Local\Temp\go-build669605574\command-
//line-arguments\_obj\exe\GoRPCWeb.exe 127.0.0.1] **********************
fmt.Println("os*****************",os.Args,"**********************")
if len(os.Args) != 4 { // todo 第二個地址是 我們本地的地址
fmt.Println("老子要退出了哦 傻逼 一號start--------》》》", os.Args[0], "《《《---------------server end")
os.Exit(1)
}else{
fmt.Println("長度是多少 "+strconv.Itoa( len(os.Args))+"才是準確的長度 哦---》")
}
//獲取輸入的地址是獲取輸入得 os 資料的 第一個位置的值
serverAddress := os.Args[1]
fmt.Println("severAddress==",serverAddress)
// //DelayHTTP在指定的網路地址連線到HTTP RPC伺服器
///在預設HTTP RPC路徑上監聽。
client, err := rpc.DialHTTP("tcp", serverAddress)
if err != nil {
log.Fatal("發生錯誤了 在這裡地方 DialHTTP", err)
}
i1,_:=strconv.Atoi( os.Args[2])
i2,_:=strconv.Atoi( os.Args[3])
args := ArgsTwo{i1, i2}
var reply int
//呼叫呼叫命名函式,等待它完成,並返回其錯誤狀態。
err = client.Call("Arith.Multiply", args, &reply)
if err != nil {
log.Fatal("Call Multiply 發生錯誤了哦 arith error:", err)
}
fmt.Printf("Arith 乘法: %d*%d=%d\n", args.A, args.B, reply)
var quot QuotientTwo
//呼叫呼叫命名函式,等待它完成,並返回其錯誤狀態。
err = client.Call("Arith.Divide", args, ")
if err != nil {
log.Fatal("arith error:", err)
}
fmt.Printf("Arith 除法取整數: %d/%d=%d 餘數 %d\n", args.A, args.B, quot.Quo, quot.Rem)
}
複製程式碼
E:\new_code\GoDemo\web_server>go run GoRPCWeb8.go 127.0.0.1:1234 20 3
os***************** [C:\Users\win7\AppData\Local\Temp\go-build011170718\command-
line-arguments\_obj\exe\GoRPCWeb8.exe 127.0.0.1:1234 20 3] *********************
*
長度是多少 4才是準確的長度 哦---》
severAddress== 127.0.0.1:1234
Arith 乘法: 20*3=60
Arith 除法取整數: 20/3=6 餘數 2
複製程式碼
- go run GoRPCWeb8.go 127.0.0.1:1234 20 3
- go run 執行的命令
- GoRPCWeb8.go :這是檔案的名稱,需要到指定的目錄下啟動
cmd
- 127.0.0.1:1234 : ip地址和埠號
- 20 3 這是客服端傳入的值:一個除數,一個被除數,傳入到伺服器做乘法運算 乘法:
20*3=60
和除法取整數:20/3=6
餘數2
,怎麼做的,客戶端不關心,給到服務端去完成
os.Args[0]
=[C:\Users\win7\AppData\Local\Temp\go-build011170718\command- line-arguments\_obj\exe\GoRPCWeb8.exe 127.0.0.1:1234 20 3]
if len(os.Args) != 4 { // todo 第二個地址是 我們本地的地址
fmt.Println("老子要退出了哦 傻逼 一號start--------》》》", os.Args[0], "《《《---------------server end")
os.Exit(1)
}else{
fmt.Println("長度是多少 "+strconv.Itoa( len(os.Args))+"才是準確的長度 哦---》")
}
複製程式碼
TCP RPC Demo
- 基於TCP協議實現的RPC,服務端的程式碼
package main
import (
"fmt"
"net/rpc"
"net"
"os"
"errors"
)
func init() {
fmt.Println("基於TCP協議實現的RPC,服務端的程式碼如下")
}
type Me struct {
A,B int
}
type You struct {
CC,D int
}
type Num int
/*
Go RPC的函式只有符合下面的條件才能夠被遠端訪問,不然會被忽略
1 函式必須是匯出的(首字母大寫)
2 必須有兩個匯出型別的引數
3 第一個引數是接受的引數,第二個引數是返回給客戶端的引數,第二個引數必須是指正型別的
4 函式還必須有一個返回值error
*/
func (n *Num) M(args *Me,reply *int) error {
*reply=args.A * args.B
return nil
}
func (n *Num) F(args * Me,u *You ) error {
if args.B==0{
return errors.New("輸入不能夠為0 被除數")
}
u.D=args.A/args.B
u.CC=args.A % args.B
return nil
}
func main() {
//內建函式new本質上說跟其它語言中的同名函式功能一樣:new(T)分配了零值填充的T型別的記憶體空間,並且返回其地址,即一個*T型別的值。用Go的術語說,它返回了一個指標,指向新分配的型別T的零值。有一點非常重要:
//new返回指標。
num:=new(Num)
rpc.Register(num)
//ResolveTCPAddr返回TCP端點的地址。
//網路必須是TCP網路名稱。
tcpAddr,err:=net.ResolveTCPAddr("tcp",":1234")
if err != nil {
fmt.Println("錯誤了哦")
os.Exit(1)
}
listener,err:=net.ListenTCP("tcp",tcpAddr)
for {
// todo 需要自己控制連線,當有客戶端連線上來後,我們需要把這個連線交給rpc 來處理
conn,err:=listener.Accept()
if err != nil {
continue
}
rpc.ServeConn(conn)
}
}
複製程式碼
- 基於TCP客戶端程式碼
package main
import (
"fmt"
"os"
"net/rpc"
"log"
"strconv"
)
func main() {
fmt.Println("客戶端 其他端 去呼叫的地方 對應的例子是 GoTCPRPC9.go")
if len(os.Args)==4{
fmt.Println("長度必須等於4,因為呢,你輸入的肯定是一個ip的地址ip=",os.Args[1],"嘿嘿,加上後面的被除數os.Args[2]=",os.Args[2],"和除數os.Args[3]=",os.Args[3])
//os.Exit(1)
}
// 獲取 ip 地址
service:= os.Args[1]
//連線 撥號連線到指定的網路地址的RPC伺服器。
client,err:=rpc.Dial("tcp",service)
if err!=nil {
log.Fatal("老子在連線Dial的發生了錯誤,我要退出了",err)
}
num1:=os.Args[2]
i1,error1:=strconv.Atoi(num1)
if error1!=nil {
fmt.Println("自己不知道 自己輸入錯誤了啊 請看error :",error1)
os.Exit(1)
}
num2:=os.Args[3]
i2,error2:=strconv.Atoi(num2)
if error2!=nil {
fmt.Println("自己不知道 自己輸入錯誤了啊 請看error :",error2)
os.Exit(1)
}
aa:=AAA{i1,i2}
var reply int
err1:=client.Call("Num.M",aa,&reply)
if err1 != nil{
log.Fatal("我要退出了,因為我在Call的時候發生了 錯誤",err1)
}
fmt.Println("我進行正常結果如下")
fmt.Printf("Num : %d*%d=%d\n",aa.A,aa.B,reply)
var bb BDemo
//呼叫呼叫命名函式,等待它完成,並返回其錯誤狀態。
err= client.Call("Num.F",aa,&bb)
if err!=nil {
log.Fatal("我對這個方法發生了過敏的反應 哈哈哈哈 err=====",err)
}
fmt.Printf("Num: %d/%d=%d 餘數 %d\n",aa.A,aa.B,bb.DD,bb.CC)
}
// 定義兩個類,那邊需要操作的類
type AAA struct {
A,B int
}
//記住這裡不能夠大寫 兩個連著一起大寫 有點意思
//reading body gob: type mismatch: no fields matched compiling decoder for DDDD
// todo 為啥 第二個引數 只要是兩個連在一起的DDDD 就會報錯 reading body gob: type mismatch: no fields matched compiling decoder for
type BDemo struct {
DD, CC int
}
複製程式碼
- 執行結果圖
E:\new_code\GoDemo\web_server>go run GoTCPRPCWeb10.go 127.0.0.1:1234 20 1
客戶端 其他端 去呼叫的地方 對應的例子是 GoTCPRPC9.go
長度必須等於4,因為呢,你輸入的肯定是一個ip的地址ip= 127.0.0.1:1234 嘿嘿,加上後面
的被除數os.Args[2]= 20 和除數os.Args[3]= 1
我進行正常結果如下
Num : 20*1=20
Num: 20/1=0 餘數 0
E:\new_code\GoDemo\web_server>go run GoTCPRPCWeb10.go 127.0.0.1:1234 20 2
客戶端 其他端 去呼叫的地方 對應的例子是 GoTCPRPC9.go
長度必須等於4,因為呢,你輸入的肯定是一個ip的地址ip= 127.0.0.1:1234 嘿嘿,加上後面
的被除數os.Args[2]= 20 和除數os.Args[3]= 2
我進行正常結果如下
Num : 20*2=40
Num: 20/2=0 餘數 0
E:\new_code\GoDemo\web_server>go run GoTCPRPCWeb10.go 127.0.0.1:1234 20 3
客戶端 其他端 去呼叫的地方 對應的例子是 GoTCPRPC9.go
長度必須等於4,因為呢,你輸入的肯定是一個ip的地址ip= 127.0.0.1:1234 嘿嘿,加上後面
的被除數os.Args[2]= 20 和除數os.Args[3]= 3
我進行正常結果如下
Num : 20*3=60
Num: 20/3=0 餘數 2
複製程式碼
- 在定義
BDemo
的時候, 如果定義的DD, CC int
和服務端不一樣,就會報錯reading body gob: type mismatch: no fields matched compiling decoder for
,其實發現好多種情況,也會出現這種錯誤,但是目前不知道為啥會這樣,後續,等原始碼深入一點,回來看這個問題 todo2018/07/19 - 這種
TCP
方式和上面的HTTP
不同的是- HTTP:指定的網路地址連線到HTTP RPC伺服器
//DelayHTTP在指定的網路地址連線到HTTP RPC伺服器
///在預設HTTP RPC路徑上監聽。
client, err := rpc.DialHTTP("tcp", serverAddress)
複製程式碼
- TCP:指定的網路地址連線到HTTP RPC伺服器
client,err:=rpc.Dial("tcp",service)
複製程式碼
JSON RPC
-
JSON RPC
是資料編碼採用了JSON
,而不是gob
編碼,其他和上面介紹的RPC
概念一模一樣的。 -
服務端的程式碼如下
package main
import (
"fmt"
"net/rpc"
"net"
"net/rpc/jsonrpc"
)
//使用Go提供的json-rpc 標準包
func init() {
fmt.Println("JSON RPC 採用了JSON,而不是 gob編碼,和RPC概念一模一樣,")
}
type Work struct {
Who,DoWhat string
}
type DemoM string
func (m *DemoM) DoWork(w *Work,whoT *string) error {
*whoT="是誰:"+w.Who+",在做什麼---"+w.DoWhat
return nil
}
func main() {
str:=new(DemoM)
rpc.Register(str)
tcpAddr,err:=net.ResolveTCPAddr("tcp",":8080")
if err!=nil{
fmt.Println("大哥發生錯誤了啊,請看錯誤 ResolveTCPAddr err=",err)
}
listener,err:=net.ListenTCP("tcp",tcpAddr)
if err!=nil {
fmt.Println("發生錯誤了--》err=",err)
}
for {
conn,err:= listener.Accept()
if err!=nil {
continue
}
jsonrpc.ServeConn(conn)
}
}
複製程式碼
- 客戶端的程式碼
package main
import (
"fmt"
"os"
"net/rpc/jsonrpc"
"log"
)
func main() {
fmt.Println("這是客戶端,用來啟動,通過命令列來啟動")
fmt.Println("客戶端 其他端 去呼叫的地方 對應的例子是 GoTCPRPC9.go")
if len(os.Args)==4{
fmt.Println("長度必須等於4,因為呢,你輸入的肯定是一個ip的地址ip=",os.Args[1],"嘿嘿,加上後面的被除數os.Args[2]=",os.Args[2],"和除數os.Args[3]=",os.Args[3])
//os.Exit(1)
}
service:=os.Args[1]
client,err:=jsonrpc.Dial("tcp",service)
if err != nil {
log.Fatal("Dial 發生了錯誤了哦 錯誤的資訊為 err=",err)
}
send:=Send{os.Args[2],os.Args[3]}
var resive string
err1:=client.Call("DemoM.DoWork",send,&resive)
if err1!=nil {
fmt.Println("shiming call error ")
fmt.Println("Call 的時候發生了錯誤了哦 err=",err1)
}
fmt.Println("收到資訊了",resive)
}
// 類可以不一樣 但是 Who 和DoWhat 要必須一樣 要不然接收到不到值,等我在詳細的瞭解了 才去分析下原因 感覺有點矇蔽啊
type Send struct {
Who, DoWhat string
}
複製程式碼
- 執行的結果如下
E:\new_code\GoDemo\web_server>go run GoJSONRPCWeb11.go 127.0.0.1:8080 shiming g
ongzuo
這是客戶端,用來啟動,通過命令列來啟動
客戶端 其他端 去呼叫的地方 對應的例子是 GoTCPRPC9.go
長度必須等於4,因為呢,你輸入的肯定是一個ip的地址ip= 127.0.0.1:8080 嘿嘿,加上後面
的被除數os.Args[2]= shiming 和除數os.Args[3]= gongzuo
收到資訊了 是誰:shiming,在做什麼---gongzuo
E:\new_code\GoDemo\web_server>go run GoJSONRPCWeb11.go 127.0.0.1:8080 shiming q
iaodaima
這是客戶端,用來啟動,通過命令列來啟動
客戶端 其他端 去呼叫的地方 對應的例子是 GoTCPRPC9.go
長度必須等於4,因為呢,你輸入的肯定是一個ip的地址ip= 127.0.0.1:8080 嘿嘿,加上後面
的被除數os.Args[2]= shiming 和除數os.Args[3]= qiaodaima
收到資訊了 是誰:shiming,在做什麼---qiaodaima
複製程式碼
os.Args
是一個陣列var Args []string
,通過輸入獲取到,然後把這個客戶端輸入的內容傳送到服務端,服務端做些操作,然後在返回給客戶端Go
已經提供了RPC
良好的支援,通過HTTP
TCP
JSONRPC
的實現,可以很方便開發分散式的Web
應用,但是我目前還不會,在學習中。遺憾的是Go
沒有提供SOAP RPC
的支援~~~