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
猜你喜欢
- Python2.7还是一个比较稳定的版本,目前80%以上的公司都在使用python2.7的版本。他不会在安装的时候报编码错误之类的问题。但是
- Sequence Typessequence类型有六种:strings, byte sequences (bytes objects), b
- 一、文件形式的邮件#!/usr/bin/env python3#coding: utf-8import smtplibfrom email.
- 函数描述int(x [,base])将x转换为一个整数long(x [,base] )将x转换为一个长整数float(x)将x转换到一个浮点
- 看代码吧~package mainimport ("fmt""io""net/http&q
- 一、MHA介绍(一)、什么是MHAMHA(MasterHigh Availability)是一套优秀的MySQL高可用环境下故障切换和主从复
- PDOStatement::errorCodePDOStatement::errorCode — 获取跟上一次语句句柄操作相关的 SQLST
- 描述cmp() 方法用于比较两个列表的元素。语法cmp()方法语法:cmp(list1, list2)参数list1 -- 比较的列表。li
- 我正在参加天池上的一个竞赛,刚开始用的是DenseNet121但是效果没有达到预期,因此开始尝试使用模型融合,将Desenet和Xcepti
- 概述Alwayson相对于数据库镜像最大的优势就是可读副本,带来可读副本的同时还添加了一个新的功能就是配置只读路由实现读写分离;当然这里的读
- 回车和换行的历史:机械打字机有回车和换行两个键作用分别是:换行就是把滚筒卷一格,不改变水平位置。 (即移到下一行,但不是行首,而是和上一行水
- javascript作为一个动态语言,动态解析脚本的方法非常多,如万恶又万能的eval,低调的Function,IE独占的execScrip
- 1、变量的定义定义变量语法格式:variable_name = variable_namevariable_name表示变量名;variab
- 您是否记得关闭所有的XHTML元素,在HTML中一些元素没有必要被关闭。当下一个元素开始的时候,上一个元素就自动被关闭。XHTML中是不允许
- 上次介绍了Access 导入 MSSQL 2000/2005 数据库工具,不过,在使用这个工具时还是有一些地方需要注意的,我把整个导入过程记
- 安装显示目录功能:pip install jupyter_contrib_nbextensions配置:安装完之后需要配置 nbextens
- 解释器模式(Interpreter Pattern)是什么解释器模式是一种行为型模式,它定义了一种语言文法,并且定义了一个解释器,用来解释这
- 光的干涉干涉即两束光在叠加过程中出现的强度周期性变化情况,其最简单的案例即为杨氏双缝干涉。如图所示,光从 S S S点发出,通过两个狭缝 S
- 从照片里面获取GPS信息。可交换图像文件常被简称为EXIF(Exchangeable
- 背景之前是用的是typora来写的文章,最近typora最近开始收费了,所以就不想用了,于是找到了一个替代品MarkText,感觉跟typo