Golang远程调用框架RPC的具体使用

 更新时间:2022年12月14日 09:25:49   作者:梦想画家  
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)
}

到此这篇关于Golang远程调用框架RPC的具体使用的文章就介绍到这了,更多相关Go RPC内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Golang分布式锁详细介绍

    Golang分布式锁详细介绍

    分布式锁是控制分布式系统之间同步访问共享资源的一种方式。如果不同的系统或是同一个系统的不同主机之间共享了一个或一组资源,那么访问这些资源时,需要通过一些互斥手段来防止彼此之间的干扰以保证一致性,在这种情况下,就需要使用分布式锁了
    2022-10-10
  • golang并发执行的几种方式小结

    golang并发执行的几种方式小结

    本文主要介绍了golang并发执行的几种方式小结,主要包括了Channel,WaitGroup ,Context,使用这三种机制中的一种或者多种可以达到并发控制很好的效果,具有一定的参考价值,感兴趣的可以了解一下
    2023-08-08
  • golang如何使用gomobile进行Android开发

    golang如何使用gomobile进行Android开发

    golang可以开发android,使用golang开发android需要下载安装gomobile,下面这篇文章主要给大家介绍了关于golang如何使用gomobile进行Android开发的相关资料,需要的朋友可以参考下
    2023-01-01
  • Golang捕获panic堆栈信息的讲解

    Golang捕获panic堆栈信息的讲解

    今天小编就为大家分享一篇关于Golang捕获panic堆栈信息的讲解,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧
    2019-04-04
  • GO语言类型查询类型断言示例解析

    GO语言类型查询类型断言示例解析

    这篇文章主要为大家介绍了GO语言类型判断及类型断言,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步早日升职加薪
    2022-04-04
  • golang判断结构体为空的问题

    golang判断结构体为空的问题

    这篇文章主要介绍了golang判断结构体为空的问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2023-02-02
  • golang pprof监控memory block mutex使用指南

    golang pprof监控memory block mutex使用指南

    这篇文章主要为大家介绍了golang pprof监控memory block mutex使用指南,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-04-04
  • 使用go操作redis的有序集合(zset)

    使用go操作redis的有序集合(zset)

    这篇文章主要介绍了使用go操作redis的有序集合(zset),具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-12-12
  • go json转换实践中遇到的坑

    go json转换实践中遇到的坑

    在使用 go 语言开发过程中,经常需要使用到 json 包来进行 json 和 struct 的互相转换,这篇文章主要介绍了go json转换实践中遇到的坑,非常具有实用价值,需要的朋友可以参考下
    2018-12-12
  • Go连接数据库操作基础讲解

    Go连接数据库操作基础讲解

    这篇文章主要为大家介绍了Go连接数据库操作基础讲解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-12-12

最新评论