Golang远程调用框架RPC的具体使用
作者:梦想画家 发布时间:2024-02-16 01:50:02
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)
}
来源:https://blog.csdn.net/neweastsun/article/details/128269912


猜你喜欢
- 通用用法但上图的字段名,类型需要根据不同接口填写,如某服务接口:因而对应的上传代码如下:# 输出参数:请求响应报文import reques
- 在并不知道DIV浮动层的宽度,高度的情况下,如何使浮动层在任何分辩率的情况下居中呢! 下面使用了JAVASCRIPT来
- 一、安装插件npm install -D vuedraggable二、在需要排序的界面中引入组件<script>import d
- 参考的一些文章以及论文我都会给大家分享出来 —— 链接就贴在原文,论文我上传到资源中去,大家可以免费下载学习,如果当天资源区找不到论文,那就
- 1、安装requests、xlrd、json、unittest库<1>pip 命令安装:pip install requests
- 问题描述字符串本身作为 bytess = '\xe4\xbd\xa0\xe5\xa5\xbd'解决方案s.encode(
- 不同点: 1. truncate和 delete只删除数据不删除表的结构(定义) &
- 元组(tuple)与列表类似,但是元组是不可修改的 (immutable)。也就是说,元组一旦被创建就不可被修改了。操作符 (in、+、*)
- 提叻一个代码段,要人帮助解释一下。 代码段如下: declare type t_indexby is table of number ind
- python3用到2个库import itertoolsimport metacomm.combinatorics.all_pairs2 a
- 前言密码是个很私密的东西它一直关联着一系列的机密事物,二战中密码起了很大的作用。在我们生活中尤其是现在我们手机上存在着许多的app是我们生活
- 一、汉诺塔问题1. 问题来源问题源于印度的一个古老传说,大梵天创造世界的时候做了三根金刚石柱子,在一根柱子上从下往上按照大小顺序摞着64片黄
- Python装饰器用法Python的装饰器是个好东西,它能干很多事情。但对于新手,它看起来似乎没那么简单。但事实上,装饰器本身也只是个函数。
- opencv读取和写入路径有汉字的处理读取图片 img_gt = cv2.imdecode(np.fromfile(path, d
- 正文方法一:直接使用已知的cookie访问特点:简单,但需要先在浏览器登录原理:简单地说,cookie保存在发起请求的客户端中,服务器利用c
- 对于开发使用到数据库的应用,免不了就要使用联合查询,SQL中常用的联合查询有inner join、outer join和cross join
- 这是一个简易的员工管理系统,实现最简单的功能:1.登录用户密码验证(错误三次自动退出) 2.支持文本员工的搜索、添加、删除、修改 3.一级层
- 浏览器中某些计算和处理要比其他的昂贵的多。例如,DOM操作比起非DOM交互需要更多的内存和CPU时间。连续尝试进行过多的DOM相关操作可能会
- 一、闭包闭包相当于函数中,嵌套另一个函数,并返回。代码如下:def func(name): # 定义外层函数 def inner
- amend翻译成中文:修改1.修改还未push的提交//修改最近一次提交git commit --amend//进入注释页面,进行修改//修