本网站(662p.com)打包出售,且带程序代码数据,662p.com域名,程序内核采用TP框架开发,需要联系扣扣:2360248666 /wx:lianweikj
精品域名一口价出售:1y1m.com(350元) ,6b7b.com(400元) , 5k5j.com(380元) , yayj.com(1800元), jiongzhun.com(1000元) , niuzen.com(2800元) , zennei.com(5000元)
需要联系扣扣:2360248666 /wx:lianweikj
Golang远程调用框架RPC的具体使用
iamgeek · 133浏览 · 发布于2022-12-14 +关注

Remote Procedure Call (RPC) 是一种使用TCP协议从另一个系统调用应用程序功能执行的方法。Go有原生支持RPC服务器实现,本文通过简单实例介绍RPC的实现过程

gRPC

gRPC远程过程调用框架是基于动作的模式,类似远程调用微服务。这使得gRPC成为一种围绕Protobufs构建的进程间通信(IPC)协议,用于处理客户端和服务器之间的消息传递。gRPC非常适合密集而高效的通信,因为它支持客户端和服务器流。

与REST对比

REST是一种基于资源的协议,客户端根据请求体告诉服务器需要创建、读取、更新或删除哪些资源。gRPC还可以通过protobuf来定义接口,从而可以有更加严格的接口约束条件。gRPC为什么比REST更快?

gRPC利用HTTP/2协议,提供多种方式提供性能:

  • 报头压缩和重用以减少消息大小

  • 在单个TCP连接上同时发送多个请求和接收多个响应的多路复用

  • 持久TCP连接用于单个TCP连接上的多个连续请求和响应

  • 二进制格式支持,如协议缓冲区

需求说明

Go RPC服务器允许注册任何Go类型及其方法,通过RPC协议公开这些方法,即可以从远程客户端按名称进行调用。我们要实现的需求可以简单描述为:

  • 用户可以添加书籍的信息

  • 用户添加书籍的阅读进度

  • 用户查询书籍的阅读进度

当然我们还可以实现其他更复杂的功能,这里为了简化,仅说明这几个方法。下面首先定义Book类型:

// Book represents a book entry
type Book struct {
ISBN string
Title, Author string
Year, Pages int
}
// ReadingList keeps tracks of books and pages read
type ReadingList struct {
Books []Book
Progress []int
}

ReadingList 类内部包括两个slice,模拟数据库存储书籍及对应进度。下面定义助手方法:

func (r *ReadingList) bookIndex(isbn string) int {
    for i := range r.Books {
        if isbn == r.Books[i].ISBN {
            return i
        }
    }
    return -1
}

上面通过ISBN查询书籍对应索引号,现在继续在ReadingList类型上定义几个方法:

// AddBook checks if the book is not present and adds it
func (r *ReadingList) AddBook(b Book) error {
    if b.ISBN == "" {
        return ErrISBN
    }
    if r.bookIndex(b.ISBN) != -1 {
        return ErrDuplicate
    }
    r.Books = append(r.Books, b)
    r.Progress = append(r.Progress, 0)
    return nil
}
// GetProgress returns the progress of a book
func (r *ReadingList) GetProgress(isbn string) (int, error) {
    if isbn == "" {
        return -1, ErrISBN
    }
    i := r.bookIndex(isbn)
    if i == -1 {
        return -1, ErrMissing
    }
    return r.Progress[i], nil
}
// 设置进度
func (r *ReadingList) SetProgress(isbn string, pages int) error {
    if isbn == "" {
        return ErrISBN
    }
    i := r.bookIndex(isbn)
    if i == -1 {
        return ErrMissing
    }
    if p := r.Books[i].Pages; pages > p {
        pages = p
    }
    r.Progress[i] = pages
    return nil
}

我们需求就是通过RPC协议公开这些方法,让客户端进行远程调用。下面实现RPC服务器。

创建RPC服务器

上节已经准备好了公开的方法,创建RPC服务需要遵守一些规则:

  • 方法的类型和方法自身必须是公开的(大写开头)

  • 方法有两个参数,类型也是公开的

  • 第二个参数是指针类型

  • 方法返回一个error类型

语法如下:

 func (t *T) Method(in T1, out *T2) error

知道了方法规范,下面对上节的方法进行包装,首先我们定义几个错误类型:

// List of errors
var (
    ErrISBN = fmt.Errorf("missing ISBN")
    ErrDuplicate = fmt.Errorf("duplicate book")
    ErrMissing = fmt.Errorf("missing book")
)

再定义一个助手方法:

// sets the success pointer value from error
func setSuccess(err error, b *bool) error {
    *b = err == nil
    return err
}

首先实现Addbook和GetProgress包装方法:

func (r *ReadingService) AddBook(b Book, success *bool) error {
    return setSuccess(r.ReadingList.AddBook(b), success)
}
func (r *ReadingService) GetProgress(isbn string, pages *int) (err error) {
    *pages, err = r.ReadingList.GetProgress(isbn)
    return err
}

由于更新阅读进度需要传入两个参数,但rpc方法不能传入多个参数,因此我们定义类型进行参数封装:

type Progress struct {
    ISBN  string
    Pages int
}
func (r *ReadingService) SetProgress(p Progress, success *bool) error {
    return setSuccess(r.ReadingList.SetProgress(p.ISBN, p.Pages), success)
}

准备了包装方法,下面定义RPC服务就很简单了:

func main() {
    if err := rpc.Register(&book.ReadingService{}); err != nil {
        log.Fatalln(err)
    }
    rpc.HandleHTTP()
    l, err := net.Listen("tcp", ":8900")
    if err != nil {
        log.Fatalln(err)
    }
    log.Println("Server Started")
    if err := http.Serve(l, nil); err != nil {
        log.Fatal(err)
    }
}

服务端实现完毕,下面实现客户端。基础类型Book,Progress再客户端也需要使用,因此可以独立定义进行共享。

// Book represents a book entry
type Book struct {
    ISBN          string
    Title, Author string
    Year, Pages   int
}
type Progress struct {
    ISBN  string
    Pages int
}

实现客户端

首先定义测试数据:

const hp = "H.P. Lovecraft"
var books = []Book{
    {ISBN: "1540335534", Author: hp, Title: "The Call of Cthulhu", Pages: 36},
    {ISBN: "1980722803", Author: hp, Title: "The Dunwich Horror ", Pages: 53},
    {ISBN: "197620299X", Author: hp, Title: "The Shadow Over Innsmouth", Pages: 40},
    {ISBN: "1540335536", Author: hp, Title: "The Case of Charles Dexter Ward", Pages: 176},
}

下面定义rpc客户端:

client, err := rpc.DialHTTP("tcp", ":8900")
if err != nil {
    log.Fatalln(err)
}
defer client.Close()

连接服务端后,可以调用服务端方法,下面定义统一方法进行调用:

func callClient(client *rpc.Client, method string, in, out interface{}) {
    var r interface{}
    if err := client.Call(method, in, out); err != nil {
        out = err
    }
    switch v := out.(type) {
    case error:
        r = v
    case *int:
        r = *v
    case *bool:
        r = *v
    }
    log.Printf("%s: [%+v] -> %+v", method, in, r)
}

最后是循环测试数据,依此调用RPC方法:

 for i, book := range books {
        callClient(client, "ReadingService.AddBook", book, new(bool))
         callClient(client, "ReadingService.SetProgress", Progress{
            ISBN:  book.ISBN,
            Pages: 10 + i*i,
        }, new(bool))
         callClient(client, "ReadingService.GetProgress", book.ISBN, new(int))
    }
}

完整代码如下:

package main
import (
    log "log"
    "net/rpc"
)
func main() {
    client, err := rpc.DialHTTP("tcp", ":8900")
    if err != nil {
        log.Fatalln(err)
    }
    defer client.Close()
    for i, book := range books {
        callClient(client, "ReadingService.AddBook", book, new(bool))
        callClient(client, "ReadingService.SetProgress", Progress{
            ISBN:  book.ISBN,
            Pages: 10 + i*i,
        }, new(bool))
        callClient(client, "ReadingService.GetProgress", book.ISBN, new(int))
    }
}
// Book represents a book entry
type Book struct {
    ISBN          string
    Title, Author string
    Year, Pages   int
}
type Progress struct {
    ISBN  string
    Pages int
}
const hp = "H.P. Lovecraft"
var books = []Book{
    {ISBN: "1540335534", Author: hp, Title: "The Call of Cthulhu", Pages: 36},
    {ISBN: "1980722803", Author: hp, Title: "The Dunwich Horror ", Pages: 53},
    {ISBN: "197620299X", Author: hp, Title: "The Shadow Over Innsmouth", Pages: 40},
    {ISBN: "1540335536", Author: hp, Title: "The Case of Charles Dexter Ward", Pages: 176},
}
func callClient(client *rpc.Client, method string, in, out interface{}) {
    var r interface{}
    if err := client.Call(method, in, out); err != nil {
        out = err
    }
    switch v := out.(type) {
    case error:
        r = v
    case *int:
        r = *v
    case *bool:
        r = *v
    }
    log.Printf("%s: [%+v] -> %+v", method, in, r)
}


相关推荐

PHP实现部分字符隐藏

沙雕mars · 1325浏览 · 2019-04-28 09:47:56
Java中ArrayList和LinkedList区别

kenrry1992 · 908浏览 · 2019-05-08 21:14:54
Tomcat 下载及安装配置

manongba · 970浏览 · 2019-05-13 21:03:56
JAVA变量介绍

manongba · 963浏览 · 2019-05-13 21:05:52
什么是SpringBoot

iamitnan · 1087浏览 · 2019-05-14 22:20:36
加载中

0评论

评论
努力追求卓越,极致,这么一个GEEK人,希望可以跟大家认识,交流一下。
分类专栏
小鸟云服务器
扫码进入手机网页