Golang远程调用框架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内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!