什么是RPC
RPC:远程过程调用(Remote Procedure Call,缩写为 RPC)是一个计算机通信协议。
在平时的开发中,会遇到我的程序,要去访问另一个非本机的服务,这时候就需要编写网络访问代码来实现,来处理网络间的请求和相应。这时有两个角色,一个是本地客户端,另一个是远程服务端。本地客户端想要的数据或功能自己没有,这时有个远程服务能够提供数据或具备某个服务,那么,本地客户端就要实现一套网络通信功能的代码,能够从本地发送请求到网络上的服务端,并且能解析服务端返回的数据。
如果对微服务有了解,那么肯定知道,RPC就是微服务中各个服务之间沟通的基带,服务与服务相互之间的通信,都是依赖的RPC。
大多数编程语言都具备完整的网络编程支持,但是编写起来特别麻烦,我们看一下传统的网络编程socket步骤一般如下:
(1) 建立Socket:使用socket()函数。
(2) 绑定Socket:使用bind()函数。
(3) 监听:使用listen()函数。或者连接:使用connect()函数。
(4) 接受连接:使用accept()函数。
(5) 接收:使用receive()函数。或者发送:使用send()函数。
PS:Go语言标准库对此过程进行了抽象和封装。无论我们期望使用什么协议建立什么形式的连接,都只需要调用net.Dial()即可。这是Go的网络编程的使用,我们再来看一下Go中的RPC使用。
go中的RPC
服务端代码:
package main
import (
"errors"
"log"
"net"
"net/http"
"net/rpc"
)
type Args struct {
A,B int
}
type Quotient struct {
Quo, Rem int
}
type Arith int
func (a *Arith) Multiply(args *Args, reply *int) error {
*reply = args.A * args.B
return nil
}
func (a *Arith) Divide(args *Args, quotient *Quotient) error {
if args.B == 0 {
return errors.New("divide by zero")
}
quotient.Quo = args.A / args.B
quotient.Rem = args.A % args.B
return nil
}
func main() {
arith := new(Arith)
rpc.Register(arith)
rpc.HandleHTTP()
listener, err := net.Listen("tcp", "localhost:12345")
if err != nil {
log.Fatal(err)
}
http.Serve(listener, nil)
}
客户端代码:
package main
import (
"fmt"
"log"
"net/rpc"
"sync"
)
type Argsc struct {
A,B int
}
type Quotientc struct {
Quo, Rem int
}
var wg sync.WaitGroup
func main() {
client, err := rpc.DialHTTP("tcp", "localhost:12345")
if err != nil {
log.Fatal("dialing:", err)
}
for i:=0; i<5; i++ {
wg.Add(1)
go cl(client)
}
wg.Wait()
}
func cl(client *rpc.Client) {
args := &Argsc{9,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 q = &Quotientc{0,0,}
err = client.Call("Arith.Divide", args, q)
if err != nil {
log.Fatal("arith error:", err)
}
fmt.Printf("Arith: %d/%d=%d 余数 %d \n", args.A, args.B, q.Quo,q.Rem)
wg.Done()
}
先运行服务端,再运行客户端,打印结果如下:
Arith: 9*8=72
Arith: 9*8=72
Arith: 9*8=72
Arith: 9*8=72
Arith: 9*8=72
Arith: 9/8=1 余数 1
Arith: 9/8=1 余数 1
Arith: 9/8=1 余数 1
Arith: 9/8=1 余数 1
Arith: 9/8=1 余数 1
在这个demo中,客户端使用5个并发协程,向服务端发起调用,并最终输出打印结果。
那客户端的调用代码其实只有一行:
err := client.Call(“Arith.Multiply”, args, &reply)
这里指定的远程服务调用的服务名和出入参
相比Go自己的net.Dial(),调用也简单了不少。因为RPC对编程人员来说没有了参数的封装和返回结果的解析,比如下面的例子,用的是go的http方式请求网络数据,代码中就包含数据流的关闭、数据流的解析和处理,而RPC则没有这些步骤,请求远程服务和处理远程服务返回的结果,就像是调用本地服务一样。
func main() {
resp, err := http.Get("http://www.baidu.com")
if err != nil {
fmt.Fprint(os.Stdout,err)
}
defer resp.Body.Close()
bytes, err := ioutil.ReadAll(resp.Body)
if err != nil {
fmt.Fprint(os.Stdout,err)
}
fmt.Println("返回打印结果:", string(bytes))
}
go中的RPC使用注意
我们再来看看官方的说明:
rpc包提供了通过网络或其他I/O连接对一个对象的导出方法的访问。服务端注册一个对象,使它作为一个服务被暴露,服务的名字是该对象的类型名。注册之后,对象的导出方法就可以被远程访问。服务端可以注册多个不同类型的对象(服务),但注册具有相同类型的多个对象是错误的。
只有满足如下标准的方法才能用于远程访问,其余方法会被忽略:
- 方法是导出的 - 方法有两个参数,都是导出类型或内建类型 - 方法的第二个参数是指针 - 方法只有一个error接口类型的返回值
结果是这样的:
func (t *T) MethodName(argType T1, replyType *T2) error
argType T1 表示传入的参数
replyType *T2表示返回的结果
error来判断方法调用是否发生错误
本节就到这里,休息一下…
参考资料:
https://studygolang.com/pkgdoc
Go语言编程(许式伟)