命令记不住

登陆

Go 原生 RPC

Go原生RPC

1.RPC概述

  • Remote Procedure Call Protocol,通过网络从远程计算机程序上请求服务,隐藏了底层网络技术
  • 是一个技术思想
1.1 调用过程

rpc1

1.2 架构
  • 客户端(Client):服务的调用方
  • 服务端(Server):真正的服务提供者
  • 客户端存根:存放服务端的地址消息,再将客户端的请求参数打包成网络消息,然后通过网络远程发送给服务方
  • 服务端存根:接收客户端发送过来的消息,将消息解包,并调用本地的方法
1.3 RPC实现
  • go原生rpc
  • grpc:Google开源的rpc实现
  • thrift:Facebook开源的跨语言的服务开发框架
  • dubbo:阿里开源的rpc框架

2.Go内置RPC

Go标准包中已经提供了对RPC的支持,而且支持三个级别的RPC:TCP、HTTP、JSONRPC。但Go的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里面的一些函数。

3.rpc式http

server代码
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())
	}
}
client代码:
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]

	//rpc.DialHTTP 拨号
	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)
	//通过字符串调用"Arith.Multiply",类的名称和方法,通过字符串找到方法(type Arith int)
	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, &quot)
	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)

}
运行结果:
gorpc\http\client> .\main.exe  127.0.0.1
Arith: 17*8=136
Arith: 17/8=2 remainder 1

4.rpc式tcp

service代码:
package main

import (
	"errors"
	"fmt"
	"net"
	"net/rpc"
	"os"
	"time"
)

type Args struct {
	A, B int
}

type Quotient struct {
	Quo, Rem int
}

type Arith int

func (t *Arith) Multiply(args *Args, reply *int) error {
	fmt.Printf("Multiply: %v\n", time.Now())
	// time.Sleep(10 * time.Second)
	*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 {
		fmt.Printf("accept at %v\n", time.Now())
		conn, err := listener.Accept()
		if err != nil {
			continue
		}
		fmt.Printf("get at %v\n", time.Now())
		go rpc.ServeConn(conn)
	}

}

func checkError(err error) {
	if err != nil {
		fmt.Println("Fatal error ", err.Error())
		os.Exit(1)
	}
}

client代码:
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, &quot)
    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)

}

运行结果:
server:
tcp\server>main.exe
accept at 2020-12-20 20:51:04.7746436 +0800 CST m=+0.008995401
get at 2020-12-20 20:51:18.9825206 +0800 CST m=+14.216872401
accept at 2020-12-20 20:51:18.9835037 +0800 CST m=+14.217855501
Multiply: 2020-12-20 20:51:18.9844907 +0800 CST m=+14.218842501
get at 2020-12-20 20:52:14.9675121 +0800 CST m=+70.201863901
accept at 2020-12-20 20:52:14.9685095 +0800 CST m=+70.202861301
Multiply: 2020-12-20 20:52:14.9685095 +0800 CST m=+70.202861301


client:
\tcp\client>client.exe 127.0.0.1:1212
Arith: 17*8=136
Arith: 17/8=2 remainder 1