Go語言實現RPC

RPC定義,來源於百度百科

  • RPC(Remote Procedure Call)—遠程過程調用,它是一種經過網絡從遠程計算機程序上請求服務,而不須要了解底層網絡技術的協議。RPC協議假定某些傳輸協議的存在,如TCP或UDP,爲通訊程序之間攜帶信息數據。在OSI網絡通訊模型中,RPC跨越了傳輸層應用層。RPC使得開發包括網絡分佈式多程序在內的應用程序更加容易。git

  • RPC採用客戶機/服務器模式。請求程序就是一個客戶機,而服務提供程序就是一個服務器。首先,客戶機調用進程發送一個有進程參數的調用信息到服務進程,而後等待應答信息。在服務器端,進程保持睡眠狀態直到調用信息到達爲止。當一個調用信息到達,服務器得到進程參數,計算結果,發送答覆信息,而後等待下一個調用信息,最後,客戶端調用進程接收答覆信息,得到進程結果,而後調用執行繼續進行。web

  • 有多種 RPC模式和執行。最初由 Sun 公司提出。IETF ONC 憲章從新修訂了 Sun 版本,使得 ONC RPC 協議成爲 IETF 標準協議。如今使用最廣泛的模式和執行是開放式軟件基礎的分佈式計算環境(DCE)。json

  • 我的的理解:不用管什麼底層網絡技術的協議,是一種經過網絡從計算機程序上請求服務,通俗一點,咱們寫代碼,要在一個地方,好比安卓,就須要在一個工程裏面,才能夠調用到其餘的程序代碼執行的過程。Go語言提供RPC支持使得開發網絡分佈式多程序在內的應用程序更加的easy數組

RPC工做流程圖

圖片來源於gitHub

  • 1.調用客戶端句柄;執行傳送參數
  • 2.調用本地系統內核發送網絡消息
  • 3.消息傳送到遠程主機
  • 4.服務器句柄獲得消息並取得參數
  • 5.執行遠程過程
  • 6.執行的過程將結果返回服務器句柄
  • 7.服務器句柄返回結果,調用遠程系統內核
  • 8.消息傳回本地主機
  • 9.客戶句柄由內核接收消息
  • 10.客戶接收句柄返回的數據

Go語言提供對RPC的支持:HTTP、TCP、JSPNRPC,可是在GoRPC是獨一無二的,它採用了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包編解碼。bash

  • 客戶端的代碼服務器

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, &quot)
	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的支持~~~
相關文章
相關標籤/搜索