golang中的rpc开发
阅读原文时间:2023年07月09日阅读:3

golang中实现RPC非常简单,官方提供了封装好的库,还有一些第三方的库

golang官方的net/rpc库使用encoding/gob进行编解码,支持tcp和http数据传输方式,由于其他语言不支持gob编解码方式,所以golang的RPC只支持golang开发的服务器与客户端之间的交互

官方还提供了net/rpc/jsonrpc库实现RPC方法,jsonrpc采用JSON进行数据编解码,因而支持跨语言调用,目前jsonrpc库是基于tcp协议实现的,同时也支持http传输方式

go快速体验rpc开发

  1. 服务端

    package main

    import (
    "net"
    "net/rpc"
    )

    type HelloService struct {}

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

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

    // 2. 注册处理逻辑handler
    _ = rpc.RegisterName("HelloService", &HelloService{})
    
    // 3. 启动server
    conn, _ := listener.Accept()  // 当一个新的连接进来的时候
    rpc.ServeConn(conn)

    }

  2. 客户端

    package main

    import (
    "fmt"
    "net/rpc"
    )

    func main() {
    // 1. 建立连接
    client, err := rpc.Dial("tcp", "192.168.0.101:1234")
    if err != nil {
    panic("连接失败")
    }
    //var ret *string nil 内存地址没有 只声明不初始化没有办法赋值,所以需要使用new方法声明并初始化
    // 方法一
    //var reply = new(string)
    // 方法二:
    var reply string // 空字符串,已经有内存地址了
    if err = client.Call("HelloService.Hello", "马亚南", &reply); err != nil {
    panic(err)
    }
    fmt.Println(reply)
    }

  • net/rpc完成的 1. call id的映射 2. 序列化和反序列化(编码和解码)
  • net完成的 网络通信:tcp

替换rpc的序列化协议为json

  1. go语言的rpc序列化协议是什么:Gob编码
  2. 能够替换成常见的序列化

go服务端

package main

import (
    "net"
    "net/rpc"
    "net/rpc/jsonrpc"
)

type HelloRPC struct {}
func (h *HelloRPC) Hello(request string, reply *string) error {
    *reply = "hello " + request
    return nil
}

func main() {
    listener, _ := net.Listen("tcp", ":1285")
    rpc.RegisterName("HelloRPC", &HelloRPC{})

    for {
        conn, _ := listener.Accept()
        // 不使用go默认的Gob序列化方式,而是改成json编码方式
        go rpc.ServeCodec(jsonrpc.NewServerCodec(conn))
    }
}

go客户端

package main

import (
    "fmt"
    "net"
    "net/rpc"
    "net/rpc/jsonrpc"
)

func main() {
    // rpc.Dial拨号会走Gob序列化协议,所以需要改成net.Dial
    // rpc.Dial返回的是client,而net.Dial返回的是conn
    conn, _ := net.Dial("tcp", "127.0.0.1:1285")
    var reply = new(string)
    client := rpc.NewClientWithCodec(jsonrpc.NewClientCodec(conn))
    _ = client.Call("HelloRPC.Hello", "马馨彤", reply)

    fmt.Println(*reply)
}

python客户端请求上面的go服务端,完成多语言rpc通信

import socket
import json

send_data = {
    "id": 0,
    "params": ["abc", "马馨彤22"],
    "method": "HelloRPC.Hello",
}

# tcp_client_socket = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM)
# tcp_client_socket.connect(("127.0.0.1", 1285))
# 上面两句可以用下面一句代替
tcp_client_socket = socket.create_connection(("localhost", 1285))
tcp_client_socket.send(json.dumps(send_data).encode())
# 获取服务器返回的数据
rsp = tcp_client_socket.recv(1024)
rsp = json.loads(rsp.decode())
print(rsp)  # {'id': 0, 'result': 'hello abc', 'error': None}
tcp_client_socket.close()

针对上面的升级版本

go服务端接收的request是一个json,python客户端传递的params列表中是一个json数据

  1. go服务端

点击查看代码

package main

import (
    "encoding/json"
    "fmt"
    "net"
    "net/rpc"
    "net/rpc/jsonrpc"
    "strconv"
)

type HelloRPC struct {}
func (h *HelloRPC) Hello(request string, reply *string) error {
    fmt.Println(request)
    var m1 map[string]interface{}
    json.Unmarshal([]byte(request), &m1)
    i, _ := strconv.Atoi(fmt.Sprintf("%1.0f", m1["age"]))
    m1["age"] = float64(i + 1)
    rsp, _ := json.Marshal(&m1)
    *reply = "hello " + string(rsp)
    return nil
}

func main() {
    listener, _ := net.Listen("tcp", ":1285")
    rpc.RegisterName("HelloRPC", &HelloRPC{})

    for {
        conn, _ := listener.Accept()
        // 不使用go默认的Gob序列化方式,而是改成json编码方式
        go rpc.ServeCodec(jsonrpc.NewServerCodec(conn))
    }
} 

2. python客户端

点击查看代码

import socket
import json

send_data = {
    "id": 0,
    "params": ['{"name": "Lisi","age": 18}'],
    "method": "HelloRPC.Hello",
}

# tcp_client_socket = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM)
# tcp_client_socket.connect(("127.0.0.1", 1285))
# 上面两句可以用下面一句代替
tcp_client_socket = socket.create_connection(("localhost", 1285))
tcp_client_socket.send(json.dumps(send_data).encode())
# 获取服务器返回的数据
rsp = tcp_client_socket.recv(1024)
rsp = json.loads(rsp.decode())
print(rsp)  # {'id': 0, 'result': 'hello abc', 'error': None}
tcp_client_socket.close() 

替换rpc的传输协议为http

同时使用json编码解码

  1. go服务端

    package main

    import (
    "io"
    "net/http"
    "net/rpc"
    "net/rpc/jsonrpc"
    )

    type HelloRPC struct {}
    func (h *HelloRPC) Hello(request string, reply *string) error {
    *reply = "hello " + request
    return nil
    }

    func main() {
    _ = rpc.RegisterName("HelloRPC", &HelloRPC{})
    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(":1234", nil)
    }

  2. python客户端

    import requests

    request = {
    "id": 0,
    "params": ["马艳娜"],
    "method": "HelloRPC.Hello"
    }

    rsp = requests.post("http://127.0.0.1:1234/jsonrpc", json=request)
    print(rsp.text)

进一步改造rpc调用的代码(封装达到grpc的使用效果)

  1. 目录结构图

  2. server.go

    package main

    import (
    "goRPC/new_helloworld/handler"
    "goRPC/new_helloworld/server_proxy"
    "net"
    "net/rpc"
    )

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

    // 2. 注册处理逻辑handler
    _ = server_proxy.RegisterHelloService(&handler.HelloService{})
    
    // 3. 启动server
    for {
        conn, _ := listener.Accept()  // 当一个新的连接进来的时候
        go rpc.ServeConn(conn)
    }

    }

  3. handler.go

    package handler

    const HelloServiceName = "handler/HelloService"

    // 我们关心的是HelloService的名字呢,还是结构体中的Hello方法呢?
    type HelloService struct {}

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

  4. server_proxy.go

    package server_proxy

    import (
    "goRPC/new_helloworld/handler"
    "net/rpc"
    )

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

    // 如何做到与handler中的结构体名称解耦? 我们关心的是函数: 鸭子类型
    func RegisterHelloService(srv HelloServer) error {
    return rpc.RegisterName(handler.HelloServiceName, srv)
    }

  5. client.go

    package main

    import (
    "fmt"
    "goRPC/new_helloworld/client_proxy"
    )

    func main() {
    // 建立链接
    client := client_proxy.NewHelloServiceClient("tcp", "127.0.0.1:1234")
    // 指向写业务逻辑,不想关注每个函数的名称
    var reply = new(string)
    err := client.Hello("马亚南121", reply)
    if err != nil {
    panic(err)
    }
    fmt.Println(*reply)
    }

  6. client_proxy.go

    package client_proxy

    import (
    "goRPC/new_helloworld/handler"
    "net/rpc"
    )

    type HelloServiceStub struct {
    *rpc.Client
    }
    // 在go语言中没有类、对象,就意味着没有初始化方法
    func NewHelloServiceClient(protocol, address string) *HelloServiceStub {
    client, _ := rpc.Dial(protocol, address)
    return &HelloServiceStub{Client: client}
    }

    func (c *HelloServiceStub) Hello(request string, reply *string) error {
    return c.Call(handler.HelloServiceName + ".Hello", request, reply)
    }

  • 上面这些概念在grpc中都有相关对应
  • 发自灵魂的拷问:server_proxy和client_proxy能否自动生成啊?为多种语言生成?
  • 答:上面两个问题都能解决: protobuf + grpc