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

Golang如何实现简易的rpc调用

2023-07-05 09:07:37 688人浏览 安东尼
摘要

这篇文章主要介绍“golang如何实现简易的rpc调用”,在日常操作中,相信很多人在Golang如何实现简易的rpc调用问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”Golang如何实现简易的rpc调用”的疑

这篇文章主要介绍“golang如何实现简易的rpc调用”,在日常操作中,相信很多人在Golang如何实现简易的rpc调用问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”Golang如何实现简易的rpc调用”的疑惑有所帮助!接下来,请跟着小编一起来学习吧!

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

    Golang如何实现简易的rpc调用

    开始

    本文只实现一个rpc框架基本的功能,不对性能做保证,因此尽量使用go原生自带的net/JSON库等进行操作,对使用方面不做stub(偷懒,只使用简单的json格式指定需要调用的方法),用最简单的方式实现一个简易rpc框架,也不保证超时调用和服务发现等集成的逻辑。

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

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

    package serverimport ("fmt""log""net")// Server: transport底层实现,通过Server去接受客户端的字节流type Server struct {ls   net.Listenerport int}// NewServer: 根据端口创建一个serverfunc 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 clientimport ("fmt""log""net""unsafe")type Client struct {port intconn net.Conn}func NewClient(port int) *Client {c := &Client{port: port}c.init()return c}// init: initialize tcp clientfunc (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 connectionfunc (c *Client) Close() error {return c.conn.Close()}

    使用main.go做测试 main.go

    package mainimport ("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 internalimport ("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] = plfmt.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 internalimport ("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 stringParams     []interface{}}type RpcResponses struct {Returns []interface{}Err     error}

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

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

    json_transport.go

    package transportimport ("encoding/json""net")var _ Transport = (*JSONTransport)(nil)type JSONTransport struct {encoder *json.Encoderdecoder *json.Decoder}// NewJSONTransport: 负责读取和写入connfunc NewJSONTransport(conn net.Conn) *JSONTransport {return &JSONTransport{json.NewEncoder(conn), json.NewDecoder(conn)}}// Decode: use json package to decodefunc (t *JSONTransport) Decode(v interface{}) error {if err := t.decoder.Decode(v); err != nil {return err}return nil}// Encode: use json package to encodefunc (t *JSONTransport) Encode(v interface{}) error {if err := t.encoder.Encode(v); err != nil {return err}return nil}// Close: not implementfunc (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 invokefunc (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 mainimport ("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

    到此,关于“Golang如何实现简易的rpc调用”的学习就结束了,希望能够解决大家的疑惑。理论与实践的搭配能更好的帮助大家学习,快去试试吧!若想继续学习更多相关知识,请继续关注编程网网站,小编会继续努力为大家带来更多实用的文章!

    您可能感兴趣的文档:

    --结束END--

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

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

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

    猜你喜欢
    • Golang如何实现简易的rpc调用
      这篇文章主要介绍“Golang如何实现简易的rpc调用”,在日常操作中,相信很多人在Golang如何实现简易的rpc调用问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”Golang如何实现简易的rpc调用”的疑...
      99+
      2023-07-05
    • Golang实现简易的rpc调用
      目录开始实现两点之间的通讯(transport)实现反射调用已注册的方法总结(自我pua)RPC(Remote Procedure Call Protocol)远程过程调用协议。 一...
      99+
      2023-03-06
      Golang实现rpc调用 Golang rpc调用 Golang rpc
    • golang实现简单rpc调用过程解析
      目录基本概念RPC通信过程RPC 具体实现server端客户端基本概念 RPC(Remote Procedure Call)远程过程调用,简单的理解是一个节点请求另一个节点提供的服务...
      99+
      2024-04-02
    • springboot+HttpInvoke如何实现RPC调用
      小编给大家分享一下springboot+HttpInvoke如何实现RPC调用,希望大家阅读完这篇文章之后都有所收获,下面让我们一起去探讨吧!开始用springboot2+hession4实现RPC服务时,发现第一个服务可以调用成功,但第二...
      99+
      2023-06-29
    • Golang如何用RPC实现转发服务
      今天小编给大家分享一下Golang如何用RPC实现转发服务的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下吧。首先,我们需要了解一...
      99+
      2023-07-06
    • Netty实现简易版的RPC框架过程详解
      目录正文1:如何运行项目2:从客户端调用开始(springboot-zk-study项目)3:服务端处理请求4:接下来要做什么正文 项目地址:gitee.com/baojh123...
      99+
      2023-02-10
      Netty简易版RPC框架 Netty RPC
    • 如何使用Golang Facade实现简洁的接口调用
      使用Golang的Facade模式可以实现简洁的接口调用。Facade模式是一种结构设计模式,它提供了一种简化接口的方式,使得客户端...
      99+
      2023-10-10
      Golang
    • 如何在PHP中实现RPC远程调用?
      随着互联网的快速发展和云计算技术的广泛应用,分布式系统和微服务架构变得越来越普遍。在这样的背景下,远程过程调用(RPC)成为了一种常见的技术手段。RPC能够使得不同的服务在网络上实现远程调用,从而实现不同服务之间的互联操作,提高代码的复用性...
      99+
      2023-05-14
      PHP rpc 远程调用
    • 如何在Python中实现一个简单的RPC远程过程调用框架
      如何在Python中实现一个简单的RPC远程过程调用框架在分布式系统中,一种常见的通信机制是通过RPC(Remote Procedure Call,远程过程调用)来实现不同进程之间的函数调用。RPC允许开发者像调用本地函数一样调用远程函数,...
      99+
      2023-10-27
      远程调用 Python RPC框架 实现RPC
    • dubbo怎么实现rpc调用
      Dubbo是一个基于Java的高性能RPC框架,可以实现远程服务的调用。以下是使用Dubbo实现RPC调用的步骤:1. 定义服务接口...
      99+
      2023-10-23
      dubbo
    • 如何使用PHP和Swoole实现RPC远程调用
      如何使用PHP和Swoole实现RPC远程调用RPC(Remote Procedure Call)是一种远程调用的协议,可以让应用程序在不同计算机上进行函数调用。RPC通常被用于构建分布式系统,其可以让不同的微服务能够协同工作。在PHP和S...
      99+
      2023-05-14
      PHP rpc swoole
    • 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
    • 如何用PHP实现简易的MVC框架
      这篇文章主要介绍了如何用PHP实现简易的MVC框架的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇如何用PHP实现简易的MVC框架文章都会有所收获,下面我们一起来看看吧。一、前言MVC的全名是Model View...
      99+
      2023-06-30
    • 如何在 Golang 中使用 RPC 实现文件上传?
      使用 rpc 实现文件上传:创建 rpc 服务器来处理文件上传请求,使用 net/rpc 包创建。创建 rpc 客户端来向服务器发起文件上传请求,使用 net/rpc 包创建,将文件序列...
      99+
      2024-05-13
      文件上传 rpc golang
    • 基于python如何实现rpc远程过程调用
      这篇文章主要介绍“基于python如何实现rpc远程过程调用”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“基于python如何实现rpc远程过程调用”文章能帮助大家解决问题。一、主要内容所谓RPC,...
      99+
      2023-07-02
    • 如何实现一个简易的NpmInstall
      本篇文章给大家分享的是有关如何实现一个简易的NpmInstall ,小编觉得挺实用的,因此分享给大家学习,希望大家阅读完这篇文章后可以有所收获,话不多说,跟着小编一起来看看吧。现在写代码我们一般不会全部自己...
      99+
      2024-04-02
    • 模仿mybatis-plus实现rpc调用
      目录正文组件的整合优化点场景:背景:步骤:正文 首先我的目标就是 为了把rpc调用进行封装,让业务人员开发的时候 快速使用 组件的整合 pom.xml 整合 <depende...
      99+
      2023-02-14
      模仿mybatis-plus rpc调用 mybatis-plus rpc
    • 如何用python实现简易聊天室
      本篇内容主要讲解“如何用python实现简易聊天室”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“如何用python实现简易聊天室”吧!1.功能:类似qq群聊功能有人进入聊天室需要输入姓名,姓名不...
      99+
      2023-06-20
    • 如何实现一个简易promise
      这篇文章主要介绍如何实现一个简易promise,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!step1 搭建框架 首先我们需要在这里放置一个promise函数本体 后面要在里面添加resolve、reject的执行函...
      99+
      2023-06-25
    软考高级职称资格查询
    编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
    • 官方手机版

    • 微信公众号

    • 商务合作