内容简介:RPC采用客户端-服务器端的工作模式,请求程序就是一个客户端,而服务提供程序就是一个服务器端。当执行一个远程过程调用时,客户端程序首先先发送一个带有参数的调用信息到服务端,然后等待服务端响应。在服务端,服务进程保持睡眠状态直到客户端的调用信息到达。当一个调用信息到达时,服务端获得进程参数,计算出结果,并向客户端发送应答信息。然后等待下一个调用。在Go中,标准库提供的net/rpc包实现了RPC协议需要的相关细节,开发者可以很方便的使用该包编写RPC的服务端和客户端程序。这使得用Go语言开发的多个进程之间的
一、RPC编程
- 参考资料
- 介绍
RPC采用客户端-服务器端的工作模式,请求程序就是一个客户端,而服务提供程序就是一个服务器端。当执行一个远程过程调用时,客户端程序首先先发送一个带有参数的调用信息到服务端,然后等待服务端响应。在服务端,服务进程保持睡眠状态直到客户端的调用信息到达。当一个调用信息到达时,服务端获得进程参数,计算出结果,并向客户端发送应答信息。然后等待下一个调用。
- Go语言中的RPC支持与处理
在 Go 中,标准库提供的net/rpc包实现了RPC协议需要的相关细节,开发者可以很方便的使用该包编写RPC的服务端和客户端程序。这使得用Go语言开发的多个进程之间的通信变得非常简单
-
一个对象中只有满足如下条件的方法,才能被PRC服务端设置为可供远程访问
1.<font color="Brown">必须是在对象外部可公开调用的方法(首字母大写)</font>
2.<font color="Brown">必须有两个参数,且参数的类型都必须是包外部可以访问的类型或者是Go內建支持的类型</font>
3.<font color="Brown">第二个参数必须是一个指针</font>
4.<font color="Brown">方法必须返回一个error类型的值</font>
用代码表示
func (t T*)MethodName(argType T1,replyType *T2)error
在上面这行代码中,类型 T T1 T2 默认会使用Go内置的encoding/gob包进行编码和解码
改方法的第一个参数表示由PRC客户端传入的参数,第二个参数表示要返回给PRC客户端的结果。改方法最后返回一个error类型
- RPC客户端和服务器端的使用
RPC服务端可以通过调用 ```rpc.ServerConn```处理单个连接请求。多数情况下,通过tcp或是http在某个网络地址上监听然后再创建该服务是个不错的选择 在RPC客户端,Go的net/rpc包提供了便利的```rpc.Dial()```和```rpc.DialHTTP()```方法来与指定的RPC服务建立连接。在建立连接之后,Go的net/rpc包允许我们使用通过或者异步的方式接受RPC服务端的结果。调用RPC客户端的```Call()```方法则进行同步处理。这个时候客户端程序按照顺序执行。当调用RPC客户端的```Go()```方法时,则进行异步处理。客户端无需等待服务端的结果即可执行后面的程序,当接收到服务端响应时,再对其进行相应的处理。 无论是哪个方法,都必须要指定要调用的服务及其方法名称,以及一个客户端传入参数的引用,还有一个用于接收处理结果参数的指针 如果没有指定RPC传输过程中使用何种编码解码器,默认使用Go标准库提供的eccoding/gob包进行数据传输
- 代码示例
服务器端代码
package main
import (
"errors"
"log"
"net"
"net/http"
"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 {
time.Sleep(time.Second * 3) //睡三秒,同步调用会等待,异步会先往下执行
*reply = args.A * args.B
return nil
}
//计算商和余数
func (t *Arith) Divide(args *Args, quo *Quotient) error {
time.Sleep(time.Second * 3)
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服务注册了一个arith对象 公开方法供客户端调用
rpc.Register(arith)
//指定rpc的传输协议 这里采用http协议作为rpc调用的载体 也可以用rpc.ServeConn处理单个连接请求
rpc.HandleHTTP()
l, e := net.Listen("tcp", ":1234")
if e != nil {
log.Fatal("listen error", e)
}
go http.Serve(l, nil)
os.Stdin.Read(make([]byte, 1))
}
客户端代码
package main
import (
"fmt"
"log"
"net/rpc"
"time"
)
type Args struct {
A, B int
}
type Quotient struct {
Quo, Rem int
}
func main() {
//调用rpc服务端提供的方法之前,先与rpc服务端建立连接
client, err := rpc.DialHTTP("tcp", "127.0.0.1:1234")
if err != nil {
log.Fatal("dialHttp error", err)
return
}
//同步调用服务端提供的方法
args := &Args{7, 8}
var reply int
//可以查看源码 其实Call同步调用是用异步调用实现的。后续再详细学习
err = client.Call("Arith.Multiply", args, &reply) //这里会阻塞三秒
if err != nil {
log.Fatal("call arith.Multiply error", err)
}
fmt.Printf("Arith:%d*%d=%d\n", args.A, args.B, reply)
//异步调用
quo := Quotient{}
divCall := client.Go("Arith.Divide", args, &quo, nil)
//使用select模型监听通道有数据时执行,否则执行后续程序
for {
select {
case <-divCall.Done:
fmt.Printf("商是%d,余数是%d\n", quo.Quo, quo.Rem)
default:
fmt.Println("继续向下执行....")
time.Sleep(time.Second * 1)
}
}
}
说明
//Go函数的原型,注意其最后一个参数是一个channel 也就是调用结果存到了这个channel里 里面的类型数据是*Call类型
//如果你不传递这个channel这个参数,Go函数内部会默认创建一个*Call类型的10个长度的channel来缓存结果数据。channel的名字叫做
//Done也就是返回值*Calll俩面的最后一个值
//好吧,其实我就是想说,cient.Go的返回值包含了最后一个参数(channel),想获取调用结果,可以从参数管道中直接获取,也可以从返回值
//Done中获取
// Call represents an active RPC.
type Call struct {
ServiceMethod string // The name of the service and method to call.
Args interface{} // The argument to the function (*struct).
Reply interface{} // The reply from the function (*struct).
Error error // After completion, the error status.
Done chan *Call // Strobes when call is complete.
}
func (client *Client) Go(serviceMethod string, args interface{}, reply interface{}, done chan *Call) *Call {
二 gRpc
- gRPC是什么
gRPC是一个高性能、开源、通用的RPC框架。基于 HTTP/2 协议标准设计开发,默认采用Protocol Buffers数据序列化协议([Protocol Buffers基本语法]()),支持多种开发语言。gRPC提供了一种简单的方法来精确的定义服务,并且为客户端和服务端自动生成可靠的功能库
- gRPC应用场景
在gRPC客户端可以直接调用不通服务器上的远程程序,就想调用本地程序一样,很容易构建分布式应用和服务。和很多RPC系统一样,服务负责实现定义好的接口并处理客户端请求,客户端根据接口描述直接调用需要的服务。客户端和服务器可以分别使用gRPC支持的不同语言实现
- 安装
protobuf Golang插件
执行完成后会在GOPATH/bin目录下生成 protoc-gen-go
工具,在编译.proto文件时,protoc命令需要用到此插件
[关于protobuf语法和基本使用]()
go get -u github.com/golang/protobuf/{proto,protoc-gen-go}
grpc-go golang第三方库下载(需要翻墙用ss配置http代理完成终端翻墙))
go get -u google.golang.org/grpc
- gPRC列子 hello gRPC
流程
1.编写.proto描述文件 2.编译生成.pb.go文件 3.客户端实现约定的接口并提供服务 4.客户端按照约定调用方法请求服务
目录结构
$GOPATH/src/go_demo/
23gPRC/
|—— hello/
|—— client/
|—— main.go // 客户端
|—— server/
|—— main.go // 服务端
|—— proto/
|—— hello.proto // proto描述文件
|—— hello.pb.go // proto编译后文件
- 代码示例
proto rpc服务描述文件
syntax = "proto3"; //指定proto版本
package proto;
//定义请求结构
message HelloRequest{
string name=1;
}
//定义响应结构
message HelloReply{
string message=1;
}
//定义Hello服务
service Hello{
//定义服务中的方法
rpc SayHello(HelloRequest)returns (HelloReply){}
}
protoc -I . --go_out=plugins=grpc:. ./hello.proto
hello.proto文件中定义了一个Hello Service 该服务包含了一个SayHello方法 同时声明了HelloRequest和HelloReply消息结构
用于请求和响应。客户端使用HelloRequest参数调用SayHello方法请求服务端 服务端响应HelloReply消息
根据hello.proto文件编译生成Golang源文件 hello.pb.go
源文件中包含消息传递的请求和响应结构。服务端注册对象的方法 创建客户端 以及调用服务端方法
服务器端代码
package main
import (
"fmt"
pb "go_demo/23gRPC/proto"
"net"
"golang.org/x/net/context"
"google.golang.org/grpc"
)
const (
//gRPC服务地址
Address = "127.0.0.1:50052"
)
//定义一个helloServer并实现约定的接口
type helloService struct{}
func (h helloService) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
resp := new(pb.HelloReply)
resp.Message = "hello" + in.Name + "."
return resp, nil
}
var HelloServer = helloService{}
func main() {
listen, err := net.Listen("tcp", Address)
if err != nil {
fmt.Printf("failed to listen:%v", err)
}
//实现gRPC Server
s := grpc.NewServer()
//注册helloServer为客户端提供服务
pb.RegisterHelloServer(s, HelloServer) //内部调用了s.RegisterServer()
fmt.Println("Listen on" + Address)
s.Serve(listen)
}
go run main.go
客户端代码
package main
import (
"fmt"
pb "go_demo/23gRPC/proto"
"golang.org/x/net/context"
"google.golang.org/grpc"
)
const (
Address = "127.0.0.1:50052"
)
func main() {
//连接gRPC服务器
conn, err := grpc.Dial(Address, grpc.WithInsecure())
if err != nil {
fmt.Println(err)
}
defer conn.Close()
//初始化客户端
c := pb.NewHelloClient(conn)
//调用方法
reqBody := new(pb.HelloRequest)
reqBody.Name = "gRPC"
r, err := c.SayHello(context.Background(), reqBody)
if err != nil {
fmt.Println(err)
}
fmt.Println(r.Message)
}
go run main.go
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
PHP for the World Wide Web, Second Edition (Visual QuickStart Gu
Larry Ullman / Peachpit Press / 2004-02-02 / USD 29.99
So you know HTML, even JavaScript, but the idea of learning an actual programming language like PHP terrifies you? Well, stop quaking and get going with this easy task-based guide! Aimed at beginning ......一起来看看 《PHP for the World Wide Web, Second Edition (Visual QuickStart Gu》 这本书的介绍吧!