【Go】四、rpc跨语言编程基础与rpc的调用基础原理
Go管理工具
早期 Go 语言不使用 go module 进行包管理,而是使用 go path 进行包管理,这种管理方式十分老旧,两者最显著的区别就是:Go Path 创建之后没有 go.mod 文件被创建出来,而 go module 模式会创建出一个 go.mod 文件用于管理包信息
现在就是:尽量使用 Go Modules 模式
另外,我们在引入包的时候,可以先进行 import 再通过编译器来下载内容,这样能让我们更简便的处理包关系
Go 编码规范
命名规范
命名首字母大写 被视为 Public,小写被视为 Private
包名和目录名应为小写,不带有下划线与驼峰
文件名应为全小写,其中可能带有的多个单词以下划线分隔
对于接口的命名,我们应该在末尾加上 ‘er’ 来标注
常量使用全大写命名,中间使用下划线隔开
Go 语言中的包分为三种:Go 语言自带的标准库中的包、第三方包、自己写的包
RPC
rpc 是指 remote procedure call 也就是远程节点调用,其实就是一个节点调用另一个节点
这之中最关键的三个问题是:Call的id映射、序列化与反序列化、网络传输
Call id 映射问题解决的是:系统A的程序想要远程调用系统B的程序时,B中有许多个程序,到底调用哪个程序的问题,也就是说,B系统中的每个程序都具有一个唯一 id,只要其他系统在发起远程调用时携带自己要调用程序的 Call id,系统B就能成功识别他想要运行的程序
我们的调用逻辑是:
将传递的参数使用 json 协议进行传输(类似的协议还有 xml、protobuf、msgpack)另外现在网络调用有两个端:客户端用于发送数据,服务器端用于接收数据
另外一个问题就是:JSON 不是一个高性能的编码协议,我们在追求极致性能的时候可能不会优先考虑 json
另外:json 的优势在于其通用性,可扩展性,几乎所有的系统都支持 json,但其另外的问题就是其过于灵活,不能将其作为程序的对象存储来代替struct
而我们的网络协议是看不懂 struct 的,其只能识别二进制的流,故而我们必须将我们转换的数据转换成二进制的流才可以进行传输
在一次 RPC 过程中,服务器端和客户端分别要做的事情:
客户端:
1. 建立连接:tcp \ http
2. 将我们要发送的数据序列化为 json 字符串 - 序列化
3. 发送,实际上发送的是二进制流
4. 等待服务器结果
5. 服务器返回结果,客户端将结果反序列化为可识别数据
服务器端:
1. 监听网络端口(80)
2. 读取客户端发来的二进制数据,并将其转换成Employee对象,反序列化
3. 处理数据,生成一个带有更完成信息的对象,例如:R,其中封装了404、201等信息
4. 将处理的数据结果转换成 json 二进制, 序列化 发送给客户端
我们的序列化技术不一定非要使用 json、我们还可以选择:xml、protobuf、msgpack 等
我们只要解决了 序列化问题,其实就解决了数据互通问题,其实也就屏蔽了我们相互调用时的语言不同的问题(java、python、go)
网络问题:
我们使用 http 与 tcp 最大的区别就是:
http是一次性的,建立连接之后,一旦收到数据的返回,tcp 连接就断开,而 tcp 连接是可以复用的,解决了 tcp 连接要重新建立的问题
另外,我们也可以使用 http2.0 来解决这个问题,http2.0 支持长连接,可以解决连接的建立问题
一个简单的例子:(服务器端)
func main() {http.HandleFunc("/add", func(w http.ResponseWriter, r *http.Request) {// 这里面写逻辑_ = r.ParseForm() // 解析参数,可能会报错fmt.Println("path: ", r.URL.Path)a, _ := strconv.Atoi(r.Form["a"][0])b, _ := strconv.Atoi(r.Form["a"][0])// 进行返回w.Header().Set("Content-Type", "application/json")// 构建返回体jData, _ := json.Marshal(map[string]int{"data": a + b,})// 真正写入w.Write(jData)})// 设置监听的端口_ = http.ListenAndServe(":8000", nil)
}
上面这个例子就典型的解决了:
Call ID问题:使用URL路径指明要调用的方法
数据传输协议:Http 的参数传递协议
但这里的问题是:使用的是 http1.0的协议,性能低,手写http,数据需要自己解析,效率低
客户端举例:
我们也可以不写客户端,直接使用浏览器来发送请求解决问题,
我们访问:127.0.0.1:8000/add?a=1&b=4
RPC 技术原理:
客户端发送请求,由客户端存根处理,客户端存根将请求整理成协议对应的格式进行发送,将其发送到服务器端,由服务器端接收这之后发送给服务器端存根进行解码,再返回给服务器处理
RPC 开发(Hello World级别)
创建目录结构
Project
server
server.go
client
client.go
server.go:
type HelloService struct {
}// 给这个类绑定这个方法
func (s *HelloService) Hello(request string, reply *string) error {// 通过修改 reply 值来进行返回*reply = "hello, " + requestreturn nil
}/*
*
1. 实例化一个server
2. 注册逻辑
3. 开启服务
*/
func main() {// 第一步:实例化 serverlistener, _ := net.Listen("tcp", ":1234")// 第二步:将我们的 struct 注册进 RPC// 我们如果把形参列表中的参数定义为 interface{} 就代表这个参数我们可以任意传入_ = rpc.RegisterName("HelloService", &HelloService{})// 第三部:启动服务,绑定rpcconn, _ := listener.Accept()rpc.ServeConn(conn)
}
client.go:
func main() {// Dial() 意思是拨号,也就是尝试进行连接,同时进行Gob进行编码client, err := rpc.Dial("tcp", "localhost:1234")if err != nil {panic("链接失败")}// 注意这种方式会直接开辟一片空间,并且给这片空间的 string 赋予初值 ''//var reply string// 如果以下面这种方式就复杂一点var replyy *string = new(string)// 发送请求,请求对应的方法,其后面的参数是传入的参数,根据我们方法的编写,我们最后一个参数用来接收数据err = client.Call("HelloService.Hello", "Chen", replyy)if err != nil {panic("方法调用出错")}fmt.Println(*replyy)}hello, Chen
注意在上面的代码中,实例化 server、启动服务都是由 net 包完成的,但单独 net 包是不能完成一整个流程的,这是因为还有 call id 的匹配以及序列化机制是由 rpc 来完成的
另外的是:GRPC在此时还不够简洁,使用效率不够高,这体现在包括客户端在调用时不能直接调用方法,而是需要明确方法名等
同时,上面这种最基本的rpc使用的编码解码协议是 Gob,这只能在 go 语言中进行通信,其不支持跨语言
使用JSON协议的RPC
建立目录结构:
json_rpc_test
server
server.go
client
client.go
server.go
type HelloService struct {
}func (s *HelloService) Hello(request string, reply *string) error {// 通过修改 reply 值来进行返回*reply = "hello, " + requestreturn nil
}
func main() {listener, _ := net.Listen("tcp", ":1234")_ = rpc.RegisterName("HelloService", &HelloService{})// 允许服务器处理多次请求:for {conn, _ := listener.Accept()// 使用自定义协议进行修改go rpc.ServeCodec(jsonrpc.NewServerCodec(conn)) // 这里不加 go 协程会出现多个请求的并发问题}
}
client.go
func main() {// 使用基础的拨号,不进行编码,编码在后面进行conn, err := net.Dial("tcp", "localhost:1234")if err != nil {panic("链接失败")}// 进行 json 编码client := rpc.NewClientWithCodec(jsonrpc.NewClientCodec(conn))var replyy *string = new(string)err = client.Call("HelloService.Hello", "Chen", replyy)if err != nil {panic("方法调用出错")}fmt.Println(*replyy)}
测试 RPC 的跨语言特性:
使用 python 的socket 编程发送一个json (不使用http,因为我们服务器没有使用http,无法进行解析)
import json
import socketrequest = {"id":0,"params":["Zhang"],"method":"HelloService.Hello"
}client = socket.create_connection(("localhost", 1234))
client.sendall(json.dumps(request).encode())# 获取服务器返回的数据
rsp = client.recv(1024)
rsp = json.loads(rsp.decode())
print(rsp["result"])hello, Zhang
使用 java:
public static void main(String[] args) {try {// Connect to the serverSocket socket = new Socket("localhost", 1234);// Create a JSON requestString jsonRequest = "{\"id\": 0, \"method\": \"HelloService.Hello\", \"params\": [\"Yang\"]}";// Send the JSON request to the serverOutputStream outputStream = socket.getOutputStream();outputStream.write(jsonRequest.getBytes());outputStream.flush();// Receive the response from the serverBufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));String response = reader.readLine();System.out.println("Response from server: " + response);// Close the socketsocket.close();} catch (IOException e) {e.printStackTrace();}}Response from server: {"id":0,"result":"hello, Yang","error":null}
使用 HTTP 协议 + JSON 的RPC
其实在这一步,已经有成熟框架可以使用,这里为了学习,我们使用rpc搭建一个自己的框架
构建目录:
http_rpc_test
server
server.go
client
client.go
server.go
type HelloService struct {
}func (s *HelloService) Hello(request string, reply *string) error {// 通过修改 reply 值来进行返回*reply = "hello, " + requestreturn nil
}
func main() {_ = rpc.RegisterName("HelloService", &HelloService{})http.HandleFunc("/jsonrpc", func(w http.ResponseWriter, r *http.Request) {var conn io.ReadWriteCloser = struct {io.Writerio.ReadCloser}{ReadCloser: r.Body,Writer: w,}rpc.ServeRequest(jsonrpc.NewServerCodec(conn))})http.ListenAndServe(":1234", nil)
}
在这里就可以使用其他语言发送 http 请求,获取结果了
将方法改造成调用式的方法
以基础的 HelloWorld 级别代码为例,构建目录:
new_helloworld
server
server.go
client
client.go
handler
handler.go
server_proxy
server_proxy.go
client_proxy
client_proxy.go
我们通过定义一个公共文件,来实现:
handler.go
const HelloServiceName = "handler/HelloService"type HelloService struct{}// 给这个类绑定这个方法
func (s *HelloService) Hello(request string, reply *string) error {// 通过修改 reply 值来进行返回*reply = "hello, " + requestreturn nil
}
server.go
func main() {// 第一步:实例化 serverlistener, _ := net.Listen("tcp", ":1234")// 第二步:将我们的 struct 注册进 RPC// 我们如果把形参列表中的参数定义为 interface{} 就代表这个参数我们可以任意传入_ = rpc.RegisterName(handler.HelloServiceName, &handler.HelloService{})// 第三部:启动服务,绑定rpcfor {conn, _ := listener.Accept()go rpc.ServeConn(conn)}
client.go
func main() {// Dial() 意思是拨号,也就是尝试进行连接client := client_proxy.NewHelloServiceClient("tcp", "127.0.0.1:1234")// 注意这种方式会直接开辟一片空间,并且给这片空间的 string 赋予初值 ''//var reply string// 如果以下面这种方式就复杂一点var replyy *string = new(string)// 发送请求,请求对应的方法,其后面的参数是传入的参数,根据我们方法的编写,我们最后一个参数用来接收数据_ = client.Hello("Chen", replyy)fmt.Println(*replyy)
}
server_proxy.go
type HellosSrvicer interface {Hello(request string, reply *string) error
}func RegisterHelloService(srv HellosSrvicer) error {return rpc.RegisterName(handler.HelloServiceName, srv)
}
client_proxy.go
type HelloServiceStub struct {*rpc.Client
}// 初始化,在Go中使用 Newxxx进行初始化
func NewHelloServiceClient(protcol, address string) HelloServiceStub {conn, err := rpc.Dial(protcol, address)if err != nil {panic("connect error")}return HelloServiceStub{conn}
}func (c *HelloServiceStub) Hello(request string, reply *string) error {err := c.Call(handler.HelloServiceName+".Hello", request, reply)return err
}
相关文章:

【Go】四、rpc跨语言编程基础与rpc的调用基础原理
Go管理工具 早期 Go 语言不使用 go module 进行包管理,而是使用 go path 进行包管理,这种管理方式十分老旧,两者最显著的区别就是:Go Path 创建之后没有 go.mod 文件被创建出来,而 go module 模式会创建出一个 go.mod…...

Linux CentOS系统安装SQL Server并结合内网穿透实现公网访问本地数据
🔥博客主页: 小羊失眠啦. 🎥系列专栏:《C语言》 《数据结构》 《C》 《Linux》 《Cpolar》 ❤️感谢大家点赞👍收藏⭐评论✍️ 前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默&…...

输入捕获模式测频率PWM输入模式(PWMI)测占空比
一、概念介绍 输出比较: 比较电路输入的CNT、CCR大小关系 ,在通道引脚输出高低电平 二、*频率知识、测量方法补充 * N/fc得到标准频率的时长,也就是待测频率的周期 测频法代码实现:修改对射式红外传感器计次(上升沿…...

解锁VIP会员漫画:用Python爬虫轻松实现高清漫画下载
嗨喽~大家好呀,这里是魔王呐 ❤ ~! python更多源码/资料/解答/教程等 点击此处跳转文末名片免费获取 环境使用: Python 3.10 Pycharm 模块使用: requests >>> pip install requests 数据请求模块 parsel >>> pip install parsel 数据解析模块…...

备战蓝桥杯---动态规划(入门3之子串问题)
本专题再介绍几种经典的字串问题。 这是一个两个不重叠字串和的问题,我们只要去枚举分界点c即可,我们不妨让c作为右区间的左边界,然后求[1,c)上的单个字串和并用max数组维护。对于右边,我们只要反向求单个字串和然后选左边界为c的…...

JavaScript:隐式类型转换与显式类型转换
文章目录 隐式类型转换(Implicit Type Conversion)1、字符串与数字的转换2、非布尔值到布尔值的转换3、在相等性比较中的转换4、对象到基础类型的转换5、在算术运算符中的其他转换 显式类型转换(Explicit Type Conversion)1、Numb…...

【电路笔记】-LR串联电路
LR串联电路 文章目录 LR串联电路1、概述2、示例1所有线圈、电感器、扼流圈和变压器都会在其周围产生磁场,由电感与电阻串联组成,形成 LR 串联电路。 1、概述 在本节有关电感器的第一个文章中,我们简要介绍了电感器的时间常数,指出流过电感器的电流不会瞬时变化,而是会以恒…...

Ansible 自动化运维工具的使用
目录 Ansible的简介 ansible 环境安装部署 ansible 命令行模块 command 模块 shell 模块 cron 模块 user 模块 group 模块 copy 模块 file 模块 hostname 模块 ping 模块 yum 模块 service/systemd 模块 script 模块 mount 模块 archive 模块 unarchive 模…...

亚马逊、ozon、速卖通、Lazada等跨境平台为什么评论老是被删
对于卖家而言,最难的并不是销售量,最难的是让客户在购买后能够留下一个高质量的review,毕竟现在的市场,以listing的排名为基准,以review数量多少和质量的高低来评判店铺的好坏 几乎所有的卖家都会有索评的烦恼&#x…...

手把手带你在Linux上安装带GPU加速的opencv库(C++版本)
1.安装依赖 sudo apt-get install build-essential cmake git libgtk2.0-dev pkg-config libavcodec-dev libavformat-dev libswscale-dev sudo apt-get install python-dev python-numpy python3-dev python3-numpy sudo apt-get install libtbb2 libtbb-dev libjpeg-dev l…...

【Linux】软件包管理器 yum | vim编辑器
前言: 软件包管理器 yum和vim编辑器讲解 文章目录 软件包管理器 yum编辑器-vim四种模式普通模式批量化注释和批量化去注释末行模式临时文件 软件包管理器 yum yum(Yellowdog Updater, Modified)是一个在基于 RPM(管理软件包的格式和工具集合&…...

vue常见问题
文章目录 data为什么是一个函数,而不是一个对象?什么情况下可以使用对象?key的作用,为什么不能用Index?render函数,h函数,和template什么关系?vue 是怎么解析template的? template会…...

ArcgisForJS基础
文章目录 0.引言1.第一个ArcgisForJS应用程序1.1.安装部署ArcgisForJS1.2.实现ArcgisForJS应用程序 2.开发与调试工具2.1.集成开发环境2.2.调试工具2.3.Firebug 0.引言 ArcGIS API for JavaScript是一款由Esri公司开发的用于创建WebGIS应用的JavaScript库。它允许开发者通过调…...

白话微机:5.解释串行接口以及一些考研面试问题
一. 前言(回顾世界观) 很久很久以前,有这样一个世界,这个世界有着现实世界一样的元素:那里的人又有一个别的名字叫做“数据”,人有0有1;人们也有住房,这些住房在这个世界叫做“存储器…...

版本控制(Git)
Fork 本课程网站的仓库 将版本历史可视化并进行探索是谁最后修改了 README.md文件?(提示:使用 git log 命令并添加合适的参数)最后一次修改_config.yml 文件中 collections: 行时的提交信息是什么?(提示&am…...

USB-C音频转接器:实现边充电边听歌的新选择 | LDR6020P
随着科技浪潮的推进,Type-C接口已逐渐成为电子设备的主流选择,以其正反随意插、高速传输和强大功能等独特优势,在日常生活中占据越来越重要的地位。而Type-C音频转接器,作为连接Type-C接口与音频设备的桥梁,正引领着音…...

C/C++ 怎么把多个静态库给整合成一个静态库?
来源:https://www.wikitechy.com/tutorials/linux/how-to-merge-two-ar-static-libraries-into-one 使用 libtool (这也是可移植性最强的方式)(但这通常要求两个子库也是 libtool 制作的) libtool --modelink cc -static -o libaz.la libab…...

OBD部署OceanBase集群-配置文件方式
前一篇文章介绍了OBD白屏可视化方式部署OceanBase集群 ,其原理是把可视化设置生成为一个配置文件,然后使用OBD命令部署集群 本篇想使用命令行加配置文件方式,只部署OceanBase和ODProxy两个组件 服务器参数配置和 oceanbase-all-in-one-*.ta…...

Flink介绍
Flink 介绍 文章目录 Flink 介绍1. 简介1.1 背景1.2 用途 2. 核心概念2.1 流(Stream)2.2 转换(Transformation)2.3 窗口(Window)2.4 状态(State) 3. 编程模型3.1 编程模型介绍3.2 程…...

vscode突然连不上服务器了,以前都可以的,并且ssh等其它方式是可以连接到服务器的
过完年回来准备开工干活,突然发现vscode连不上服务器了,奇了怪了,年前都可以的,看了一下报错,如下, 以为是服务器挂了,结果执行ssh xxxxxx 发现是可以远程连接的,看来服务器没有问题…...

【shell】Shell学习后篇
Linux 常用 Shell 文章目录 Linux 常用 ShellBanner设置字体颜色设置提示操作系统操作系统版本号系统处理器架构关闭防火墙和SELinux系统操作防火墙相关获取当前目录判断文件是否存在判断目录是否存在后台挂起静默执行判断之前的命令是否成功 Banner 设置字体颜色 RED\033[31…...

协同程序原理
一、协程的本质 //协程可以分为两个部分 //1.协程函数本体 //2.协程调度器 //协程本体就是一个能够中间暂停返回的函数 //协程调度器是Unity内部实现的,会在对应的时机帮我们继续执行协程函数 //Unity只实现了协程调度器部分 //协程的本体本质上就是 C#的一个迭代…...

怎样保证数据库和redis里的数据一致性
使用缓存更新策略:在更新数据库时,同时更新Redis中相应的数据。这可以通过编写代码来实现,在数据库更新操作完成后,同步更新Redis中对应的数据。这可以通过在代码中使用事务来保证更新的原子性,确保数据库和Redis中的数…...

探索设计模式的魅力:创建型设计模式的比较与决策
设计模式专栏:http://t.csdnimg.cn/U54zu 目录 一、设计模式概览 1.1 创建型模式 二、比较创建型设计模式 1.1 适用场景典型用例 1.2 关键要素与差异对比 1.3 结构图 三、模式选择指南 3.1 场景分析 3.2 决策流程图 四、结语 4.1 优势 4.2 考量因素 一、…...

Linux之Shell
第 1 章 Shell 概述 1)Linux 提供的 Shell 解析器有 [zhaohadoop101 ~]$ cat /etc/shells /bin/sh /bin/bash /usr/bin/sh /usr/bin/bash /bin/tcsh /bin/csh2)bash 和 sh 的关系 [zhaohadoop101 bin]$ ll | grep bash -rwxr-xr-x. 1 root root 941880…...

nginx upstream server主动健康检测模块添加https检测功能[完整版]
目录 1 缘起1.1 功能定义2. 实现后的效果2.1 配置文件2.2 运行效果3. 代码实现3.1 配置指令3.1.1 配置指令定义:3.1.2 配置指令结构体:3.1.3 配置指令源码定义:3.2 模块的初始化3.3 添加新的健康检测类型的定义3.4 握手完成后的处理3. 5 发送http请求3.6 接收http响应3.7 连…...

django中admin页面汉化
在Django中,将admin界面汉化为中文需要进行一些配置和翻译文件的添加。下面是一个基本的步骤指南,帮助你实现Django admin的汉化: 一:安装并配置Django: 如果你还没有安装Django,首先通过pip安装它: pip…...

prometheus基于consul的服务发现
文章目录 一、基础二、安装consul下载地址启动consul访问consul 三、编写服务发现文件nodes.json四、prometheus配置consul发现修改prometheus.yml重启Prometheus 参考 一、基础 二、安装consul 下载地址 https://developer.hashicorp.com/consul/install 启动consul mkdi…...

深度学习之pytorch实现线性回归
度学习之pytorch实现线性回归 pytorch用到的函数torch.nn.Linearn()函数torch.nn.MSELoss()函数torch.optim.SGD() 代码实现结果分析 pytorch用到的函数 torch.nn.Linearn()函数 torch.nn.Linear(in_features, # 输入的神经元个数out_features, # 输出神经元个数biasTrue # 是…...

Vue3快速上手(八) toRefs和toRef的用法
顾名思义,toRef 就是将其转换为ref的一种实现。详细请看: 一、toRef 1.1 示例 <script langts setup name"toRefsAndtoRef"> // 引入reactive,toRef import { reactive, toRef } from vue // reactive包裹的数据即为响应式对象 let p…...