热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Golang中的RPC和gRPC

RPC采用客户端-服务器端的工作模式,请求程序就是一个客户端,而服务提供程序就是一个服务器端。当执行

一、RPC编程

  • 参考资料

    <> --许式伟

    gRPC介绍与安装 gRPC官方文档 gRPC中文文档 protocol-buffers
  • 介绍

RPC采用客户端-服务器端的工作模式,请求程序就是一个客户端,而服务提供程序就是一个服务器端。当执行一个远程过程调用时,客户端程序首先先发送一个带有参数的调用信息到服务端,然后等待服务端响应。在服务端,服务进程保持睡眠状态直到客户端的调用信息到达。当一个调用信息到达时,服务端获得进程参数,计算出结果,并向客户端发送应答信息。然后等待下一个调用。

  • Go语言中的RPC支持与处理

在 Go 中,标准库提供的net/rpc包实现了RPC协议需要的相关细节,开发者可以很方便的使用该包编写RPC的服务端和客户端程序。这使得用Go语言开发的多个进程之间的通信变得非常简单

  • 一个对象中只有满足如下条件的方法,才能被PRC服务端设置为可供远程访问

    1.必须是在对象外部可公开调用的方法(首字母大写)

    2.必须有两个参数,且参数的类型都必须是包外部可以访问的类型或者是Go內建支持的类型

    3.第二个参数必须是一个指针

    4.方法必须返回一个error类型的值

    用代码表示

    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中文文档

gRPC是一个高性能、开源、通用的RPC框架。基于 HTTP/2 协议标准设计开发,默认采用Protocol Buffers数据序列化协议([Protocol Buffers基本语法]()),支持多种开发语言。gRPC提供了一种简单的方法来精确的定义服务,并且为客户端和服务端自动生成可靠的功能库

  • gRPC应用场景

在gRPC客户端可以直接调用不通服务器上的远程程序,就想调用本地程序一样,很容易构建分布式应用和服务。和很多RPC系统一样,服务负责实现定义好的接口并处理客户端请求,客户端根据接口描述直接调用需要的服务。客户端和服务器可以分别使用gRPC支持的不同语言实现

Golang中的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


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 我们


推荐阅读
  • SOA架构理解理解SOA架构,了解ESB概念,明白SOA与微服务的区别和联系,了解SOA与热门技术的结合与应用。1、面向服务的架构SOASOA(ServiceOrien ... [详细]
  • 本文分享了一个关于在C#中使用异步代码的问题,作者在控制台中运行时代码正常工作,但在Windows窗体中却无法正常工作。作者尝试搜索局域网上的主机,但在窗体中计数器没有减少。文章提供了相关的代码和解决思路。 ... [详细]
  • 本文介绍了C#中生成随机数的三种方法,并分析了其中存在的问题。首先介绍了使用Random类生成随机数的默认方法,但在高并发情况下可能会出现重复的情况。接着通过循环生成了一系列随机数,进一步突显了这个问题。文章指出,随机数生成在任何编程语言中都是必备的功能,但Random类生成的随机数并不可靠。最后,提出了需要寻找其他可靠的随机数生成方法的建议。 ... [详细]
  • 解决Cydia数据库错误:could not open file /var/lib/dpkg/status 的方法
    本文介绍了解决iOS系统中Cydia数据库错误的方法。通过使用苹果电脑上的Impactor工具和NewTerm软件,以及ifunbox工具和终端命令,可以解决该问题。具体步骤包括下载所需工具、连接手机到电脑、安装NewTerm、下载ifunbox并注册Dropbox账号、下载并解压lib.zip文件、将lib文件夹拖入Books文件夹中,并将lib文件夹拷贝到/var/目录下。以上方法适用于已经越狱且出现Cydia数据库错误的iPhone手机。 ... [详细]
  • 本文介绍了RPC框架Thrift的安装环境变量配置与第一个实例,讲解了RPC的概念以及如何解决跨语言、c++客户端、web服务端、远程调用等需求。Thrift开发方便上手快,性能和稳定性也不错,适合初学者学习和使用。 ... [详细]
  • 本文介绍了Python爬虫技术基础篇面向对象高级编程(中)中的多重继承概念。通过继承,子类可以扩展父类的功能。文章以动物类层次的设计为例,讨论了按照不同分类方式设计类层次的复杂性和多重继承的优势。最后给出了哺乳动物和鸟类的设计示例,以及能跑、能飞、宠物类和非宠物类的增加对类数量的影响。 ... [详细]
  • (三)多表代码生成的实现方法
    本文介绍了一种实现多表代码生成的方法,使用了java代码和org.jeecg框架中的相关类和接口。通过设置主表配置,可以生成父子表的数据模型。 ... [详细]
  • Android系统源码分析Zygote和SystemServer启动过程详解
    本文详细解析了Android系统源码中Zygote和SystemServer的启动过程。首先介绍了系统framework层启动的内容,帮助理解四大组件的启动和管理过程。接着介绍了AMS、PMS等系统服务的作用和调用方式。然后详细分析了Zygote的启动过程,解释了Zygote在Android启动过程中的决定作用。最后通过时序图展示了整个过程。 ... [详细]
  • 本文介绍了RxJava在Android开发中的广泛应用以及其在事件总线(Event Bus)实现中的使用方法。RxJava是一种基于观察者模式的异步java库,可以提高开发效率、降低维护成本。通过RxJava,开发者可以实现事件的异步处理和链式操作。对于已经具备RxJava基础的开发者来说,本文将详细介绍如何利用RxJava实现事件总线,并提供了使用建议。 ... [详细]
  • Java和JavaScript是什么关系?java跟javaScript都是编程语言,只是java跟javaScript没有什么太大关系,一个是脚本语言(前端语言),一个是面向对象 ... [详细]
  • 本文介绍了H5游戏性能优化和调试技巧,包括从问题表象出发进行优化、排除外部问题导致的卡顿、帧率设定、减少drawcall的方法、UI优化和图集渲染等八个理念。对于游戏程序员来说,解决游戏性能问题是一个关键的任务,本文提供了一些有用的参考价值。摘要长度为183字。 ... [详细]
  • 后台自动化测试与持续部署实践
    后台自动化测试与持续部署实践https:mp.weixin.qq.comslqwGUCKZM0AvEw_xh-7BDA后台自动化测试与持续部署实践原创 腾讯程序员 腾讯技术工程 2 ... [详细]
  • 微服务下的几个难点问题及常见的解决方案
    原文链接:https:cloud.tencent.comdevelopernews1362051背景介绍1.1幂等性定义数学定义在数学里,幂等有 ... [详细]
  • 本文介绍了在Python3中如何使用选择文件对话框的格式打开和保存图片的方法。通过使用tkinter库中的filedialog模块的asksaveasfilename和askopenfilename函数,可以方便地选择要打开或保存的图片文件,并进行相关操作。具体的代码示例和操作步骤也被提供。 ... [详细]
  • 本文介绍了在多平台下进行条件编译的必要性,以及具体的实现方法。通过示例代码展示了如何使用条件编译来实现不同平台的功能。最后总结了只要接口相同,不同平台下的编译运行结果也会相同。 ... [详细]
author-avatar
vm经典全屏
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有