您好,登錄后才能下訂單哦!
1.遠(yuǎn)程過程調(diào)用(Remote Procedure Call,RPC)是一個計算機通信協(xié)議。
2.該協(xié)議允許運行于一臺計算機的程序調(diào)用另一臺計算機的子程序,而程序員無需額外地為這個交互作用編程。
3.如果涉及的軟件采用面向?qū)ο缶幊?,那么遠(yuǎn)程過程調(diào)用也可稱為遠(yuǎn)程調(diào)用或遠(yuǎn)程方法調(diào)用。
Golang中實現(xiàn)RPC比較簡單,官方提供了封裝好的庫,還有第三方庫。
官方庫:net/rpc
1.net/rpc庫使用encoding/gob進(jìn)行編碼
2.支持tcp和http數(shù)據(jù)傳輸方式
3.由于其他語言不支持gob編解碼方式,所以golang的RPC只支持golang開發(fā)的服務(wù)端與客戶端的交互
官方庫:net/jsonrpc
1.jsonrpc采用JSON進(jìn)行數(shù)據(jù)編解碼,因而支持跨語言調(diào)用。
2.jsonrpc庫是基于tcp協(xié)議實現(xiàn)的,暫不支持http傳輸方式。
golang的RPC必須符合四個條件才可以:
1.結(jié)構(gòu)體字段首字母要大寫,要跨域訪問,所以大寫。
2.函數(shù)名必須首字母大寫。
3.函數(shù)第一個參數(shù)是接收參數(shù),第二個參數(shù)是返回給客戶端參數(shù),必須是指針類型。
4.函數(shù)必須有一個返回值err。
golang實現(xiàn)RPC程序,實現(xiàn)求矩形面積和周長
//rpcClient/server.go
package main
import (
"log"
"net/http"
"net/rpc"
)
//服務(wù)端,求舉行面積和周長
//聲明矩形對象
type Rect struct {
}
//聲明參數(shù)結(jié)構(gòu)體,字段首字母大寫
type Param struct {
Width,Height int
}
//golang的RPC必須符合4個條件才可以
//? 結(jié)構(gòu)體字段首字母要大寫,要跨域訪問,所以大寫
//? 函數(shù)名必須首字母大寫(可以序列號導(dǎo)出的)
//? 函數(shù)第一個參數(shù)是接收參數(shù),第二個參數(shù)是返回給客戶端參數(shù),必須是指針類型
//? 函數(shù)必須有一個返回值error
//定義求矩形面積的方法
func (r Rect) Area(p Param,ret *int) error{
*ret = p.Width * p.Height
return nil
}
//定義求矩形周長的方法
func (r Rect) Perimeter(p Param,ret *int) error{
*ret = (p.Width + p.Height)*2
return nil
}
func main() {
//1.注冊服務(wù)
rect := new(Rect)
rpc.Register(rect)
//2.把服務(wù)處理綁定到http協(xié)議上
rpc.HandleHTTP()
//3.監(jiān)聽服務(wù),等待客戶端調(diào)用請求面積和周長的方法
err := http.ListenAndServe(":8080",nil)
if err != nil{
log.Fatal(err)
}
}
//rpcServer/client.go
package main
import (
"fmt"
"log"
"net/rpc"
)
//聲明參數(shù)結(jié)構(gòu)體,字段首字母大寫
type Params struct {
Width,Height int
}
//調(diào)用服務(wù)
func main() {
//1.鏈接遠(yuǎn)程RPC服務(wù)
rp,err := rpc.DialHTTP("tcp","127.0.0.1:8080")
if err != nil{
log.Fatal(err)
}
//2.調(diào)用遠(yuǎn)程方法
//定義接收服務(wù)端傳回來的計算結(jié)果的變量
ret := 0
//求矩形面積
err2 := rp.Call("Rect.Area",Params{50,10},&ret)
if err2 != nil{
log.Fatal(err2)
}
fmt.Println("面積:",ret)
//求矩形周長
err3 := rp.Call("Rect.Perimeter",Params{50,10},&ret)
if err3 != nil{
log.Fatal(err3)
}
fmt.Println("周長:",ret)
}
模仿前面例題,自己實現(xiàn) RPC 程序,服務(wù)端接收 2 個參數(shù),可以做乘法運算,也可以做商和余數(shù)的運算,客戶端進(jìn)行傳參和訪問,得到結(jié)果如下:
9*2 = 18
9/2,商=4,余數(shù)=1
下面使用net/rpc/jsonrpc庫通過json格式編碼解碼,支持跨語言調(diào)用
rpcServer/server.go
package main
import (
"errors"
"fmt"
"log"
"net"
"net/rpc"
"net/rpc/jsonrpc"
)
//結(jié)構(gòu)體,用于注冊的
type Arith struct {
}
//聲明參數(shù)結(jié)構(gòu)體
type ArithRequest struct {
A,B int
}
//返回給客戶端的結(jié)果
type ArithResponse struct {
//乘積
Pro int
//商
Quo int
//余數(shù)
Rem int
}
//乘法
func (a *Arith) Multiply(req ArithRequest,res *ArithResponse) error {
res.Pro = req.A * req.B
return nil
}
//商和余數(shù)
func (a *Arith) Divide(req ArithRequest,res *ArithResponse) error {
if req.B == 0{
return errors.New("除數(shù)不能為0")
}
//商
res.Quo = req.A/req.B
//余數(shù)
res.Rem = req.A%req.B
return nil
}
func main() {
rpc.Register(new(Arith))
lis,err := net.Listen("tcp",":8080")
if err != nil{
log.Fatal(err)
}
//循環(huán)監(jiān)聽服務(wù)
for{
conn,err := lis.Accept()
if err != nil{
continue
}
go func(conn net.Conn) {
fmt.Println("new client")
jsonrpc.ServeConn(conn)
}(conn)
}
}
rpcClient/client.go
package main
import (
"fmt"
"log"
"net/rpc/jsonrpc"
)
//聲明參數(shù)結(jié)構(gòu)體
type ArithRequest struct {
A,B int
}
//返回給客戶端的結(jié)果
type ArithResponse struct {
//乘積
Pro int
//商
Quo int
//余數(shù)
Rem int
}
//調(diào)用服務(wù)
func main() {
conn,err := jsonrpc.Dial("tcp",":8080")
if err != nil{
log.Fatal(err)
}
req := ArithRequest{9,2}
var res ArithResponse
err2 := conn.Call("Arith.Multiply", req, &res)
if err2 != nil {
log.Fatal(err2)
}
fmt.Printf("%d * %d = %d\n", req.A, req.B, res.Pro)
err3 := conn.Call("Arith.Divide", req, &res)
if err3 != nil {
log.Fatal(err3)
}
fmt.Printf("%d / %d 商 %d,余數(shù) = %d\n", req.A, req.B, res.Quo, res.Rem)
}
1.微服務(wù)架構(gòu)下數(shù)據(jù)交互一般是對內(nèi)RPC,對外REST。
2.將業(yè)務(wù)按功能模塊拆分到各個微服務(wù),具有提高項目協(xié)作效率、降低模塊耦合度、提高系統(tǒng)可用性等優(yōu)點,但是開發(fā)門檻比較高,比如RPC框架的時候,后期的服務(wù)監(jiān)控等工作。
3.一般情況下,我們會將功能代碼在本地直接調(diào)用,微服務(wù)架構(gòu)下,我們需要將這個函數(shù)作為單獨的服務(wù)運行,客戶端通過網(wǎng)絡(luò)調(diào)用。
成熟的RPC框架會有自定義傳輸協(xié)議,這里網(wǎng)絡(luò)傳輸格式定義如下:
前面是固定長度消息頭,后面是變長消息體。
//會話中讀寫數(shù)據(jù)測試
//rpc/session.go
package rpc
import (
"encoding/binary"
"io"
"net"
)
//測試網(wǎng)絡(luò)中讀寫數(shù)據(jù)的情況
//會話連接的結(jié)構(gòu)體
type Session struct {
conn net.Conn
}
//構(gòu)造方法
func NewSession(conn net.Conn) *Session {
return &Session{conn:conn}
}
//向連接中寫數(shù)據(jù)
func (s *Session) Write(data []byte) error {
//定義寫數(shù)據(jù)的格式
//4字節(jié)頭部+可變體的長度
buf := make([]byte,4+len(data))
//寫入頭部,記錄數(shù)據(jù)長度
binary.BigEndian.PutUint32(buf[:4],uint32(len(data)))
//將整個數(shù)據(jù),放在索引4的后面
copy(buf[4:],data)
_,err := s.conn.Write(buf)
if err != nil{
return err
}
return nil
}
//從連接中讀數(shù)據(jù)
func (s *Session) Read() ([]byte,error) {
//讀取頭部記錄的長度
header := make([]byte, 4)
//按長度讀取頭部消息
_,err := io.ReadFull(s.conn, header)
if err != nil{
return nil,err
}
//根據(jù)頭部信息,讀取數(shù)據(jù)長度
dataLen := binary.BigEndian.Uint32(header)
data := make([]byte,dataLen)
//讀取到的數(shù)據(jù)存到data中
_,err = io.ReadFull(s.conn, data)
if err != nil{
return nil,err
}
return data,nil
}
//rpc/session_test.go
package rpc
import (
"fmt"
"net"
"sync"
"testing"
)
func TestSession_ReadWriter(t *testing.T) {
//定義地址
addr := "127.0.0.1:8000"
my_data := "hello lili"
wg := sync.WaitGroup{}
wg.Add(2)
//寫數(shù)據(jù)的協(xié)成
go func() {
defer wg.Done()
lis,err := net.Listen("tcp", addr)
if err != nil{
t.Fatal(err)
}
conn,_ := lis.Accept()
s:= NewSession(conn)
err = s.Write([]byte(my_data))
if err != nil{
t.Fatal(err)
}
}()
//讀數(shù)據(jù)的協(xié)成
go func() {
defer wg.Done()
conn,err := net.Dial("tcp", addr)
if err != nil{
t.Fatal(err)
}
s:= NewSession(conn)
data,err := s.Read()
if err!= nil{}
t.Fatal(err)
//最后一層校驗
if string(data) != my_data{
t.Fatal(err)
}
fmt.Println("讀取到的數(shù)據(jù)為:",string(data))
}()
}
測試結(jié)果:
rpc go test -v
=== RUN TestSession_ReadWriter
--- PASS: TestSession_ReadWriter (0.00s)
PASS
ok _/Users/tongchao/Desktop/gopath/src/test/rpc 0.008s
? rpc
//codec.go
package rpc
import (
"bytes"
"encoding/gob"
)
//定義RPC交互的數(shù)據(jù)結(jié)構(gòu)
type RPCData struct {
//訪問的函數(shù)
Name string
//訪問時的參數(shù)
Args []interface{}
}
//編碼
func encode(data RPCData) ([]byte, error) {
//得到字節(jié)數(shù)組的編碼器
var buf bytes.Buffer
bufEnc := gob.NewEncoder(&buf)
//編碼器對數(shù)據(jù)編碼
if err := bufEnc.Encode(data); err != nil{
return nil,err
}
return buf.Bytes(),nil
}
//解碼
func decode(b []byte) (RPCData,error) {
buf := bytes.NewBuffer(b)
//得到字節(jié)數(shù)組的解碼器
bufDec := gob.NewDecoder(buf)
//解碼器對數(shù)據(jù)解碼
var data RPCData
if err:= bufDec.Decode(&data);err != nil{
return data,err
}
return data,nil
}
//codec_test.go
package rpc
import (
"fmt"
"testing"
)
func TestCode(t *testing.T) {
args := make([]interface{},0)
data := RPCData{
Name: "lili",
Args: args,
}
encodeData,err := encode(data)
if err != nil{
fmt.Println(err)
}
fmt.Printf("encodeData:%v\n",encodeData)
decodeData,err := decode(encodeData)
if err != nil{
fmt.Println(err)
}
fmt.Printf("decodeData:%v\n",decodeData)
}
結(jié)果:
=== RUN TestCode
encodeData:[40 255 129 3 1 1 7 82 80 67 68 97 116 97 1 255 130 0 1 2 1 4 78 97 109 101 1 12 0 1 4 65 114 103 115 1 255 132 0 0 0 28 255 131 2 1 1 14 91 93 105 110 116 101 114 102 97 99 101 32 123 125 1 255 132 0 1 16 0 0 9 255 130 1 4 108 105 108 105 0]
decodeData:{lili []}
--- PASS: TestCode (0.00s)
PASS
Process finished with exit code 0
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。