• Go语言下RPC的开发


    一、rpc之HelloWorld

    Go语言的rpc包的路径为net/rpc:

    1、server.go

    package main
    
    import (
        "net"
        "net/rpc"
    )
    
    type HelloWorldService struct {
    }
    
    func (s *HelloWorldService) HelloWorld(request string, response *string) error {
        *response = "hello" + request
        return nil
    }
    
    func main() {
        _ = rpc.RegisterName("HelloWorldService", &HelloWorldService{})
        listener, err := net.Listen("tcp", ":8000")
        if err != nil {
            panic("监听端口失败!")
        }
        conn, err := listener.Accept()
        if err != nil {
            panic("建立连接失败!")
        }
        rpc.ServeConn(conn)
    }
    • HelloWorld方法需要满足Go语言RPC规则,接收两个参数,第二个参数是指针类型,并且返回一个error类型,同时必须是公开方法
    • HelloWorldService类型的对象注册到RPC服务
    • rpc.register函数调用会将对象类型中满足RPC规则的对象方法注册为RPC函数,所有注册的方法会放置在HelloWorldService服务空间下
    • 建立TCP链接,通过rpc.ServerConn函数在该链接上提供RPC服务

    1、client.go

    package main
    
    import (
        "fmt"
        "log"
        "net/rpc"
    )
    
    func main() {
        client, err := rpc.Dial("tcp", "127.0.0.1:8000")
        if err != nil {
            log.Fatal("dial", err)
        }
        var response string
        err = client.Call("HelloWorldService.HelloWorld", "world", &response)
        if err != nil {
            log.Fatal("caller", err)
        }
        fmt.Println(response)
    
    }
    • 通过rpc.Dial进行RPC拨号服务
    • 通过client.Call调用具体方法
    • 方法中第一个参数是RPC服务名称和方法名称,第二个和第三个参数是方法中传入的实参 

    二、基于json实现RPC

    上述RPC默认采用的是Go语言特有的gob编码,所以其它语言调用Go语言实现的RPC服务相对困难。比较常见的有基于json实现的RPC服务,所以如果使用json来替换gob将会使其通用性变的更强。在Go语言中可以通过net/rpc/jsonrpc来基于json实现RPC,实现跨语言调用。

    1、server.go

    package main
    
    import (
        "net"
        "net/rpc"
        "net/rpc/jsonrpc"
    )
    
    type HelloWorldService struct {
    }
    
    func (s *HelloWorldService) HelloWorld(request string, response *string) error {
        *response = "hello" + request
        return nil
    }
    
    func main() {
        _ = rpc.RegisterName("HelloWorldService", &HelloWorldService{})
        listener, err := net.Listen("tcp", ":8000")
        if err != nil {
            panic("监听端口失败!")
        }
        // 不断的接收新的请求
        for {
            conn, err := listener.Accept()
            if err != nil {
                panic("建立连接失败!")
            }
            go rpc.ServeCodec(jsonrpc.NewServerCodec(conn)) // 启动协程处理请求
        }
    
    }

    这与之前相比使用rpc.ServeCodec函数替代了rpc.ServeConn函数,传入符合json编码的参数。

    2、client.go

    package main
    
    import (
        "fmt"
        "log"
        "net"
        "net/rpc"
        "net/rpc/jsonrpc"
    )
    
    func main() {
        conn, err := net.Dial("tcp", "127.0.0.1:8000")
        if err != nil {
            log.Fatal("dial", err)
        }
        client := rpc.NewClientWithCodec(jsonrpc.NewClientCodec(conn))
        var response string
        err = client.Call("HelloWorldService.HelloWorld", "world", &response)
        if err != nil {
            log.Fatal("caller", err)
        }
        fmt.Println(response)
    
    }

    这与之前相比通过net进行拨号建立连接,然后通过NewClientWithCodec函数传入符合json编码的参数。

    既然使用的是json进行编、解码,那么就具备一定的通用性,可以使用其它语言来进行调用,比如使用Python客户端进行调用,但是Go的RPC监听的是TCP连接,如果使用Python中的requests包是行不通的,它使用的是HTTP协议,会携带很多比如请求头之类的多余信息,所以使用socket编程发送请求:

    3、client.py

    import json
    import socket
    
    # 发送的数据格式必须满足这样的
    """
    method: 服务名称、方法名称
    result:返回的结果
    id:随意指定一个值,如果不指定,返回值id为None
    """
    request = {
        "method": "HelloWorldService.HelloWorld",
        "params": ["bily"],
        "id": 0
    }
    
    client = socket.create_connection(("127.0.0.1", 8000))
    client.sendall(json.dumps(request).encode())
    
    # 设置一次性接收的数据大小
    response = client.recv(4096)
    response = json.loads(response.decode())
    print(response)
    
    # 关闭连接
    client.close()

    请求结果:

    {'id': 0, 'result': 'hellobily', 'error': None}

    三、基于http实现RPC

     1、server.go

    package main
    
    import (
        "io"
        "net/http"
        "net/rpc"
        "net/rpc/jsonrpc"
    )
    
    type HelloWorldService struct {
    }
    
    func (s *HelloWorldService) HelloWorld(request string, response *string) error {
        *response = "hello" + request
        return nil
    }
    
    func main() {
        _ = rpc.RegisterName("HelloWorldService", new(HelloWorldService))
        http.HandleFunc("/jsonrpc", func(writer http.ResponseWriter, request *http.Request) {
            var conn io.ReadWriteCloser = struct {
                io.Writer
                io.ReadCloser
            }{
                ReadCloser: request.Body,
                Writer:     writer,
            }
            _ = rpc.ServeRequest(jsonrpc.NewServerCodec(conn))
        })
        _ = http.ListenAndServe(":8000", nil)
    
    }

    2、client.go

    import requests
    
    request = {
        "method": "HelloWorldService.HelloWorld",
        "params": ["karry"],
        "id": 0
    }
    
    res = requests.post("http://127.0.0.1:8000/jsonrpc", json=request)
    print(res.text)

    四、封装代理

    在之前的调用中,存在一些明显的问题:

    • 服务端中,业务代码与RPC通信底层混在一起,需要一种结构层次,将其分离
    • 客户端中,每次调用都需要知道业务端的服务名称与方法名称,将其单独封装

    这样就引出服务端代理Server Stub和客户端代理Client Stub:

    1、目录结构

    ├─client
    │      client.go
    │
    ├─client_stub
    │      client_stub.go
    │
    ├─handler
    │      handler.go
    │
    ├─server
    │      server.go
    │
    └─server_stub
            server_stub.go

    2、client.go

    package main
    
    import (
        "fmt"
        "go_rpc_project/stub_rpc/client_stub"
    )
    
    func main() {
        // 建立连接
        client := client_stub.NewHelloWorldServiceClient("tcp", "127.0.0.1:8000")
        // 调用业务函数HelloWorld
        var response string
        _ = client.HelloWorld("harry", &response)
        fmt.Println(response)
    
    }

    3、client_stub.go

    package client_stub
    
    import (
        "go_rpc_project/stub_rpc/handler"
        "log"
        "net/rpc"
    )
    
    type HelloWorldServiceStub struct {
        *rpc.Client
    }
    
    func NewHelloWorldServiceClient(protcol string, address string) HelloWorldServiceStub {
        conn, err := rpc.Dial(protcol, address)
        if err != nil {
            log.Fatal("拨号错误", err)
        }
        return HelloWorldServiceStub{conn}
    }
    
    func (c *HelloWorldServiceStub) HelloWorld(request string, response *string) error {
        err := c.Call(handler.HelloWorldServiceName+".HelloWorld", request, response)
        if err != nil {
            log.Fatal("调用服务失败", err)
        }
        return nil
    }

    4、handler.go

    package handler
    
    const HelloWorldServiceName = "handler/HelloWorldService"
    
    type HelloWorldService struct {
    }
    
    func (s *HelloWorldService) HelloWorld(request string, response *string) error {
        *response = "hello" + request
        return nil
    }

    5、server.go

    package main
    
    import (
        "go_rpc_project/stub_rpc/handler"
        "go_rpc_project/stub_rpc/server_stub"
        "net"
        "net/rpc"
    )
    
    func main() {
        // 实例化一个server
        listener, _ := net.Listen("tcp", ":8000")
        // 注册,将业务逻辑代码注册到代理中
        _ = server_stub.RegisterServicer(&handler.HelloWorldService{})
        // 启动服务
        for {
            conn, _ := listener.Accept()
            go rpc.ServeConn(conn)
        }
    }

    6、server_stub.go

    package server_stub
    
    import (
        "go_rpc_project/stub_rpc/handler"
        "net/rpc"
    )
    
    type HelloWorldServicer interface {
        HelloWorld(request string, response *string) error
    }
    
    func RegisterServicer(srv HelloWorldServicer) error {
        return rpc.RegisterName(handler.HelloWorldServiceName, srv)
    }
  • 相关阅读:
    平凡人生的忠告
    Visio建模
    2007的第一天....
    PowerDesigner12对SQL2005反向工程问题.
    强人画的画:)
    2006的最后一天
    ASP.NET生成树形显示的GridView
    输出由1~9组成和三组三位数,第二组是第一组的2倍,第三组是第一组的3倍,三组数字中无重复数字
    C#读取图片Exif信息
    C#读取数据库图片显示、缩小、更新
  • 原文地址:https://www.cnblogs.com/shenjianping/p/15863845.html
Copyright © 2020-2023  润新知