IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 网络协议 -> 用一个简单的函数来理一下RPC调用过程 -> 正文阅读

[网络协议]用一个简单的函数来理一下RPC调用过程

1.什么是RPC

RPC(Remote Procedure Call)远程过程调度,简单的理解就是一个节点请求另一个节点的提供的服务。

2.远程调用要面临的三个问题。

(1)Call ID映射。本地调用中,函数体是直接通过函数指针来指定的,调用函数时,编译器会自动调用它相应的函数指针。但在远程调用中,函数指针是不行的,因为两个进程的地址空间完全不一样。所以,在RPC中所有的函数都必须有自己的一个ID。这个ID在所有进程中都是唯一确定的。客户端在做远程过程调用时,必须附上这个ID。然后我们还需要在客户端和服务端分别维护一个 {函数 :Call ID} 的对应表。两者的表不一定需要完全相同,但相同的函数对应的Call ID必须相同。当客户端需要进行远程调用时,它就查该表,找出相应的Call ID,然后把它传给服务端,服务端也通过查表,来确定客户端需要调用的函数,然后执行相应函数的代码。

(2)序列化和反序列化。客户端如何将参数值传给远程的函数就成了一个问题在本地调用中,只需要把参数压到栈里,函数自己去栈里读即可。在远程过程调用时,客户端跟服务端是不同的进程,不能通过内存来传递参数。甚至有时候客户端和服务端使用的都不是同一种语言(比如服务端用C++,客户端用Java或者Python)。这时需要客户端把参数先转成一个字节流,传给服务端后,再把字节流转成自己能读取的格式。这个过程叫序列化和反序列化(也叫编码和解码)。同理,从服务端返回的值也需要序列化反序列化的过程。

(3)网络传输。远程调用往往用在网络上,客户端和服务端是通过网络连接的。所有的数据都需要通过网络传输,因此就需要有一个网络传输层。网络传输层需要把Call ID和序列化后的参数字节流传给服务端,然后再把序列化后的调用结果传回客户端。只要能完成这两者的,都可以作为传输层使用。因此,它所使用的协议其实是不限的,能完成传输就行。尽管大部分RPC框架都使用TCP协议,但其实UDP也可以,而gRPC干脆就用了HTTP2,Java的Netty也属于这层的东西。

3.RPC调用过程是怎么样的

PRC的过程大概就是客户端让服务端做一件事,服务端将处理结果发给客户端。笔者认为可以用这样的一个例子来比喻:

客户端好比一个“老板”服务端好比一个“仓库”“老板”要从“仓库”要东西,“仓库”会自动将“老板”要的东西下单发送给“老板”,但是这样的过程是不是有点不太好管理,因为“老板”必须将注意力放到重大决策上,不能过多关注于如何去联系“仓库”,所以,“老板”需要有一个“秘书”来帮他处理这些事情,“仓库”也一样,每天有那么多“老板”都要东西,需要有一个“仓库管理员”来帮接收“老板”要什么东西,将东西打包发给“老板”

老板:

  • 客户端(client):服务调用发起方,也称为消费者。

秘书:

  • 客户端存根(client Stub):该程序运行在客户端所在的计算机机器上,主要用来存储要调用的服务器的地址,另外,该程序还负责将客户端请求远端服务器程序的数据信息打包成数据包,通过网络发送给服务端Stub程序;其次,还要接收服务端Stub程序发送的调用结果数据包,并解析返回给客户端。

仓库:

  • 服务端(server):远端的计算机机器上运行的程序,其中有客户端要调用的方法。

仓库管理员:

  • 服务端存根(server Stub):接收客户Stub程序通过网络发送的请求消息数据包,并调用服务端中真正的程序功能方法,完成功能调用;其次,将服务端执行调用的结果进行数据处理打包发送给客户端Stub程序。

?RPC调用过程为:

  • 客户端想要发起一个远程过程调用,首先通过调用本地客户端Stub程序的方式调用想要使用的功能方法名;
  • 客户端Stub程序接收到了客户端的功能调用请求,将客户端请求调用的方法名,携带的参数等信息做序列化操作,并打包成数据包。
  • 客户端Stub查找到远程服务器程序的IP地址,调用Socket通信协议,通过网络发送给服务端。
  • 服务端Stub程序接收到客户端发送的数据包信息,并通过约定好的协议将数据进行反序列化,得到请求的方法名和请求参数等信息。
  • 服务端Stub程序准备相关数据,调用本地Server对应的功能方法进行,并传入相应的参数,进行业务处理。
  • 服务端程序根据已有业务逻辑执行调用过程,待业务执行结束,将执行结果返回给服务端Stub程序。
  • 服务端Stub程序将程序调用结果按照约定的协议进行序列化,并通过网络发送回客户端Stub程序。
  • 客户端Stub程序接收到服务端Stub发送的返回数据,对数据进行反序列化操作,并将调用返回的数据传递给客户端请求发起者。
  • 客户端请求发起者得到调用结果,整个RPC调用过程结束。

4.用一个简单的函数来看看这个过程是怎么样的

先看下目录结构:

handler(放处理函数)

package handler

const HelloServiceName = "handler/HelloService"

type NewHelloService struct {
}

func (s *NewHelloService) Hello(request string, reply *string) error {
	//返回值是通过修改reply
	*reply = "hello," + request
	return nil
}

服务端(仓库):server.go

package main

import (
	"microServices/rpc_call/handler"
	"microServices/rpc_call/server_proxy"
	"net"
	"net/rpc"
)

func main() {
	//1.实例化一个server
	listener, _ := net.Listen("tcp", ":12345")

	//2.注册处理逻辑  handler
	_ = server_proxy.RegisterHelloService(&handler.NewHelloService{})
	//3.启动服务
	for {
		conn, _ := listener.Accept()
		go rpc.ServeConn(conn)
	}

}

服务端存根(仓库管理员):server_proxy.go

package server_proxy

import (
	"microServices/rpc_call/handler"
	"net/rpc"
)

type HelloServicer interface {
	Hello(request string, reply *string) error
}

func RegisterHelloService(srv HelloServicer) error {
	return rpc.RegisterName(handler.HelloServiceName, srv)
}
func main() {

}

客户端(老板):client.go

package main

import (
	"fmt"
	"microServices/rpc_call/client_proxy"
)

func main() {
	//1.建立连接
	client := client_proxy.NewHelloServiceStub("tcp", "localhost:12345")
	//1.只写业务逻辑,不关注每个函数的名称
	var reply string
	err := client.Hello("voyager", &reply)
	if err != nil {
		panic("调用失败")
	}
	fmt.Println(reply)
}

客户端存根(秘书):client_proxy.go

package client_proxy

import (
	"microServices/rpc_call/handler"
	"net/rpc"
)

type HelloServiceStub struct {
	*rpc.Client
}

func NewHelloServiceStub(protol, address string) HelloServiceStub {
	conn, err := rpc.Dial(protol, 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)
	if err != nil {
		return err
	}

	return nil
}

梳理一下调用流程:

  • client端通过调用了本地的client_proxy的NewHelloServiceStub函数建立了连接
  • 然后client端要调用Hello这个函数。
  • client_proxy将hello的方法名,携带的参数做序列化操作,并打包成数据包,发出请求调用hello这个函数。
  • handler里面的函数实现了server_proxy里面的接口,所以在server端在注册处理逻辑的时候可以传入handler里面的函数。
  • server处理完返回给client端

为什么server_proxy里面要用interface?

因为如果不用的话,server_proxy和handler的耦合性太强, 也就是说handler里面的结构体改了,server_proxy也必须改,而server_proxy的RegisterHelloService方法关注的是方法是否被实现,在go里面interface刚好可以解决这个问题。

  网络协议 最新文章
使用Easyswoole 搭建简单的Websoket服务
常见的数据通信方式有哪些?
Openssl 1024bit RSA算法---公私钥获取和处
HTTPS协议的密钥交换流程
《小白WEB安全入门》03. 漏洞篇
HttpRunner4.x 安装与使用
2021-07-04
手写RPC学习笔记
K8S高可用版本部署
mySQL计算IP地址范围
上一篇文章      下一篇文章      查看所有文章
加:2022-05-13 11:59:22  更:2022-05-13 11:59:43 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/26 0:41:05-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码