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)
}

 

上一篇:debug 3的配置


下一篇:JAVA版的Sleep Sort