Golang网络编程四 RPC

什么是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语言编程(许式伟)