《Go语言高级编程》玩转RPC

一、客户端 RPC 实现原理:异步调用机制

Go 的 RPC 客户端支持同步和异步调用,核心在于 Client.Go 方法的实现:

1. 同步调用(Client.Call)的本质
func (client *Client) Call(serviceMethod string, args, reply interface{}) error {// 通过 Client.Go 发起异步调用,阻塞等待结果call := <-client.Go(serviceMethod, args, reply, make(chan *Call, 1)).Donereturn call.Error
}

同步调用本质是封装了异步流程:创建调用请求后,通过通道阻塞等待结果返回。

2. 异步调用(Client.Go)的流程
func (client *Client) Go(serviceMethod string, args, reply interface{}, done chan *Call) *Call {call := &Call{ServiceMethod: serviceMethod,Args: args,Reply: reply,Done: make(chan *Call, 10), // 带缓冲通道,避免阻塞}client.send(call) // 线程安全地发送调用请求return call
}

异步调用返回 Call 对象,调用完成后通过 call.Done 通道通知结果:

func (call *Call) done() {select {case call.Done <- call: // 结果写入通道default: // 通道满时不阻塞(由调用方保证缓冲区足够)}
}
3. 异步调用示例
func doClientWork(client *rpc.Client) {// 发起异步调用,不阻塞当前 goroutinecall := client.Go("HelloService.Hello", "hello", new(string), nil)// 执行其他任务...// 等待调用结果call = <-call.Doneif call.Error != nil {log.Fatal(call.Error)}fmt.Println("参数:", call.Args.(string), "响应:", *call.Reply.(*string))
}

核心优势:异步调用允许客户端在等待 RPC 结果时处理其他任务,提升并发性能。

二、基于 RPC 实现 Watch 监控功能

通过 RPC 实现实时监控(类似订阅-发布模式),以 KV 存储为例:

1. 服务端设计(KVStoreService
type KVStoreService struct {m      map[string]string       // KV 数据存储filter map[string]func(string) // 监控过滤器列表mu     sync.Mutex              // 互斥锁保护共享资源
}// 获取 KV 值
func (p *KVStoreService) Get(key string, value *string) error {p.mu.Lock(); defer p.mu.Unlock()if v, ok := p.m[key]; ok {*value = vreturn nil}return errors.New("not found")
}// 设置 KV 值,并触发监控回调
func (p *KVStoreService) Set(kv [2]string, reply *struct{}) error {p.mu.Lock(); defer p.mu.Unlock()key, value := kv[0], kv[1]if oldVal := p.m[key]; oldVal != value {for _, fn := range p.filter {fn(key) // 调用所有监控过滤器}}p.m[key] = valuereturn nil
}// 监控方法:注册过滤器,等待 key 变化或超时
func (p *KVStoreService) Watch(timeout int, keyChanged *string) error {id := "watch-" + time.Now().Format("150405") + "-" + strconv.Itoa(rand.Intn(1000))ch := make(chan string, 10)p.mu.Lock()p.filter[id] = func(key string) { ch <- key } // 注册过滤器p.mu.Unlock()select {case <-time.After(time.Duration(timeout) * time.Second):return errors.New("timeout")case key := <-ch:*keyChanged = keyreturn nil}
}
2. 客户端调用
func doClientWork(client *rpc.Client) {// 启动独立 goroutine 执行监控,阻塞等待 key 变化go func() {var key stringif err := client.Call("KVStoreService.Watch", 30, &key); err != nil {log.Fatal(err)}fmt.Println("监控到变化的 key:", key)}()// 修改 KV 值,触发监控回调if err := client.Call("KVStoreService.Set", [2]string{"abc", "new-value"}, new(struct{})); err != nil {log.Fatal(err)}time.Sleep(3 * time.Second)
}

核心原理

  • 服务端为每个 Watch 调用生成唯一 ID,绑定过滤器函数到 filter 列表。
  • Set 方法修改数据时,遍历调用所有过滤器,通过通道通知监控方。
  • 客户端通过异步 goroutine 阻塞监听,实现实时监控。
三、反向 RPC:内网服务主动连接外网

传统 RPC 是客户端连接服务端,反向 RPC 则相反,适用于内网服务无法被外网直接访问的场景:

1. 内网服务端(主动连接外网)
func main() {rpc.Register(new(HelloService)) // 注册服务for {// 主动连接外网服务器conn, err := net.Dial("tcp", "外网IP:1234")if err != nil {time.Sleep(1 * time.Second)continue}// 基于连接提供 RPC 服务rpc.ServeConn(conn)conn.Close()}
}
2. 外网客户端(监听连接)
func main() {listener, err := net.Listen("tcp", ":1234")if err != nil {log.Fatal(err)}clientChan := make(chan *rpc.Client)// 后台 goroutine 接受连接并创建客户端go func() {for {conn, err := listener.Accept()if err != nil {log.Fatal(err)}clientChan <- rpc.NewClient(conn) // 将客户端放入通道}}()doClientWork(clientChan) // 从通道获取客户端并调用
}func doClientWork(clientChan <-chan *rpc.Client) {client := <-clientChandefer client.Close()var reply stringif err := client.Call("HelloService.Hello", "hello", &reply); err != nil {log.Fatal(err)}fmt.Println(reply)
}

核心逻辑

  • 内网服务主动拨号外网服务器,建立连接后提供 RPC 服务。
  • 外网客户端监听端口,接收连接并转换为 RPC 客户端,通过通道传递给业务逻辑。
  • 适用于内网服务需被外网访问,但内网无法暴露端口的场景(如防火墙限制)。
四、上下文信息:基于连接的定制化服务

为每个 RPC 连接添加上下文(如认证状态、客户端信息),提升服务安全性和灵活性:

1. 服务端改造(包含连接和状态)
type HelloService struct {conn    net.Conn    // 连接对象,可获取客户端地址等信息isLogin bool        // 登录状态
}// 登录方法
func (p *HelloService) Login(request string, reply *string) error {if request != "user:password" {return errors.New("认证失败")}log.Println("登录成功")p.isLogin = true*reply = "登录成功"return nil
}// 需要认证的 Hello 方法
func (p *HelloService) Hello(request string, reply *string) error {if !p.isLogin {return errors.New("请先登录")}*reply = "hello:" + request + ", from " + p.conn.RemoteAddr().String()return nil
}
2. 服务端启动逻辑(为每个连接创建独立服务)
func main() {listener, err := net.Listen("tcp", ":1234")if err != nil {log.Fatal(err)}for {conn, err := listener.Accept()if err != nil {log.Fatal(err)}// 为每个连接启动独立 goroutine,绑定 HelloService 实例go func(c net.Conn) {defer c.Close()server := rpc.NewServer()server.Register(&HelloService{conn: c}) // 传入连接对象server.ServeConn(c)}(conn)}
}
3. 客户端调用流程
func main() {client, err := rpc.Dial("tcp", "localhost:1234")if err != nil {log.Fatal(err)}// 先登录var loginReply stringif err := client.Call("HelloService.Login", "user:password", &loginReply); err != nil {log.Fatal("登录失败:", err)}// 再调用 Hello 方法var helloReply stringif err := client.Call("HelloService.Hello", "world", &helloReply); err != nil {log.Fatal("调用失败:", err)}fmt.Println(helloReply) // 输出包含客户端地址的响应
}

核心优势

  • 通过 net.Conn 获取客户端上下文(如 IP 地址、连接状态)。
  • 基于连接状态实现认证逻辑(如登录验证),确保服务安全性。
  • 每个连接独立维护状态,避免多客户端数据混淆。
五、关键概念总结
  1. 异步调用:通过通道机制实现非阻塞 RPC 调用,提升客户端并发能力。
  2. Watch 机制:利用函数回调和通道,实现服务端数据变化的实时通知。
  3. 反向 RPC:打破传统 C/S 模式,适用于内网服务主动对外提供能力的场景。
  4. 上下文管理:基于连接绑定状态(如认证信息),实现定制化服务逻辑。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/web/87178.shtml
繁体地址,请注明出处:http://hk.pswp.cn/web/87178.shtml
英文地址,请注明出处:http://en.pswp.cn/web/87178.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

四大核心要素驱动汽车智能化创新与相关芯片竞争格局

作者&#xff1a;北京华兴万邦管理咨询有限公司 翔煜 商瑞 智能汽车时代的加速到来&#xff0c;使车载智能系统面临前所未有的算力需求。随着越来越多车型引入电子电气架构转向中心化、智能驾驶的多传感器融合、智能座舱的多模态交互以及生成式AI驱动的虚拟助手等创新技术&a…

照明新基建:塔能科技如何用数字骨骼支撑智慧城市生长

一、能源管理困局&#xff1a;双碳目标下的市政用电痛点 在双碳背景下&#xff0c;城市照明用电量已引起市政部门的重点关注。据国家统计局统计&#xff1a;我国城市照明用电量已占据全市城市用电量的28%&#xff0c;部分城市的照明用电量已高达35%以上&#xff0c;高压钠灯传统…

让Claude Code像Cursor一样好用

最近折腾AI工具&#xff0c;发现Claude Code真是个宝藏。但说实话&#xff0c;初学者一上手&#xff0c;十有八九会被命令行那一堆黑框框劝退。你以为你用熟了&#xff1f;其实你只解锁了Claude Code不到20%的威力&#xff0c;剩下的80%都藏在命令行背后的“黑魔法”里。00后谁…

ROS 2 中更改从设备(如电机控制器)的运动模式

在 ROS 2 中更改从设备&#xff08;如电机控制器&#xff09;的运动模式&#xff08;例如从位置模式切换到速度模式&#xff09;&#xff0c;需要通过操作模式&#xff08;Mode of Operation&#xff0c;对应对象字典索引0x6060&#xff09; 进行设置。结合你的配置&#xff08…

朴素贝叶斯分类

一、朴素贝叶斯算法概述 朴素贝叶斯(Naive Bayes)是一种基于贝叶斯定理的简单概率分类算法&#xff0c;它假设特征之间相互独立&#xff08;"朴素"的含义&#xff09;。尽管这个假设在现实中很少成立&#xff0c;但该算法在许多实际应用中表现优异&#xff0c;特别是…

python协程:yield实现协程执行、生成器取值的三种方式

yield关键字执行流程 注意&#xff1a;yield关键字的调用次数如果超过了任务执行次数会报错&#xff0c;提示stopiteration异常&#xff0c;例如 正常范围内的任务执行 # 定义一个任务&#xff08;函数1&#xff09; def task1():for i in range(3):print(f----task1 i {i}-…

pdf删除一页 python实现(已验证)

首先安装库 使用PyPDF2 首先&#xff0c;确保你已经安装了PyPDF2。如果没有安装&#xff0c;可以通过pip安装&#xff1a; pip install PyPDF2 然后运行 import PyPDF2def remove_page(input_pdf_path, output_pdf_path, page_number_to_remove):# 打开PDF文件with open(i…

2025.1版本PyCharam找不到已存在的conda虚拟环境

前言 创建Python项目指定conda虚拟环境是最常用的操作,我下载的2025.1版本PyCharam编译器找不到我已经创建好的conda虚拟环境,解决方法如下 目录 问题描述 问题解决 总结 问题描述 我使用2025.1版本PyCharam编译器创建项目指定已经存在的虚拟环境出现如下情景 说是我没有…

开机启动项在哪里设置 实用步骤分享

电脑开机时&#xff0c;系统会自动运行一系列程序&#xff0c;其中包括必要的系统进程和用户自行添加的启动项。然而&#xff0c;过多的启动项可能会导致开机速度变慢&#xff0c;影响系统性能。因此&#xff0c;合理管理开机启动项&#xff0c;可以优化电脑的运行效率。电脑开…

LeetCode--39.组合总和

前引&#xff1a;明天就考最后一趟考试&#xff0c;最近考试周&#xff0c;我时时断更&#xff0c;从明天开始&#xff0c;就会一直更新了&#xff0c;可以期待一下 解题思路&#xff1a; 1.获取信息&#xff1a; 给定一个无重复的整数数组和一个目标值 从数组中选取任意数量的…

Visual Studio2022和C++opencv的配置保姆级教程

1.c桌面开发和windows平台开发&#xff08;Visual Studio2022安装时&#xff09; 2.下载OPenCV 3.系统属性→添加环境变量→Path 4.VS2022配置opencv 5.项目→属性→VC目录中的包含目录和库目录 5.项目→属性→VC目录中的包含目录和库目录 包含 目录添加&#xff1a; D:\…

使用Ansible的playbook安装HTTP

实验环境 安装好ansible 一、准备测试服务&#xff08;192.168.10.41&#xff09; 1、安装HTTP服务 dnf -y install httpd 2、启动HTTP服务 systemctl start httpd 3、使用浏览器访问 192.168.10.41 因为开启了防火墙&#xff0c;所有无法访问 4、开放防火墙的80端口 …

V少JS基础班之第六弹

一、 前言 第六弹内容是闭包。 距离上次函数的发布已经过去了一个多月&#xff0c; 最近事情比较多&#xff0c;很少有时间去写文章&#xff0c; 低质量还得保证所以本章放草稿箱一个月了&#xff0c;终于补齐了&#xff0c;其实还有很多细节要展开说明&#xff0c;想着拖太久…

【面板数据】全国高频交易明细数据(2000-2024.7)

中国土地交易市场作为国家宏观调控的重要组成部分&#xff0c;其通过市场机制&#xff0c;使土地使用权在不同主体间流转&#xff0c;将土地资源配置给最具利用效率的部门或企业&#xff0c;提升土地利用率和经济产出。中国土地高频交易明细数据汇集了全国范围内2000-2024年7月…

MongoDB 常用增删改查方法及示例

MongoDB 的增删改查&#xff08;CRUD&#xff09;操作是其核心功能&#xff0c;主要通过 mongo shell 或驱动&#xff08;如 Node.js、Python 等&#xff09;实现。以下是最常用操作的详细说明及示例&#xff08;基于 mongo shell 语法&#xff09;。 ​一、插入操作&#xff…

moodle升级(4.5到5.0)

升级目标 由Moodle 4.5 (Build: 20241129) 升级到Moodle 5.0.1 (Build: 20250629) 参考教程&#xff1a;moodle升级&#xff08;详细版&#xff09;-CSDN博客 操作平台&#xff1a;宝塔 通过宝塔进行备份 备份文件 将/www/wwwroot/moodle 和/www/wwwroot/moodledata 复制…

基于Apache POI实现百度POI分类快速导入PostgreSQL数据库实战

## 引言:POI数据的价值与挑战 POI(Point of Interest)数据作为地理信息系统的核心要素,在智慧城市、位置服务、商业分析等领域具有重要价值。百度POI数据包含了丰富的地点信息(如名称、类别、坐标等),但如何高效处理这些数据并将其导入数据库进行分析是开发者面临的挑战…

linux LAMP 3

[rootcode apache2]# bin/apachectl AH00558: httpd: Could not reliably determine the server’s fully qualified domain name, using fe80::20c:29ff:fe2a:708a. Set the ‘ServerName’ directive globally to suppress this message root192.168.235.5s password:┌─…

UI自动化-Selenium WebDriver

前言 Selenium WebDriver 是 Selenium 项目中最核心、最强大的组件&#xff0c;它是一个用于自动化控制网页浏览器的开源 API&#xff08;应用程序编程接口&#xff09;。 简单来说&#xff0c;Selenium WebDriver 就是一个允许你用编程语言&#xff08;如 Java、Python、C#、…

具身多模态大模型在感知与交互方面的综述

引言在本学期方老师的《机器人与大模型》课上&#xff0c;我首次接触到了关于具身智能的前沿知识&#xff0c;尤其作为课上交互组的成员&#xff0c;从表情识别到语音交互到机械狗的开发实践进行了一些有意思的探索&#xff0c;使我在其中感受到了具身智能的巨大魅力和无限潜力…