【Go】四、rpc跨语言编程基础与rpc的调用基础原理

这篇具有很好参考价值的文章主要介绍了【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, " + request
	return nil
}

/*
*
1. 实例化一个server
2. 注册逻辑
3. 开启服务
*/
func main() {
	// 第一步:实例化 server
	listener, _ := net.Listen("tcp", ":1234")
	// 第二步:将我们的 struct 注册进 RPC
	// 我们如果把形参列表中的参数定义为 interface{} 就代表这个参数我们可以任意传入
	_ = rpc.RegisterName("HelloService", &HelloService{})
	// 第三部:启动服务,绑定rpc
	conn, _ := 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, " + request
	return 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 socket

request = {
    "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 server
            Socket socket = new Socket("localhost", 1234);

            // Create a JSON request
            String jsonRequest = "{\"id\": 0, \"method\": \"HelloService.Hello\", \"params\": [\"Yang\"]}";

            // Send the JSON request to the server
            OutputStream outputStream = socket.getOutputStream();
            outputStream.write(jsonRequest.getBytes());
            outputStream.flush();

            // Receive the response from the server
            BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            String response = reader.readLine();
            System.out.println("Response from server: " + response);

            // Close the socket
            socket.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, " + request
	return nil
}
func main() {
	_ = rpc.RegisterName("HelloService", &HelloService{})
	http.HandleFunc("/jsonrpc", func(w http.ResponseWriter, r *http.Request) {
		var conn io.ReadWriteCloser = struct {
			io.Writer
			io.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, " + request
	return nil
}

server.go

func main() {
	// 第一步:实例化 server
	listener, _ := net.Listen("tcp", ":1234")
	// 第二步:将我们的 struct 注册进 RPC
	// 我们如果把形参列表中的参数定义为 interface{} 就代表这个参数我们可以任意传入
	_ = rpc.RegisterName(handler.HelloServiceName, &handler.HelloService{})
	// 第三部:启动服务,绑定rpc
	for {
		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文章来源地址https://www.toymoban.com/news/detail-827865.html

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的调用基础原理的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【Go】Go语言并发编程:原理、实践与优化

    在当今的计算机世界,多核处理器和并发编程已经成为提高程序执行效率的关键。Go语言作为一门极富创新性的编程语言,凭借其强大的并发能力,在这方面表现出色。本文将深入探讨Go语言并发编程的原理,通过实际代码示例展示其应用,并讨论可能的优化策略。 在了解G

    2024年02月10日
    浏览(55)
  • 【Golang星辰图】数据管理利器:Go编程语言中的数据库和搜索引擎综合指南

    Go编程语言是一种强大、类型安全且高效的编程语言,它在处理数据库和搜索引擎方面有着广泛的应用。本篇文章将详细介绍几个Go编程语言中常用的数据库和全文搜索引擎,包括Go-bleve、Go-pgx、Go-leveldb/leveldb、Go-xorm、Go-mysql-driver和Go-bbolt/bbolt。对于每个工具,我们将介绍其功

    2024年03月26日
    浏览(67)
  • 【Go 基础篇】走进Go语言的面向对象编程世界

    欢迎各位编程爱好者们!今天我们将进入Go语言的面向对象编程(OOP)世界,一窥这门语言如何运用OOP思想来组织和构建程序。无论你是初学者还是有一些经验的开发者,本文都将为你揭示Go语言中的OOP特性、方法和最佳实践。 面向对象编程是一种程序设计范式,它以对象为

    2024年02月10日
    浏览(51)
  • 云原生时代崛起的编程语言Go基础实战

    @ 目录 概述 定义 使用场景 Go 安全 使用须知 搜索工具 Go基础命令 标准库 基础语法 Effective Go 概览 命名规范 注释 变量 常量(const) 控制结构 数据类型 迭代(range) 函数 指针 字符串和符文 结构体(struct) 方法 接口(interface) 泛型 错误(errors) 恐慌(pinic) 推迟(defer) 恢复(

    2024年02月01日
    浏览(58)
  • 【Go 基础篇】Go语言关键字和预定义标识符解析:探索编程的基石与核心要素

    在计算机编程中,(Keywords)和预定义标识符(Predefined Identifiers)是编程语言的核心要素,它们在语法结构和语言功能中起到重要作用。在Go语言(Golang)中,和预定义标识符定义了编程的基本规则和构建块,是实现功能的关键。本篇博客将深入探讨Go语言中的关

    2024年02月12日
    浏览(64)
  • Go语言网络编程:HTTP服务端之底层原理与源码分析——http.HandleFunc()、http.ListenAndServe()

    在 Golang只需要几行代码便能启动一个 http 服务,在上述代码中,完成了两件事: 调用 http.HandleFunc 方法,注册了对应于请求路径 /ping 的 handler 函数 调用 http.ListenAndServe,启动了一个端口为 8999 的 http 服务 2.1 server 结构 Addr :表示服务器监听的地址。如\\\":8080\\\"表示服务器在本地

    2024年02月08日
    浏览(58)
  • GO编程语言:简洁、高效、强大的开源编程语言

    在现代软件开发领域,随着应用复杂度的不断提升,开发人员对编程语言的需求也日益增长。GO编程语言,作为一种简洁、高效且具备强大并发能力的新型开源编程语言,逐渐成为了许多开发者的首选。本文将详细介绍GO语言在哪些项目开发中表现出色,以及为什么许多开发者

    2024年02月02日
    浏览(97)
  • Go语言网络编程(socket编程)WebSocket编程

    WebSocket是一种在单个TCP连接上进行全双工通信的协议 WebSocket使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据 在WebSocket API中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输 需要安装第

    2024年02月09日
    浏览(71)
  • Go语言网络编程(socket编程)http编程

    Web服务器的工作原理可以简单地归纳为 客户机通过TCP/IP协议建立到服务器的TCP连接 客户端向服务器发送HTTP协议请求包,请求服务器里的资源文档 服务器向客户机发送HTTP协议应答包,如果请求的资源包含有动态语言的内容,那么服务器会调用动态语言的解释引擎负责处理“

    2024年02月09日
    浏览(65)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包