返回顶部
首页 > 资讯 > 后端开发 > GO >Golang实现简易的rpc调用
  • 699
分享到

Golang实现简易的rpc调用

Golang实现rpc调用Golangrpc调用Golangrpc 2023-03-06 17:03:59 699人浏览 八月长安
摘要

目录开始实现两点之间的通讯(transport)实现反射调用已注册的方法总结(自我pua)rpc(Remote Procedure Call Protocol)远程过程调用协议。 一

rpc(Remote Procedure Call Protocol)远程过程调用协议。 一个通俗的描述是:客户端在不知道调用细节的情况下,调用存在于远程计算机上的某个对象,就像调用本地应用程序中的对象一样。 比较正式的描述是:一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议 从使用的方面来说,服务端和客户端通过tcp/UDP/Http等通讯协议通讯,在通讯的时候客户端指定好服务端的方法、参数等信息通过序列化传送到服务端,服务端可以通过已有的元信息找到需要调用的方法,然后完成一次调用后序列化返回给客户端(rpc更多的是指服务与服务之间的通信,可以使用效率更高的协议和序列化格式去进行,并且可以进行有效的负载均衡和熔断超时等,因此跟前后端之间的WEB的交互概念上是有点不一样的) 用一张简单的图来表示

开始

本文只实现一个rpc框架基本的功能,不对性能做保证,因此尽量使用Go原生自带的net/JSON库等进行操作,对使用方面不做stub(偷懒,只使用简单的json格式指定需要调用的方法),用最简单的方式实现一个简易rpc框架,也不保证超时调用和服务发现等集成的逻辑,服务发现可以参考下文 本文代码地址(https://GitHub.com/wuhuZhao/rpc_demo)

实现两点之间的通讯(transport)

本段先实现两端之间的通讯,只确保两个端之间能互相通讯即可 server.go

package server

import (
	"fmt"
	"log"
	"net"
)

// Server: transport底层实现,通过Server去接受客户端的字节流
type Server struct {
	ls   net.Listener
	port int
}

// NewServer: 根据端口创建一个server
func NewServer(port int) *Server {
	s := &Server{port: port}
	s.init()
	return s
}

// init: 初始化服务端连接
func (s *Server) init() {
	l, err := net.Listen("tcp", fmt.Sprintf("127.0.0.1:%d", s.port))
	if err != nil {
		panic(err)
	}
	s.ls = l
}

// Start: 启动服务端的端口监听,采取一个conn一个g的模型,没有使用Reactor等高性能模型
func (s *Server) Start() {
	go func() {
		log.Printf("server [%s] start....", s.ls.Addr().String())
		for {
			conn, err := s.ls.Accept()
			if err != nil {
				panic(err)
			}
			go func() {
				buf := make([]byte, 1024)
				for {
					idx, err := conn.Read(buf)
					if err != nil {
						panic(err)
					}
					if len(buf) == 0 {
						continue
					}
					// todo 等序列化的信息
					log.Printf("[conn: %v] get data: %v\n", conn.RemoteAddr(), string(buf[:idx]))

				}
			}()
		}
	}()

}

// Close: 关闭服务监听
func (s *Server) Close() error {
	return s.ls.Close()
}


// Close: 关闭服务监听
func (s *Server) Close() error {
	return s.ls.Close()
}

client.go

package client

import (
	"fmt"
	"log"
	"net"
	"unsafe"
)

type Client struct {
	port int
	conn net.Conn
}

func NewClient(port int) *Client {
	c := &Client{port: port}
	c.init()
	return c
}

// init: initialize tcp client
func (c *Client) init() {
	conn, err := net.Dial("tcp", fmt.Sprintf("127.0.0.1:%d", c.port))
	if err != nil {
		panic(err)
	}
	c.conn = conn

}

func (c *Client) Send(statement string) error {
	_, err := c.conn.Write(*(*[]byte)(unsafe.Pointer(&statement)))
	if err != nil {
		panic(err)
	}
	return nil
}

// Close: use to close connection
func (c *Client) Close() error {
	return c.conn.Close()
}

使用main.go做测试 main.go

package main

import (
	"rpc_demo/internal/client"
	"rpc_demo/internal/server"
	"time"
)

func main() {
	s := server.NewServer(9999)
	s.Start()
	time.Sleep(5 * time.Second)
	c := client.NewClient(9999)
	c.Send("this is a test\n")
	time.Sleep(5 * time.Second)
}

执行一次main.go, go run main.go

2023/03/05 14:39:11 server [127.0.0.1:9999] start....
2023/03/05 14:39:16 [conn: 127.0.0.1:59126] get data: this is a test

可以证明第一部分的任务已经完成,可以实现两端之间的通讯了

实现反射调用已注册的方法

实现了双端的通信以后,我们在internal.go里实现两个方法,一个是注册,一个是调用,因为go有运行时的反射,所以我们使用反射去注册每一个需要调用到的方法,然后提供全局唯一的函数名,让client端可以实现指定方法的调用

internal.go

package internal

import (
	"errors"
	"fmt"
	"reflect"
	"runtime"
	"strings"
)

// 全局唯一
var GlobalMethod = &Method{methods: map[string]reflect.Value{}}

type Method struct {
	methods map[string]reflect.Value
}

func (m *Method) reGISter(impl interface{}) error {
	pl := reflect.ValueOf(impl)
	if pl.Kind() != reflect.Func {
		return errors.New("impl should be function")
	}
	// 获取函数名
	methodName := runtime.FuncForPC(pl.Pointer()).Name()
	if len(strings.Split(methodName, ".")) < 1 {
		return errors.New("invalid function name")
	}
	lastFuncName := strings.Split(methodName, ".")[1]
	m.methods[lastFuncName] = pl
	fmt.Printf("methods: %v\n", m.methods)
	return nil
}

func (m *Method) call(methodName string, callParams ...interface{}) ([]interface{}, error) {
	fn, ok := m.methods[methodName]
	if !ok {
		return nil, errors.New("impl method not found! Please Register first")
	}
	in := make([]reflect.Value, len(callParams))
	for i := 0; i < len(callParams); i++ {
		in[i] = reflect.ValueOf(callParams[i])
	}
	res := fn.Call(in)
	out := make([]interface{}, len(res))
	for i := 0; i < len(res); i++ {
		out[i] = res[i].Interface()
	}
	return out, nil
}

func Call(methodName string, callParams ...interface{}) ([]interface{}, error) {
	return GlobalMethod.call(methodName, callParams...)
}

func Register(impl interface{}) error {
	return GlobalMethod.register(impl)
}

在单测里测试一下这个注册和调用的功能internal_test.go

package internal

import (
	"testing"
)

func Sum(a, b int) int {
	return a + b
}
func TestRegister(t *testing.T) {
	err := Register(Sum)
	if err != nil {
		t.Fatalf("err: %v\n", err)
	}
	t.Logf("test success\n")
}

func TestCall(t *testing.T) {
	TestRegister(t)
	result, err := Call("Sum", 1, 2)
	if err != nil {
		t.Fatalf("err: %v\n", err)
	}
	if len(result) != 1 {
		t.Fatalf("len(result) is not equal to 1\n")
	}
	t.Logf("Sum(1,2) = %d\n", result[0].(int))
	if err := recover(); err != nil {
		t.Fatalf("%v\n", err)
	}
}

执行调用

/usr/local/go/bin/go test -timeout 30s -run ^TestCall$ rpc_demo/internal -v

Running tool: /usr/local/go/bin/go test -timeout 30s -run ^TestCall$ rpc_demo/internal -v

=== RUN   TestCall
methods: map[Sum:<func(int, int) int Value>]
    /root/go/src/juejin_demo/rpc_demo/internal/internal_test.go:15: test success
    /root/go/src/juejin_demo/rpc_demo/internal/internal_test.go:27: Sum(1,2) = 3
--- PASS: TestCall (0.00s)
PASS
ok      rpc_demo/internal    0.002s

可以看到这个注册和调用的过程已经实现并且达到指定方法调用的作用

设计struct完整表达一次完整的rpc调用,并且封装json库中的Decoder和Encoder,完成序列化和反序列化

internal.go

type RpcRequest struct {
	MethodName string
	Params     []interface{}
}

type RpcResponses struct {
	Returns []interface{}
	Err     error
}

transport.go考虑可以对接更多的格式,所以抽象了一层进行使用(demo肯定没有更多格式了)

package transport

// Transport: 序列化格式的抽象层,从connection中读取数据序列化并且反序列化到connection中
type Transport interface {
	Decode(v interface{}) error
	Encode(v interface{}) error
	Close()
}

json_transport.go

package transport

import (
	"encoding/json"
	"net"
)

var _ Transport = (*JSONTransport)(nil)

type JSONTransport struct {
	encoder *json.Encoder
	decoder *json.Decoder
}

// NewJSONTransport: 负责读取和写入conn
func NewJSONTransport(conn net.Conn) *JSONTransport {
	return &JSONTransport{json.NewEncoder(conn), json.NewDecoder(conn)}
}

// Decode: use json package to decode
func (t *JSONTransport) Decode(v interface{}) error {
	if err := t.decoder.Decode(v); err != nil {
		return err
	}
	return nil
}

// Encode: use json package to encode
func (t *JSONTransport) Encode(v interface{}) error {
	if err := t.encoder.Encode(v); err != nil {
		return err
	}
	return nil
}

// Close: not implement
func (dec *JSONTransport) Close() {

}

然后我们将服务端和客户端的逻辑进行修改,改成通过上面两个结构体进行通信,然后返回一次调用 server.go

//...
		for {
			conn, err := s.ls.Accept()
			if err != nil {
				panic(err)
			}
			tsp := transport.NewJSONTransport(conn)
			go func() {
				for {
					request := &internal.RpcRequest{}
					err := tsp.Decode(request)
					if err != nil {
						panic(err)
					}
					log.Printf("[server] get request: %v\n", request)
					result, err := internal.Call(request.MethodName, request.Params...)
					log.Printf("[server] invoke method: %v\n", result)
					if err != nil {
						response := &internal.RpcResponses{Returns: nil, Err: err}
						tsp.Encode(response)
						continue
					}
					response := &internal.RpcResponses{Returns: result, Err: err}
					if err := tsp.Encode(response); err != nil {
						log.Printf("[server] encode response err: %v\n", err)
						continue
					}
				}
			}()
		}
        //...

client.go

// ...
// Call: remote invoke
func (c *Client) Call(methodName string, params ...interface{}) (res *internal.RpcResponses) {
	request := internal.RpcRequest{MethodName: methodName, Params: params}
	log.Printf("[client] create request to invoke server: %v\n", request)
	err := c.tsp.Encode(request)
	if err != nil {
		panic(err)
	}
	res = &internal.RpcResponses{}
	if err := c.tsp.Decode(res); err != nil {
		panic(err)
	}
	log.Printf("[client] get response from server: %v\n", res)
	return res
}
// ...

main.go

package main

import (
	"log"
	"rpc_demo/internal"
	"rpc_demo/internal/client"
	"rpc_demo/internal/server"
	"strings"
	"time"
)

// Rpc方法的一个简易实现
func Join(a ...string) string {
	res := &strings.Builder{}
	for i := 0; i < len(a); i++ {
		res.WriteString(a[i])
	}
	return res.String()
}

func main() {
	internal.Register(Join)
	s := server.NewServer(9999)
	s.Start()
	time.Sleep(5 * time.Second)
	c := client.NewClient(9999)
	res := c.Call("Join", "aaaaa", "bbbbb", "ccccccccc", "end")
	if res.Err != nil {
		log.Printf("[main] get an error from server: %v\n", res.Err)
		return
	}
	log.Printf("[main] get a response from server: %v\n", res.Returns[0].(string))
	time.Sleep(5 * time.Second)
}

接下来我们运行一下main

[root@hecs-74066 rpc_demo]# go run main.go 
2023/03/05 14:39:11 server [127.0.0.1:9999] start....
2023/03/05 14:39:16 [conn: 127.0.0.1:59126] get data: this is a test

[root@hecs-74066 rpc_demo]# go run main.go 
2023/03/05 21:53:41 server [127.0.0.1:9999] start....
2023/03/05 21:53:46 [client] create request to invoke server: {Join [aaaaa bbbbb ccccccccc end]}
2023/03/05 21:53:46 [server] get request: &{Join [aaaaa bbbbb ccccccccc end]}
2023/03/05 21:53:46 [server] invoke method: [aaaaabbbbbcccccccccend]
2023/03/05 21:53:46 [client] get response from server: &{[aaaaabbbbbcccccccccend] <nil>}
2023/03/05 21:53:46 [main] get a response from server: aaaaabbbbbcccccccccend

总结(自我pua)

这样我们就实现了一个简单的rpc框架了,符合最简单的架构图,从client->序列化请求->transport -> 反序列化 ->server然后从server->序列化请求->transport->反序列化请求->client。当然从可用性的角度来说是差远了,没有实现stub代码,也没有idl的实现,导致所有的注册方法都是硬编码,可用性不高,而且没有集成服务发现(可以参考我的另一篇文章去集成)和熔断等功能,也没用中间件(也是我的另一篇文章)和超时等丰富的功能在里面,并且最近看了不少rpc框架的源码,感觉这个demo的设计也差远了。不过因为时间问题和代码的复杂性问题(单纯懒),起码算是实现了一个简单的rpc框架。

推荐一些比较好的框架实现

到此这篇关于golang实现简易的rpc调用的文章就介绍到这了,更多相关Golang rpc调用内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

您可能感兴趣的文档:

--结束END--

本文标题: Golang实现简易的rpc调用

本文链接: https://lsjlt.com/news/198724.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • Golang实现简易的rpc调用
    目录开始实现两点之间的通讯(transport)实现反射调用已注册的方法总结(自我pua)RPC(Remote Procedure Call Protocol)远程过程调用协议。 一...
    99+
    2023-03-06
    Golang实现rpc调用 Golang rpc调用 Golang rpc
  • Golang如何实现简易的rpc调用
    这篇文章主要介绍“Golang如何实现简易的rpc调用”,在日常操作中,相信很多人在Golang如何实现简易的rpc调用问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”Golang如何实现简易的rpc调用”的疑...
    99+
    2023-07-05
  • golang实现简单rpc调用过程解析
    目录基本概念RPC通信过程RPC 具体实现server端客户端基本概念 RPC(Remote Procedure Call)远程过程调用,简单的理解是一个节点请求另一个节点提供的服务...
    99+
    2024-04-02
  • Netty实现简易版的RPC框架过程详解
    目录正文1:如何运行项目2:从客户端调用开始(springboot-zk-study项目)3:服务端处理请求4:接下来要做什么正文 项目地址:gitee.com/baojh123...
    99+
    2023-02-10
    Netty简易版RPC框架 Netty RPC
  • dubbo怎么实现rpc调用
    Dubbo是一个基于Java的高性能RPC框架,可以实现远程服务的调用。以下是使用Dubbo实现RPC调用的步骤:1. 定义服务接口...
    99+
    2023-10-23
    dubbo
  • Golang实现简易的命令行功能
    目录前言开始flag.Stringflag.Intflag.StringVarflag.IntVar定义命令行参数实现 -f -v 是否强制拷贝copyFileAction 实现co...
    99+
    2023-02-13
    Golang实现命令行功能 Golang命令行功能 Golang命令行
  • springboot+HttpInvoke 实现RPC调用的方法
    开始用springboot2+hession4实现RPC服务时,发现第一个服务可以调用成功,但第二个就一直报'<'isanunknowncode。第一个服务还是...
    99+
    2024-04-02
  • springboot+HttpInvoke如何实现RPC调用
    小编给大家分享一下springboot+HttpInvoke如何实现RPC调用,希望大家阅读完这篇文章之后都有所收获,下面让我们一起去探讨吧!开始用springboot2+hession4实现RPC服务时,发现第一个服务可以调用成功,但第二...
    99+
    2023-06-29
  • 模仿mybatis-plus实现rpc调用
    目录正文组件的整合优化点场景:背景:步骤:正文 首先我的目标就是 为了把rpc调用进行封装,让业务人员开发的时候 快速使用 组件的整合 pom.xml 整合 <depende...
    99+
    2023-02-14
    模仿mybatis-plus rpc调用 mybatis-plus rpc
  • GoLang抽奖系统简易实现流程
    目录业务难点技术选项抽奖活动需求分析微信摇一摇得抽奖活动微博抢红包抽奖大转盘抽奖活动总结业务难点 设计一个抽奖系统,这个系统并不是具体化,是抽象化,具有以下的几个难点: 1、抽奖业务...
    99+
    2022-12-15
    GoLang抽奖系统 Go抽奖
  • python使用SimpleXMLRPCServer实现简单的rpc过程
    目录使用SimpleXMLRPCServer实现rpc模块定义方法python与rpc服务1.什么是RPC2.xmlrp库使用SimpleXMLRPCServer实现rpc 模块 S...
    99+
    2024-04-02
  • Golang远程调用框架RPC的具体使用
    目录gRPC需求说明创建RPC服务器实现客户端gRPC gRPC远程过程调用框架是基于动作的模式,类似远程调用微服务。这使得gRPC成为一种围绕Protobufs构建的进程间通信(I...
    99+
    2022-12-14
    Go RPC Go 远程调用 RPC
  • java实现简易版简易版dubbo
    目录一、dubbo简介二、架构设计三、开发工具四、一步步实现4.1 客户端消费实现4.2 服务实例曝光到注册中心4.3 自动化配置实现五、测试5.1 编写api5.2 实现api,标...
    99+
    2024-04-02
  • 怎样实现简单的RPC框架
    怎样实现简单的RPC框架,相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。1.定义上下文对象在RpcContext对象中增加一个map类型的参数对象,可以存放任意扩展的参数。2.R...
    99+
    2023-06-04
  • golang简易令牌桶算法实现代码
    基本思路:定义一个chan,chan大小为需要限制的qps大小,go一个协程启动tick,每1000/qps时间在tick中写入数值,启动另一个协程,读取chan中的值,如果读取到c...
    99+
    2024-04-02
  • Golang如何用RPC实现转发服务
    今天小编给大家分享一下Golang如何用RPC实现转发服务的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下吧。首先,我们需要了解一...
    99+
    2023-07-06
  • 调用 firebase golang 库时出现 RPC 数据存储错误
    各位小伙伴们,大家好呀!看看今天我又给各位带来了什么文章?本文标题是《调用 firebase golang 库时出现 RPC 数据存储错误》,很明显是关于Golang的文章哈哈哈,其中内容主要会涉及...
    99+
    2024-04-04
  • 如何使用Golang Facade实现简洁的接口调用
    使用Golang的Facade模式可以实现简洁的接口调用。Facade模式是一种结构设计模式,它提供了一种简化接口的方式,使得客户端...
    99+
    2023-10-10
    Golang
  • 如何在Python中实现一个简单的RPC远程过程调用框架
    如何在Python中实现一个简单的RPC远程过程调用框架在分布式系统中,一种常见的通信机制是通过RPC(Remote Procedure Call,远程过程调用)来实现不同进程之间的函数调用。RPC允许开发者像调用本地函数一样调用远程函数,...
    99+
    2023-10-27
    远程调用 Python RPC框架 实现RPC
  • 基于python实现rpc远程过程调用
    目录基于python实现RPC的demo前言一、主要内容二、实现步骤1. 进程间的通信2. 异步回调实现思路总结基于python实现RPC的demo 这是一个远程过程调用(RPC)的...
    99+
    2024-04-02
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作