【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
}

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

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

相关文章

Github 2024-02-17 开源项目日报 Top10

根据Github Trendings的统计,今日(2024-02-17统计)共有10个项目上榜。根据开发语言中项目的数量,汇总情况如下: 开发语言项目数量Python项目4TypeScript项目3Rust项目2Jupyter Notebook项目1PowerShell项目1JavaScript项目1 Black&#xff…

标准IO 2月4日学习笔记

IO输入输出,操作对象是文件 Linux文件类型: b block 块设备文件 按块扫描设备信息的文件 存储设备 c character 字符设备文件 按字符扫描设备信息的文件 d direct…

单片机学习笔记---AD模数转换DA数模转换

目录 AD模数转换 XPT2046.c XPT2046.h main.c DA数模转换 main.c 上一篇博客讲了AD/DA转换的工作原理,也介绍了运算放大器的工作原理,这节开始代码演示! AD模数转换 新创建一个工程:AD模数转换 第一个工程将用到LCD1602和…

相机图像质量研究(34)常见问题总结:图像处理对成像的影响--拖影

系列文章目录 相机图像质量研究(1)Camera成像流程介绍 相机图像质量研究(2)ISP专用平台调优介绍 相机图像质量研究(3)图像质量测试介绍 相机图像质量研究(4)常见问题总结:光学结构对成像的影响--焦距 相机图像质量研究(5)常见问题总结:光学结构对成…

Linux | Ubuntu通过USB访问Redmi K40存储出现xxx was not providedby any .service files错误

问题描述 通过USB Type-C数据线将Redmi K40手机(查证发现其他Redmi系手机也存在如此问题)连接至台式机Ubuntu 20.04后,手机切换至访问存储模式,Ubuntu上可以访问手机存储,并正常进行文件传输。标题所述问题的诱发原因…

AgentWeb - 基于Android的WebView

项目地址 GitCode - 开发者的代码家园 项目简介 AgentWeb 是一个基于的 Android WebView ,极度容易使用以及功能强大的库,提供了 Android WebView 一系列的问题解决方案 ,并且轻量和极度灵活,详细使用请参照上面的 Sample 。 …

5.9 BCC工具之nodejs_http_server.py简介

一,nodejs_http_server.py简介 同样地,我们先了解下USDT,USDT即Userland Statically Defined Tracing,它是一种静态定义的跟踪技术,用于在用户空间应用程序中添加自定义的跟踪点。USDT利用DTrace(动态跟踪)框架,允许开发者在代码中定义跟踪点,并在需要时启用它们。这…

力扣 第 124 场双周赛 解题报告 | 珂学家 | 非常规区间合并

前言 整体评价 T4的dp解法没想到,走了一条"不归路", 这个区间合并解很特殊,它是带状态的,而且最终的正解也是基于WA的case,慢慢理清的。 真心不容易,太难了。 T1. 相同分数的最大操作数目 I 思路: 模拟 c…

吴恩达机器学习全课程笔记第二篇

目录 前言 P31-P33 logistics (逻辑)回归 决策边界 P34-P36 逻辑回归的代价函数 梯度下降的实现 P37-P41 过拟合问题 正则化代价函数 正则化线性回归 正则化logistics回归 前言 这是吴恩达机器学习笔记的第二篇,第一篇笔记请见&…

day32打卡

day32打卡 122. 买卖股票的最佳时机 II 解法,贪心:局部,收集每天的正利润-》整体,获取最大利润 从第0天到第3天,利润为:price[3] - price[0],也可以是(price[3] - price[2]) (price[2] - pr…

160基于matlab的负熵和峭度信号的盲分离

基于matlab的负熵和峭度信号的盲分离。基于峭度的FastICA算法的收敛速度要快,迭代次数比基于负熵的FastICA算法少四倍以上。SMSE随信噪比增大两种判据下的FastICA算法都逐渐变小,但是基于峭度的算法的SMSE更小,因此基于峭度的FastICA算法性能…

论文精读--对比学习论文综述

InstDisc 提出了个体判别任务,而且利用这个代理任务与NCE Loss去做对比学习从而得到了不错的无监督表征学习的结果;同时提出了别的数据结构——Memory Bank来存储大量负样本;解决如何对特征进行动量式的更新 翻译: 有监督学习的…

C++并发编程 -3.同步并发操作

本文介绍如何使用条件变量控制并发的同步操作、C 并发三剑客,函数式编程 一.条件变量 1.概念 C条件变量(condition variable)是一种多线程编程中常用的同步机制,用于线程间的通信和协调。它允许一个或多个线程等待某个条件的发生…

爬虫之正则表达式

个人主页:Lei宝啊 愿所有美好如期而遇 概念: 正则表达式(regular expression)描述了一种字符串匹配的模式(pattern),正则匹配是一个模糊的匹配(不是精确匹配) 如下四个方法经常使用: match()search()f…

基于Java+SpringBoot+vue+elementui 实现即时通讯管理系统

目录 系统简介效果图源码结构试用地址源码下载地址技术交流 博主介绍: 计算机科班人,全栈工程师,掌握C、C#、Java、Python、Android等主流编程语言,同时也熟练掌握mysql、oracle、sqlserver等主流数据库,能够为大家提供…

Django学习笔记教程全解析:初步学习Django模型,初识API,以及Django的后台管理系统(Django全解析,保姆级教程)

把时间用在思考上是最能节省时间的事情。——[美]卡曾斯 导言 写在前面 本文部分内容引用的是Django官方文档,对官方文档进行了解读和理解,对官方文档的部分注释内容进行了翻译,以方便大家的阅读和理解。 概述 在上一篇文章里&#xff0…

huggingface入门玩耍LLM Starter

huggingface入门玩耍LLM Starter huggingface-cli 下载model 下载 本人macos系统,以下可参考 huggingface-cli 下载 brew install huggingface-climodel 下载 以 chatglm-6b 为例 huggingface-cli download --token hf_*** --resume-download THUDM/chatglm-6b-i…

Typora+PicGO+腾讯云COS做图床

文章目录 Typora+PicGO+腾讯云COS做图床一、为什么使用图床二、Typora、PicGO和腾讯云COS介绍三、下载Typora和PicGOTyporaPicGO 四、配置Typora、PicGO和腾讯云COS腾讯云COS配置PicGO配置Typora配置 Typora+PicGO+腾讯云COS做图床…

grid---选择数据功能!!

目录 总结一、步骤1.第一步-新建modelBody组件2.第二步-编写表的扩展js 总结 新建modelBody组件&#xff0c;编写表的扩展js 一、步骤 1.第一步-新建modelBody组件 复制如下代码 修改相关内容 <template><VolBoxv-model"model":lazy"true"ti…

数据库设计、JDBC、数据库连接池

数据库设计 数据库设计概念 数据库设计就是根据业务 系统的具体需求&#xff0c;结合我们所选用的DBMS,为这个业务系统构造出最优的数据存储模型。建立数据库中的表结构以及表与表之间的关联关系的过程。有哪些表?表里有哪些字段?表和表之间有什么关系? 数据库设计的步骤…