RPC(Remote Procedure Call Protocol)——是一種遠(yuǎn)程調(diào)用協(xié)議,它采用客戶機(jī)/服務(wù)器模式。請(qǐng)求程序就是一個(gè)客戶機(jī),而服務(wù)提供程序就是一個(gè)服務(wù)器。首先,客戶機(jī)調(diào)用進(jìn)程發(fā)送一個(gè)有進(jìn)程參數(shù)的調(diào)用信息到服務(wù)進(jìn)程,然后等待應(yīng)答信息。在服務(wù)器端,進(jìn)程保持睡眠狀態(tài)直到調(diào)用信息到達(dá)為止。當(dāng)一個(gè)調(diào)用信息到達(dá),服務(wù)器獲得進(jìn)程參數(shù),計(jì)算結(jié)果,發(fā)送答復(fù)信息,然后等待下一個(gè)調(diào)用信息,最后,客戶端調(diào)用進(jìn)程接收答復(fù)信息,獲得進(jìn)程結(jié)果,然后調(diào)用執(zhí)行繼續(xù)進(jìn)行。
RPC的調(diào)用過程

運(yùn)行時(shí),一次客戶機(jī)對(duì)服務(wù)器的RPC調(diào)用,其內(nèi)部操作大致有如下十步:
(1)調(diào)用客戶端句柄;執(zhí)行傳送參數(shù)
(2)調(diào)用本地系統(tǒng)內(nèi)核發(fā)送網(wǎng)絡(luò)消息
(3)消息通過網(wǎng)絡(luò)到遠(yuǎn)程主機(jī)
(4)服務(wù)器句柄得到消息并取得參數(shù)
(5)執(zhí)行遠(yuǎn)程過程
(6)執(zhí)行的過程將結(jié)果返回服務(wù)器句柄
(7)服務(wù)器句柄返回結(jié)果,調(diào)用遠(yuǎn)程系統(tǒng)內(nèi)核
(8)消息通過網(wǎng)絡(luò)傳回本地主機(jī)
(9)客戶句柄由內(nèi)核接收消息
(10)客戶接收句柄返回的數(shù)據(jù)
RPC的目標(biāo)就是要2~8這些步驟都封裝起來,讓用戶對(duì)這些細(xì)節(jié)透明。
在go中,一個(gè)對(duì)象中只有滿足如下這些條件的方法,才能被 RPC 服務(wù)端設(shè)置為可供遠(yuǎn)程訪問:
1) 必須是在對(duì)象外部可公開調(diào)用的方法(首字母大寫);
2) 必須有兩個(gè)參數(shù),且參數(shù)的類型都必須是包外部可以訪問的類型或者是Go內(nèi)建支持的類
型;
3) 第二個(gè)參數(shù)必須是一個(gè)指針;
4) 方法必須返回一個(gè)error類型的值。
總結(jié)為:func (t *T) MethodName(argType T1, replyType *T2) error
在上面這行代碼中,類型T、T1 和 T2 默認(rèn)會(huì)使用 Go 內(nèi)置的 encoding/gob 包進(jìn)行編碼解碼。
關(guān)于encoding/gob 包的內(nèi)容,稍后我們將會(huì)對(duì)其進(jìn)行介紹。
該方法(MethodName)的第一個(gè)參數(shù)表示由 RPC 客戶端傳入的參數(shù),第二個(gè)參數(shù)表示要返
回給RPC客戶端的結(jié)果,該方法最后返回一個(gè) error 類型的值。
如果沒有明確指定 RPC 傳輸過程中使用何種編碼解碼器,默認(rèn)將使用 Go 標(biāo)準(zhǔn)庫提供的
encoding/gob 包進(jìn)行數(shù)據(jù)傳輸。net/rpc包下面還有json實(shí)現(xiàn)的rpc傳輸格式。
接下來貼一段go實(shí)現(xiàn)rpc的代碼:
server端:
package main
import (
? ? "errors"
? ? "fmt"
? ? "net"
? ? "net/http"
? ? "net/rpc"
)
type Person struct {
? ? Name string
? ? Sex? string
? ? Age? int
}
func (p *Person) Hello(args Person, reply *Person) error {
? ? reply.Name = "hello-" + args.Name
? ? reply.Sex = "hello-" + args.Sex
? ? reply.Age = args.Age * 2
? ? return nil
}
func main() {
? ? person := new(Person)
? ? //注冊(cè)服務(wù)對(duì)象并開啟該RPC服務(wù)
? ? rpc.Register(person)
? ? rpc.HandleHTTP() //rpc.HandleHTTP函數(shù)把該服務(wù)注冊(cè)到了HTTP協(xié)議上
? ? //然后我們就可以利用http的方式來傳遞數(shù)據(jù)了(基于HTTP協(xié)議的RPC)
? ? //如果我們采用基于TCP協(xié)議的RPC,則不用該函數(shù)
? ? tcpAddr, err := net.ResolveTCPAddr("tcp", ":6060")
? ? checkError(err)
? ? listener, err := net.ListenTCP("tcp", tcpAddr)
? ? checkError(err)
? ? http.Serve(listener, nil)//http處理監(jiān)聽器
}
func checkError(err error) {
? ? if err != nil {
? ? ? ? fmt.Println("Fatal error", err.Error())
? ? }
}
client端:
package main
import (
? ? "fmt"
? ? "log"
? ? "net/rpc"
)
type Person struct {
? ? Name string
? ? Sex? string
? ? Age? int
}
func main() {
? ? //與RPC服務(wù)端建立連接
? ? client, err := rpc.DialHTTP("tcp", ":6060")
? ? if err != nil {
? ? ? ? log.Fatal("dialing:", err)
? ? }
? ? //連接成功,RPC客戶端調(diào)用服務(wù)端提供的方法
? ? //同步調(diào)用程序順序執(zhí)行的方式
? ? person1 := Person{
? ? ? ? Name: "初級(jí)賽亞人",
? ? ? ? Sex:? "男",
? ? ? ? Age:? 117}
? ? person2 := new(Person)
? ? //同步調(diào)用rpc
? ? client.Call("Person.Hello", person1, &person2)
? ? fmt.Println("person2=", person2)
? ? //異步rpc方式進(jìn)行調(diào)用
? ? person3 := new(Person)
? ? divCall1 := client.Go("Person.Hello", person1, &person3, nil)
? ? <-divCall1.Done? //表示異步調(diào)用完成
? ? fmt.Println("person3=", person3)
}
打印結(jié)果:
person2= &{hello-初級(jí)賽亞人 hello-男 234}
person3= &{hello-初級(jí)賽亞人 hello-男 234}
.