本地过程调用
package main
import "fmt"
func Add(a, b int) int {
return a + b
}
func main() {
r := Add(2, 3)
fmt.Println(r)
}
远程过程调用
RPC角色
- 客户端(Client):**服务调用发起方,也称为服务消费者。
- 客户端存根(Client Stub):该程序运行在客户端所在的计算机机器上,主要用来存储要调用的服务器的地址,另外,该程序还负责将客户端请求远端服务器程序的数据信息打包成数据包,通过网络发送给服务端Stub程序;其次,还要接收服务端Stub程序发送的调用结果数据包,并解析返回给客户端。
- 服务端(Server):远端的计算机机器上运行的程序,其中有客户端要调用的方法。
服务端存根(Server Stub):接收客户Stub程序通过网络发送的请求消息数据包,并调用服务端中真正的程序功能方法,完成功能调用;其次,将服务端执行调用的结果进行数据处理打包发送给客户端Stub程序。
调用流程
RPC每一步的调用过程。具体描述为:
1、客户端想要发起一个远程过程调用,首先通过调用本地客户端Stub程序的方式调用想要使用的功能方法名;
- 2、客户端Stub程序接收到了客户端的功能调用请求,将客户端请求调用的方法名,携带的参数等信息做序列化操作,并打包成数据包。
- 3、客户端Stub查找到远程服务器程序的IP地址,调用Socket通信协议,通过网络发送给服务端。
- 4、服务端Stub程序接收到客户端发送的数据包信息,并通过约定好的协议将数据进行反序列化,得到请求的方法名和请求参数等信息。
- 5、服务端Stub程序准备相关数据,调用本地Server对应的功能方法进行,并传入相应的参数,进行业务处理。
- 6、服务端程序根据已有业务逻辑执行调用过程,待业务执行结束,将执行结果返回给服务端Stub程序。
- 7、服务端Stub程序将程序调用结果按照约定的协议进行序列化,并通过网络发送回客户端Stub程序。
- 8、客户端Stub程序接收到服务端Stub发送的返回数据,对数据进行反序列化操作,并将调用返回的数据传递给客户端请求发起者。
- 9、客户端请求发起者得到调用结果,整个RPC调用过程结束。
RPC官方库
在Go语言官方网站的pkg说明中,提供了官方支持的rpc包,具体链接如下:https://golang.org/pkg/net/rpc/。官方提供的rpc包完整的包名是:net/rpc。根据官方的解释,rpc包主要是提供通过网络访问一个对象方法的功能。
开发流程
server服务定义及暴露
在编程实现过程中,服务器端需要注册结构体对象,然后通过对象所属的方法暴露给调用者,从而提供服务,该方法称之为输出方法,此输出方法可以被远程调用。当然,在定义输出方法时,能够被远程调用的方法需要遵循一定的规则。我们通过代码进行讲解:
type T struct {}// 定义注册结构体
func (t *T) MethodName(request T1,response *T2) error// 定义输出方法
eg:
type Calculate struct {}
type AddPara struct {
Arga int
Argb int
}
func(c *Calculate)Square(req int,resp *int)error{
*resp =req*req
return nil
}
func(c *Calculate)Add(req AddPara, resp *int)error{
*resp =req.Arga+req.Argb
return nil
}
server注册服务及监听
HelloWorld
服务端 helloservice.go
package main
import (
"log"
"net"
"net/rpc"
)
type HelloService struct {}
func (h *HelloService)Hello(request string,response *string)error{
*response = "hello: "+request
return nil
}
func main() {
rpc.RegisterName("HelloService",new(HelloService))
listener, err := net.Listen("tcp", ":8080")
if err!= nil {
log.Fatal(err)
}
for {
conn, err := listener.Accept()
if err != nil {
log.Fatal("Accept error:", err)
}
go rpc.ServeConn(conn)
}
}
启动服务端工程
go run helloservice.go
客户端
package main
import (
"fmt"
"log"
"net/rpc"
)
func main() {
client, err := rpc.Dial("tcp", "127.0.0.1:8080")
if err != nil {
log.Fatal(err)
}
var r string
err = client.Call("HelloService.Hello", "world", &r)
if err != nil {
log.Fatal(err)
}
fmt.Println(r)
}
启动客户端
$ go run helloclient.go
hello: world
第二个例子
创建请求模型
package model
type OperationReq struct {
A int
B int
}
type OperationResp struct {
Result int
}
创建service 实现了加法和减法运算的服务
package main
import (
"github.com/baxiang/go-note/gprc/rpc/model"
"log"
"net"
"net/rpc"
"net/http"
)
type Operation struct {
}
func (o *Operation) Add(req model.OperationReq, resp *model.OperationResp) error {
resp.Result = req.A + req.B
return nil
}
func (o *Operation) Sub(req model.OperationReq, resp *model.OperationResp) error {
resp.Result = req.A - req.B
return nil
}
func main() {
rpc.Register(&Operation{})
rpc.HandleHTTP()
lis, err := net.Listen("tcp", "127.0.0.1:8080")
if err != nil {
log.Fatalln("fatal error: ", err)
}
http.Serve(lis, nil)
}
client
package main
import (
"net/rpc"
"log"
"github.com/baxiang/go-note/gprc/rpc/model"
"fmt"
)
func main() {
conn, err := rpc.DialHTTP("tcp", "127.0.0.1:8080")
if err != nil {
log.Fatalln("http error: ", err)
}
req := model.OperationReq{A: 5, B: 2}
var res model.OperationResp
err = conn.Call("Operation.Add", req, &res) // 乘法运算
if err != nil {
log.Fatalln("add error: ", err)
}
fmt.Printf("%d + %d = %d\n", req.A, req.B, res.Result)
err = conn.Call("Operation.Sub", req, &res)
if err != nil {
log.Fatalln("sub error: ", err)
}
fmt.Printf("%d - %d = %d\n", req.A, req.B, res.Result)
}
gRPC使用protobuf来定义服务。protobuf是由Google开发的一种数据序列化协议,可以把它想象成是XML或JSON格式,但更小,更快更简洁。而且一次定义,可生成多种语言的代码。
jsonrpc
但目前的jsonrpc库是基于tcp协议实现的,暂时不支持使用http进行数据传输。
service
package main
import (
"github.com/baxiang/go-note/gprc/model"
"log"
"net"
"net/rpc"
"net/rpc/jsonrpc"
)
type Operation struct {
}
func (o *Operation) Add(req model.OperationReq, resp *model.OperationResp) error {
resp.Result = req.A + req.B
return nil
}
func (o *Operation) Sub(req model.OperationReq, resp *model.OperationResp) error {
resp.Result = req.A - req.B
return nil
}
func main() {
rpc.Register(&Operation{})
lis, err := net.Listen("tcp", ":8081")
if err != nil {
log.Fatalln("fatal error: ", err)
}
for {
conn, err := lis.Accept() // 接收客户端连接请求
if err != nil {
continue
}
go func(conn net.Conn) { // 并发处理客户端请求
jsonrpc.ServeConn(conn)
}(conn)
}
}
JSON RPC采用JSON进行数据编解码,因而支持跨语言调用。
package main
import (
"github.com/baxiang/go-note/gprc/model"
"log"
"fmt"
"net/rpc/jsonrpc"
)
func main() {
conn, err := jsonrpc.Dial("tcp", "127.0.0.1:8081")
if err != nil {
log.Fatalln("http error: ", err)
}
req := model.OperationReq{A: 5, B: 2}
var res model.OperationResp
err = conn.Call("Operation.Add", req, &res) // 乘法运算
if err != nil {
log.Fatalln("add error: ", err)
}
fmt.Printf("%d + %d = %d\n", req.A, req.B, res.Result)
err = conn.Call("Operation.Sub", req, &res)
if err != nil {
log.Fatalln("sub error: ", err)
}
fmt.Printf("%d - %d = %d\n", req.A, req.B, res.Result)
}