Skip to content
本页目录

RPC ​

什么是 RPC ​

  • RPC(Remote Procedure Call) 远程过程调用, 简单理解是一个节点请求另一个节点提供的服务
  • 对应 RPC 的是本地过程调用,函数调用是最常见的本地过程调用
  • 将本地过程变成远程过程调用会面临各种问题

下面是一段 python 代码, 服务端注册一个 hello 方法

py
import zerorpc

class HelloRPC(object):
    def hello(self, name):
        return "Hello, %s" % name

s = zerorpc.Server(HelloRPC())
s.bind("tcp://0.0.0.0:3000")
s.run()

在客户端,连接到服务端后,就可以直接调用服务端的 hello 方法

py
import zerorpc

c = zerorpc.Client()
c.connect("tcp://localhost:3000")
print(c.hello("Shibin")) # Hello, Shibin

远程过程面临的问题 ​

本地函数放到另一个服务器上去运行,但是会引入很多新问题

  • Call 的 id 映射
  • 序列化和反序列化
  • 网络传输

快速体验 RPC ​

服务端

go
package main

import (
	"net"
	"net/rpc"
)

type HelloService struct {
}

func (s *HelloService) Hello(request string, reply *string) error {
	*reply = "Hello," + request
	return nil
}
func main() {
	// 注册一个 server
	listener, _ := net.Listen("tcp", ":8080")
	// 注册处理逻辑
	err := rpc.RegisterName("HelloService", &HelloService{})
	if err != nil {
		return
	}
	// 启动服务
	conn, _ := listener.Accept()
	rpc.ServeConn(conn)
}

客户端

go
package main

import (
	"fmt"
	"net/rpc"
)

func main() {
	client, err := rpc.Dial("tcp", "localhost:8080")
	if err != nil {
		panic("连接失败")
	}
	var reply *string = new(string)
	err = client.Call("HelloService.Hello", "Shibin", reply)
	if err != nil {
		panic("调用失败")
	}
	fmt.Println(*reply) // Hello,Shibin
}

替换 RPC 序列化协议 ​

上面客户端调用有点麻烦,直接使用 client.Hello 调用呢

如果要实现跨语言调用,首先需要了解

  • Go 语言的 RPC 的序列化和反序列化的协议是什么(Gob)
  • 能否换成常见的序列化协议。如 JSON

下面将数据改成 json 传输

服务端 ​

go
package main

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

type HelloService struct {
}

func (s *HelloService) Hello(request string, reply *string) error {
	*reply = "Hello," + request
	return nil
}
func main() {
	// 注册一个 server
	listener, _ := net.Listen("tcp", ":8080")
	// 注册处理逻辑
	err := rpc.RegisterName("HelloService", &HelloService{})
	if err != nil {
		return
	}

	for {
		conn, _ := listener.Accept()
		go rpc.ServeCodec(jsonrpc.NewServerCodec(conn))
	}
}

客户端 ​

go
package main

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

func main() {
	client, err := net.Dial("tcp", "localhost:8080")
	if err != nil {
		panic("连接失败")
	}
	var reply string
	newClient := rpc.NewClientWithCodec(jsonrpc.NewClientCodec(client))
	err = newClient.Call("HelloService.Hello", "Shibin", &reply)
	if err != nil {
		panic("调用失败")
	}
	fmt.Println(reply)
}

使用 python 发送请求

py
import json
import socket

request = {
    "id": 0,
    "params": ["Shibin"],
    "method": "HelloService.Hello"
}
client = socket.create_connection(("localhost", "8080"))
# 发送请求
client.send(json.dumps(request).encode())
# 接收请求
res = client.recv(4096)
# 数据转化为json
json = json.loads(res.decode())  
print(json)

nodejs 发送请求

ts
import net from 'net'

const data = {
  id: 0,
  params: ['Shibin'],
  method: 'HelloService.Hello'
}
const client = net.createConnection({
  port: 8080,
  host: '10.24.118.170'
}, () => {
  console.log('connected to server!')
  client.write(JSON.stringify(data))
})
client.on('data', (data) => {
  console.log(data.toString()) // {"id":0,"result":"Hello,Shibin","error":null}
  client.end()
})
client.on('end', () => {
  console.log('disconnected from server')
})

替换 RPC 传输协议 ​

将传输协议替换为 http 协议

服务端 ​

go
package main

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

type HelloService struct {
}

func (s *HelloService) Hello(request string, reply *string) error {
	*reply = "Hello," + request
	return nil
}
func main() {
	// 注册处理逻辑
	err := rpc.RegisterName("HelloService", &HelloService{})
	if err != nil {
		return
	}
	// server
	http.HandleFunc("/json", func(w http.ResponseWriter, r *http.Request) {
		var conn io.ReadWriteCloser = struct {
			io.Writer
			io.ReadCloser
		}{
			ReadCloser: r.Body,
			Writer:     w,
		}
		rpc.ServeRequest(jsonrpc.NewServerCodec(conn))
	})
	http.ListenAndServe(":8080", nil)
}

客户端 ​

下面使用 python 代码测试

py
import requests

request = {
    "id": 0,
    "params": ["body"],
    "method": "HelloService.Hello"
}

res = requests.post("http://localhost:8080/json", json=request)
print(res.text) # {"id":0,"result":"Hello,body","error":null}

也可以使用 nodejs 调用

ts
import axios from 'axios'

axios.post('http://localhost:8080/json', {
  id: 0,
  params: ['shibin'],
  method: 'HelloService.Hello'
}).then(res => {
  console.log(res.data) // { id: 0, result: 'Hello,shibin', error: null }
})