RPC简单实现
# python实现rpc协议
# 1.http实现
- 服务端
from http.server import HTTPServer, BaseHTTPRequestHandler
host = ('', 8003)
class TodoHandler(BaseHTTPRequestHandler):
def do_GET(self):
from urllib.parse import urlparse, parse_qsl
import json
parsed_url = urlparse(self.path)
qs = dict(parse_qsl(parsed_url.query))
a = int(qs.get("a", 0))
b = int(qs.get("b", 0))
self.send_response(200)
self.send_header('Content-type', "application/json")
self.end_headers()
self.wfile.write(json.dumps(
{
"result": a + b
}
).encode("utf-8"))
if __name__ == '__main__':
server = HTTPServer(host, TodoHandler)
print("Starting server, listen at: %s:%s" % host)
server.serve_forever()
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
- 客户端
import requests
class Client:
def __init__(self, url):
self.url = url
def add(self, a, b):
payload = {
"method": "add",
"params": [a, b],
"jsonrpc": "2.0",
"id": 0,
}
response = requests.post(self.url, json=payload).json()
print(response)
return response["result"]
cli = Client("http://localhost:8001/jsonrpc")
print(cli.add(1,2))
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
- 从上面的代码我们关注几点:
- 通信使用了json,所以json中的内容应该如何写就成为了一种协议
- 要想实现远程调用必须要要有网络连接
- 上面通过http连接,可以发现客户端和服务器端之间可以独立,实际上除了通过json以外还可以通过xml作为数据格式
- rpc不应该和http拿来比较
- rpc也不应该和restful拿来比较,我们完全可以把上述代码通过client和server端的封装将rpc编程基于restful来实现
- rpc可以理解为一种调用风格,具体实现可以随意写,至于底层是走tcp协议还是http协议看需求
# 2.xml实现
- 服务端
from xmlrpc.server import SimpleXMLRPCServer
class calculate:
def add(self, x, y):
return x + y
def multiply(self, x, y):
return x * y
def subtract(self, x, y):
return abs(x-y)
def divide(self, x, y):
return x/y
obj = calculate()
server = SimpleXMLRPCServer(("localhost", 8088))
# 将实例注册给rpc server
server.register_instance(obj)
print("Listening on port 8088")
server.serve_forever()
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
- 客户端
from xmlrpc import client
server = client.ServerProxy("http://localhost:8088")
print(server.add(2, 3))
2
3
# 3.json实现
pip install jsonrpclib-pelix -i https://pypi.douban.com/simple
- 服务端
from jsonrpclib.SimpleJSONRPCServer import SimpleJSONRPCServer
server = SimpleJSONRPCServer(('localhost', 8081))
server.register_function(pow)
server.register_function(lambda x,y: x+y, 'add')
server.register_function(lambda x: x, 'ping')
server.serve_forever()
2
3
4
5
6
7
8
- 客户端
import jsonrpclib
server = jsonrpclib.ServerProxy('http://localhost:8081')
print(server.add(5,6))
2
3
4
# 4.zerorpc实现
- 服务端
import zerorpc
class HelloRPC(object):
def hello(self, name):
return "Hello, %s" % name
s = zerorpc.Server(HelloRPC())
s.bind("tcp://0.0.0.0:4242")
s.run()
2
3
4
5
6
7
8
9
- 客户端
import zerorpc
c = zerorpc.Client()
c.connect("tcp://127.0.0.1:4242")
print(c.hello("RPC"))
2
3
4
5
# go语言的rpc
# 1. net/rec 实现 - hello word
服务端
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(){ _ = rpc.RegisterName("HelloService", &HelloService{}) listener, err := net.Listen("tcp", ":1234") if err != nil { panic("监听端口失败") } conn, err := listener.Accept() if err != nil { panic("建立链接失败") } rpc.ServeConn(conn) }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27其中Hello方法
必须满足Go语言的RPC规则
:方法只能有两个可序列化的参数,其中第二个参数是指针类型,并且返回一个error类型,同时必须是公开的方法
。然后就可以将HelloService类型的对象注册为一个RPC服务:(TCP RPC服务)。
其中
rpc.Register
函数调用会将对象类型中所有满足RPC规则的对象方法注册为RPC函数,所有注册的方法会放在“HelloService”服务空间之下。然后我们建立一个唯一的TCP链接,并且通过rpc.ServeConn函数在该TCP链接上为对方提供RPC服务。客户端
func main() { client, err := rpc.Dial("tcp", "localhost:1234") if err != nil { log.Fatal("dialing:", err) } var reply string err = client.Call("HelloService.Hello", "hello", &reply) if err != nil { log.Fatal(err) } fmt.Println(reply) }
1
2
3
4
5
6
7
8
9
10
11
12
13
14首先是通过
rpc.Dial
拨号RPC服务,然后通过client.Call
调用具体的RPC方法。在调用client.Call
时,第一个参数是用点号链接的RPC服务名字和方法名字,第二和第三个参数分别我们定义RPC方法的两个参数
。
# 2. net/rpc 支持 json
标准库的RPC默认采用Go语言特有的gob编码,因此从其它语言调用Go语言实现的RPC服务将比较困难。在互联网的微服务时代,每个RPC以及服务的使用者都可能采用不同的编程语言,因此跨语言是互联网时代RPC的一个首要条件。得益于RPC的框架设计,Go语言的RPC其实也是很容易实现跨语言支持的。
Go语言的RPC框架有两个比较有特色的设计:一个是RPC数据打包时可以通过插件实现自定义的编码和解码;另 一个是RPC建立在抽象的io.ReadWriteCloser接口之上的,我们可以将RPC架设在不同的通讯协议之上。这里我们将尝试通过官方自带的net/rpc/jsonrpc扩展实现一个跨语言的PPC。
服务端
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(){ rpc.RegisterName("HelloService", new(HelloService)) listener, err := net.Listen("tcp", ":1234") if err != nil { panic("启动错误") } for { conn, err := listener.Accept() if err != nil { panic("接收") } go rpc.ServeCodec(jsonrpc.NewServerCodec(conn)) } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29Go客户端
package main import ( "fmt" "net" "net/rpc" "net/rpc/jsonrpc" ) func main(){ conn, err := net.Dial("tcp", "localhost:1234") if err != nil { panic("连接错误") } client := rpc.NewClientWithCodec(jsonrpc.NewClientCodec(conn)) var reply string err = client.Call("HelloService.Hello", "imooc", &reply) if err != nil { panic("调用错误") } fmt.Println(reply) }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23python 客户端
# 简版 import socket import json request = { "method": "HelloService.Hello", "params": ["test"], "id": 0 } client = socket.create_connection(("localhost", 4379)) client.sendall(json.dumps(request).encode()) res = client.recv(1096) print(json.loads(res.decode()))
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15import json import socket import itertools import time class JSONClient(object): def __init__(self, addr): self.socket = socket.create_connection(addr) self.id_counter = itertools.count() def __del__(self): self.socket.close() def call(self, name, *params): request = dict(id=next(self.id_counter), params=list(params), method=name) self.socket.sendall(json.dumps(request).encode()) # This must loop if resp is bigger than 4K response = self.socket.recv(4096) response = json.loads(response.decode()) if response.get('id') != request.get('id'): raise Exception("expected id=%s, received id=%s: %s" %(request.get('id'), response.get('id'), response.get('error'))) if response.get('error') is not None: raise Exception(response.get('error')) return response.get('result') def close(self): self._socket.close() if __name__ == '__main__': rpc = JSONClient(("localhost", 1234)) args = "hello" print(rpc.call("HelloService.Hello", args))
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# 3. net/rpc 支持 http 协议
服务端
/* * @date: 2021/10/24 * @desc: ... */ package main import ( "io" "net/http" "net/rpc" "net/rpc/jsonrpc" ) type HelloService struct{} func (h *HelloService) Hello(request string, reply *string) error { *reply = "hello " + request return nil } func main() { rpc.RegisterName("HelloService", new(HelloService)) http.HandleFunc("/jsonrpc", 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(":1234", nil) }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36客户端
import socket import json import requests request = { "method": "HelloService.Hello", "params": ["test"], "id": 0 } res = requests.post(url="http://127.0.0.1:8080/jsonRPC", json=request) print(res.text)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 4. !!! rpc 重新封装
Golang 封装典型面向对象封装思想
rpcHandler
/* * @date: 2021/10/25 * @desc: ... */ package handler const HelloServiceName = "HelloService/Hello" type HelloService struct {} func (h *HelloService) Hello(request string,reply *string) error { *reply = "hello " + request + " pro plus" return nil }
1
2
3
4
5
6
7
8
9
10
11
12
13
14rpcServer
/* * @date: 2021/10/25 * @desc: ... */ package main import ( "net" "net/rpc" "net/rpc/jsonrpc" "picturePro/rpcServerProPlus/handler" "picturePro/rpcServerProPlus/serverStub" ) func main() { listener ,err := net.Listen("tcp",":8000") err = serverStub.RegisterHelloService(&handler.HelloService{}) if err != nil { panic(err) } if err != nil { panic("监听端口失败") } for { conn,err := listener.Accept() if err != nil { panic(err.Error()) } go rpc.ServeCodec(jsonrpc.NewServerCodec(conn)) } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35serverStub
/* * @date: 2021/10/25 * @desc: ... */ package serverStub import ( "net/rpc" "picturePro/rpcServerProPlus/handler" ) // 鸭子模型:实现了Hello 方法的 struct 都可以看成 HelloServicer type HelloServicer interface { Hello(request string,reply *string) error } func RegisterHelloService(srv HelloServicer) error { err := rpc.RegisterName(handler.HelloServiceName, srv) if err != nil { return err } return nil }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25rpcClient
/* * @date: 2021/10/25 * @desc: ... */ package main import ( "fmt" "picturePro/rpcServerProPlus/clientStub" ) func main() { client := clientStub.NewHelloServiceClient("tcp",":8000") var reply string err := client.Hello("word!", &reply) // 像调用本地方法一样调用Hello方法 if err != nil { panic(err.Error()) } fmt.Println(reply) }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22clientStub
/* * @date: 2021/10/25 * @desc: ... */ package clientStub import ( "net" "net/rpc" "net/rpc/jsonrpc" "picturePro/rpcServerProPlus/handler" ) type HelloServiceStub struct { *rpc.Client } // NewHelloServiceClient 构造 HelloServiceClient func NewHelloServiceClient(network, address string) HelloServiceStub { conn, err := net.Dial(network, address) if err != nil { panic(err) } client := rpc.NewClientWithCodec(jsonrpc.NewClientCodec(conn)) return HelloServiceStub{ client, } } func (h *HelloServiceStub) Hello(request string, reply *string) error { err := h.Call(handler.HelloServiceName+".Hello", "word!", &reply) if err != nil { return err } return nil }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38