11 Go RPC

一、RPC 概述

RPC(Remote Procedure Call)—远程过程调用,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。

通俗地讲,一般在一个程序内,一个函数的调用栈是在程序结构内部完成的,即一个函数内调用另一个函数,只需简单的Call函数名。但有没有可能让你在调用外部的功能函数像调用内部函数那么简单直观?这就需要远程过程调用,要实现跨网络的外部调用,你需要构建一个C/S架构的程序,而远程通信的双方都遵守相同的通信协议,这就是RPC。

二、Go标准库提供基本的RPC

1. net/rpc

Go标准库提供net/rpc包支持构建RPC架构的程序

rpc包提供了通过网络或其他I/O连接对一个对象的导出方法的访问。服务端注册一个对象,使它作为一个服务被暴露,服务的名字是该对象的类型名。注册之后,对象的导出方法就可以被远程访问。服务端可以注册多个不同类型的对象(服务),但注册具有相同类型的多个对象是错误的。

只有满足如下标准的方法才能用于远程访问,其余方法会被忽略:

  • 方法是导出的
  • 方法有两个参数,都是导出类型或内建类型
  • 方法的第二个参数是指针
  • 方法只有一个error接口类型的返回值
编写服务端:rpc/server.go
package main

import (
    "fmt"
    "net"
    "net/rpc"
    "os"
)

func ErrorHandler(err error, where string) {
    if err != nil {
        fmt.Println("发现错误,", err, "出错位置:", where)
        os.Exit(1)
    }
}


type HelloService struct{}

func (hs *HelloService) Hello(request string, reply *string) error {
    *reply = "Hello " + request
    return nil
}


func main() {
    fmt.Println("启动rpc服务...")
    //注册一个RPC服务
    err := rpc.RegisterName("HelloService", new(HelloService))
    ErrorHandler(err, "rpc.RegisterName")

    //监听一个端口并开始接收监听数据
    listener, err := net.Listen("tcp", ":12138")
    ErrorHandler(err, "net.Listen")
    conn, err := listener.Accept()
    ErrorHandler(err, "listener.Accept")

    //rpc服务连接
    rpc.ServeConn(conn)

    fmt.Println("rpc进程退出!!!!")

}

编写客户端:rpc/client.go
package main

import (
    "fmt"
    "net/rpc"
)

func main() {
    //rpc拨号连接
    client, err := rpc.Dial("tcp", "localhost:12138")
    ErrorHandler(err, "rpc.Dail")

    //声明一个接收响应的数据
    var reply string
    err = client.Call("HelloService.Hello", "Fun", &reply)
    ErrorHandler(err, "client.Call")

    fmt.Println("请求rpc服务返回数据:", reply)
}

编译执行服务端和客户端生成可执行文件,先执行rpc服务端,在执行rpc客户端,即可发生简单的rpc通信。

2. net/rpc/jsonrpc

jsonrpc包实现了JSON-RPC的ClientCodec和ServerCodec接口,可用于rpc包。其只是将通信协议的消息结构转换成json传输。

编写服务端:jsonrpc/server.go
package main
import (
    "fmt"
    "net"
    "net/rpc"
    "net/rpc/jsonrpc"
    "os"
)

func ErrorHandler(err error, where string) {
    if err != nil {
        fmt.Println("发现错误,", err, "出错位置:", where)
        os.Exit(1)
    }
}

type HelloService struct{}

func (hs *HelloService) Hello(request string, reply *string) error {
    *reply = "Hello " + request
    return nil
}
func main() {
    fmt.Println("启动基于JSON通信的RPC服务...")
    //注册RPC服务名
    err := rpc.RegisterName("HelloService", new(HelloService))
    ErrorHandler(err, "rpc.RegisterName")
    
    //启动一个监听端口
    listener, err := net.Listen("tcp", ":12138")
    ErrorHandler(err, "net.Listen")
    
    //每来一个连接开一个协程处理json通信数据
    for {
        conn, err := listener.Accept()
        ErrorHandler(err, "listener.Accept")
        go rpc.ServeCodec(jsonrpc.NewServerCodec(conn))
    }
}
编写客户端:jsonrpc/client.go
func main() {
    //拨号RPC服务获取一个连接
    conn, err := net.Dial("tcp", "localhost:12138")
    ErrorHandler(err, "rpc.Dail")

    //创建一个处理json通信数据的客户端
    client := rpc.NewClientWithCodec(jsonrpc.NewClientCodec(conn))

    //使用客户端发送数据给服务端
    var reply string
    err = client.Call("HelloService.Hello", "Fuc", &reply)
    ErrorHandler(err, "client.Call")

    fmt.Println("请求rpc服务返回数据:", reply)

}

编译执行服务端和客户端生成可执行文件,先执行rpc服务端,在执行rpc客户端,即可发生简单的rpc通信。

三、gRPC与protobuf

1.gRPC概述

gRPC是Google开发的一套高性能RPC框架,默认使用protobuf协议,与现今流行的Thrift框架类型,它也有自己的一套IDL语法和编译引擎,执行定义IDL文件就可生成相关的协议结构和服务。

2.protobuf概述

Protobuf是Google开发的一种平台无关、语言无关、可扩展且轻便高效的序列化数据结构的协议,可以用于网络通信和数据存储。

官方介绍:https://developers.google.cn/protocol-buffers/

与XML和JSON格式相比,protobuf更小、更快、更便捷。protobuf是跨语言的,自带一套IDL协议定义语法和一个编译器(protoc),安装完protoc后,只需要用protoc进行编译,就可以编译成Java、Python、C++、C#、Go等多种语言代码,项目中可以直接引用生成的协议包,不需要编写传输协议相关的代码。
protobuf还可根据RPC框架的不同自定义协议的结构,如gRPC只需在protoc编译.proto文件是附带参数:--go_out=plugins=grpc: 即可。

相关的protoc安装、IDL语法不便展开,在此直接演示

3.一个简单演示:

1.基于protobuf定义传输协议:

syntax = "proto3"; //设置语法版本
package proto; //声明包名:通常为文件所在目录名

//service 关键字定义开放调用的接口
service UserInfoService{
    //rpc关键字定义该服务内开放调用的方法
    rpc GetUserInfo (UserRequest) returns (UserResponse){}
}


//message关键字定义请求/响应的通信数据结构
//请求的信息结构体
message UserRequest {
    //[修饰符] 类型 字段名=标识符,标识符可视为属性的顺序号
    string name = 1;
}
//响应的信息结构体
message UserResponse {
    int32 id = 1;
    string name = 2;
    int32 age = 3;
    //repeated 表示字段是可变数组,即slice类型
    repeated string title = 4;
}

2.生成proto依赖包:

Makefile

build:
    protoc --go_out=plugins=grpc:./ ./proto/*.proto
    #生成适用于gRPC的代码,在生成时设置其插件为grpc,
    #如有其它rpc运行库,可针对其定制proto插件


#使用protoc命令:protoc -I=$SRC_DIR --go_out=$DST_DIR $SRC_DIR/addressbook.proto

3.编写gRPC服务端:grpc_demo/server/mian.go

package main

import (
    "fmt"
    "golang.org/x/net/context"
    "google.golang.org/grpc"
    pb "grpc_demo/proto" //引入proto协议生成的依赖包,别名pb
    "net"
    "os"
)

func ErrorHandler(err error, where string) {
    if err != nil {
        fmt.Println("发现错误,", err, "出错位置:", where)
        os.Exit(1)
    }
}

//实现proto生成的服务接口
type UserInfoService struct{}

func (s *UserInfoService) GetUserInfo(ctx context.Context, req *pb.UserRequest) (resp *pb.UserResponse, err error) {
    name := req.Name

    //模拟在数据库查找数据得到结果
    if name == "fun" {
        resp = &pb.UserResponse{
            Id:    100,
            Name:  "fun",
            Age:   30,
            Title: []string{"Teacher", "Coder"},
        }
    }
    err = nil
    return
}

//声明以上的服务,等待在主函数注册
var us = UserInfoService{}

//服务函数
func main() {
    //开一个监听端口
    listener, err := net.Listen("tcp", ":12138")
    ErrorHandler(err, "net.Listen")
    fmt.Println("开启grpc监听服务,端口:12138")

    //创建一个grpc服务
    server := grpc.NewServer()

    //将上面实现UserInfoService的服务注册到grpc生成的代码
    pb.RegisterUserInfoServiceServer(server, &us)

    //启动服务监听
    server.Serve(listener)
}

4.编写gRPC客户端:grpc_demo/client/mian.go

package main

import (
    "fmt"
    "golang.org/x/net/context"
    "google.golang.org/grpc"
    pb "grpc_demo/proto"
    "os"
)

func ErrorHandler(err error, where string) {
    if err != nil {
        fmt.Println("发现错误,", err, "出错位置:", where)
        os.Exit(1)
    }
}

func main() {
    //拨号连接
    clientConn, err := grpc.Dial(":12138", grpc.WithInsecure())
    ErrorHandler(err, "grpc.Dial")
    defer clientConn.Close()

    //实例化微服务客户端
    userInfoServiceClient := pb.NewUserInfoServiceClient(clientConn)

    //封装请求数据
    request := new(pb.UserRequest)
    request.Name = "fun"

    //发起rpc请求
    userResponse, err := userInfoServiceClient.GetUserInfo(context.Background(), request)
    ErrorHandler(err, "client.GetUserInfo")

    //打印请求数据
    fmt.Println("请求数据为:", userResponse)
}

只需分别编译服务端和客户端生成可执行文件,分别启动即可执行简单的通信。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容

  • gRPC 是一个高性能、通用的开源RPC框架,基于HTTP/2协议标准和Protobuf序列化协议开发,支持众多的...
    小波同学阅读 19,406评论 6 19
  • 简单介绍RPC协议及常见框架,对比传统restful api和RPC方式的优缺点。常见RPC框架,gRPC及序列化...
    王胜广阅读 146,151评论 9 158
  • 原文出处:gRPC gRPC分享 概述 gRPC 一开始由 google 开发,是一款语言中立、平台中立、开源的远...
    小波同学阅读 7,190评论 0 18
  • 今天回到家里我让大宝把在托辅写的作业拿出来我看看,发现大宝没有写口算题,我让大宝吃完饭后做一些口算题,我说:咱们的...
    李妈妈阅读 122评论 0 1
  • 唯有自己一个人的时候,才会觉得这个世界是无比的安静与美妙。 因为你可以听到自己内心的声音,可以听到内心最真实的想法...
    希雅的花园阅读 173评论 1 3