刘沙河 刘沙河
首页
  • Go语言基础

    • 数据类型
    • 反射
    • Go指针
  • Go语言进阶

    • go泛型
    • go条件编译
    • cgo教程
    • Go协程调度原理及GPM模型
    • Go内存管理
    • Go垃圾回收机制
    • Go语言内存对齐
  • Go语言实现原理

    • channel 实现原理
    • slice 实现原理
    • map 实现原理
    • sync.Mutex 实现原理
    • 乐观锁CAS 实现原理
    • singlefight 实现原理
  • gin框架

    • gin中间件原理
    • gin路由原理
  • gorm

    • GORM介绍和使用
    • GORM_CURD操作指南
  • go测试

    • benchmark基准测试
    • pprof 性能分析
  • python进阶

    • Numpy&Pandas
    • celery分布式任务队列
  • Django

    • Django 常见命令
    • middleware中间件
    • Django缓存系统
    • Django信号系统
    • Django REST Framework
  • Flask

    • Flask基础知识总结
    • Flask-SQLAlchemy
  • 爬虫

    • aiohttp
    • scrapy框架
  • Mysql

    • Mysql存储引擎和索引
    • MySQL主从复制
    • Mysql读写分离
    • 数据库分库分表
    • Mysql锁
    • Mysql事务和MVCC原理
    • 分库分表带来的读扩散问题
  • Redis

    • redis基础和数据类型
    • redis主从架构
    • redis哨兵架构
    • redis集群模式
    • 如何保证缓存和数据库双写一致
    • redis底层数据结构
    • redis分布式锁
  • Elasticsearch

    • es基本概念
    • es基础语法
    • es倒排索引
  • etcd

    • Go操作etcd
    • Raft原理
    • etcd分布式锁
  • kafka

    • 消息队列MQ总结
    • kafka 概述及原理
    • kafka 消费问题记录
    • 零拷贝技术
    • kafka分区规范
  • RabbitMQ

    • rabbitMQ基础
    • Go操作rabbitmq
  • RocketMQ

    • 可靠消息队列 rocketMQ
  • Http&Https

    • http&https
    • TCP和UDP
    • Ping 原理
  • RPC

    • RPC初识
    • grpc初识和实现
  • gRPC

    • grpc 初识
    • grpc 上下文 metadata
    • grpc 健康检查
    • grpc keepalive
    • grpc 命名解析
    • grpc 中间件&拦截器
    • grpc 负载均衡
    • grpc 身份认证
    • grpc 超时重试
    • grpc 链路追踪
    • grpc-gw将gRPC转RESTfu api
    • grpc-gw自定义选项
  • protobuf

    • protobuf 进阶
    • protobuf 编码原理
  • Docker

    • Docker基础
    • Docker常用命令
    • Dockerfile
    • Docker-Compose
    • Docker多阶段构建
    • Docker Config 教程
    • Docker Swarm 教程
    • Docker Stack 教程
    • Docker Buildx 教程
  • k8s

    • k8s 基础概念
    • k8s 集群架构
    • k8s 工作负载
    • Pod 网络
    • Service 网络
    • 外部接入网络
    • 一张图搞懂k8s各种pod
    • k8s 存储抽象
    • mac快速启动k8s
    • 自制申威架构k8s-reloader
  • go-kit

    • go-kit初识
    • go-kit启动http服务
    • go-kit集成gin启动服务
    • go-kit集成grpc和protobuf
    • go-kit中间件
    • go-kit服务注册发现与负载均衡
    • go-kit限流和熔断
    • go-kit链路追踪
    • go-kit集成Prometheus
  • 设计模式

    • 初识设计模式
    • 创建型模式
    • 结构型模式
    • 行为模式
  • 数据结构

    • 时间轮
    • 堆、双向链表、环形队列
    • 队列:优先队列
    • 队列:延迟队列
  • 算法

    • 递归算法
    • 枚举算法
    • 动态规划
    • 回溯算法
    • 分治算法
    • 贪心算法
    • LRU和LFU
    • 一致性哈希

花开半夏,半夏花开
首页
  • Go语言基础

    • 数据类型
    • 反射
    • Go指针
  • Go语言进阶

    • go泛型
    • go条件编译
    • cgo教程
    • Go协程调度原理及GPM模型
    • Go内存管理
    • Go垃圾回收机制
    • Go语言内存对齐
  • Go语言实现原理

    • channel 实现原理
    • slice 实现原理
    • map 实现原理
    • sync.Mutex 实现原理
    • 乐观锁CAS 实现原理
    • singlefight 实现原理
  • gin框架

    • gin中间件原理
    • gin路由原理
  • gorm

    • GORM介绍和使用
    • GORM_CURD操作指南
  • go测试

    • benchmark基准测试
    • pprof 性能分析
  • python进阶

    • Numpy&Pandas
    • celery分布式任务队列
  • Django

    • Django 常见命令
    • middleware中间件
    • Django缓存系统
    • Django信号系统
    • Django REST Framework
  • Flask

    • Flask基础知识总结
    • Flask-SQLAlchemy
  • 爬虫

    • aiohttp
    • scrapy框架
  • Mysql

    • Mysql存储引擎和索引
    • MySQL主从复制
    • Mysql读写分离
    • 数据库分库分表
    • Mysql锁
    • Mysql事务和MVCC原理
    • 分库分表带来的读扩散问题
  • Redis

    • redis基础和数据类型
    • redis主从架构
    • redis哨兵架构
    • redis集群模式
    • 如何保证缓存和数据库双写一致
    • redis底层数据结构
    • redis分布式锁
  • Elasticsearch

    • es基本概念
    • es基础语法
    • es倒排索引
  • etcd

    • Go操作etcd
    • Raft原理
    • etcd分布式锁
  • kafka

    • 消息队列MQ总结
    • kafka 概述及原理
    • kafka 消费问题记录
    • 零拷贝技术
    • kafka分区规范
  • RabbitMQ

    • rabbitMQ基础
    • Go操作rabbitmq
  • RocketMQ

    • 可靠消息队列 rocketMQ
  • Http&Https

    • http&https
    • TCP和UDP
    • Ping 原理
  • RPC

    • RPC初识
    • grpc初识和实现
  • gRPC

    • grpc 初识
    • grpc 上下文 metadata
    • grpc 健康检查
    • grpc keepalive
    • grpc 命名解析
    • grpc 中间件&拦截器
    • grpc 负载均衡
    • grpc 身份认证
    • grpc 超时重试
    • grpc 链路追踪
    • grpc-gw将gRPC转RESTfu api
    • grpc-gw自定义选项
  • protobuf

    • protobuf 进阶
    • protobuf 编码原理
  • Docker

    • Docker基础
    • Docker常用命令
    • Dockerfile
    • Docker-Compose
    • Docker多阶段构建
    • Docker Config 教程
    • Docker Swarm 教程
    • Docker Stack 教程
    • Docker Buildx 教程
  • k8s

    • k8s 基础概念
    • k8s 集群架构
    • k8s 工作负载
    • Pod 网络
    • Service 网络
    • 外部接入网络
    • 一张图搞懂k8s各种pod
    • k8s 存储抽象
    • mac快速启动k8s
    • 自制申威架构k8s-reloader
  • go-kit

    • go-kit初识
    • go-kit启动http服务
    • go-kit集成gin启动服务
    • go-kit集成grpc和protobuf
    • go-kit中间件
    • go-kit服务注册发现与负载均衡
    • go-kit限流和熔断
    • go-kit链路追踪
    • go-kit集成Prometheus
  • 设计模式

    • 初识设计模式
    • 创建型模式
    • 结构型模式
    • 行为模式
  • 数据结构

    • 时间轮
    • 堆、双向链表、环形队列
    • 队列:优先队列
    • 队列:延迟队列
  • 算法

    • 递归算法
    • 枚举算法
    • 动态规划
    • 回溯算法
    • 分治算法
    • 贪心算法
    • LRU和LFU
    • 一致性哈希
  • rpc

    • RPC 初识
    • RPC简单实现
      • python实现rpc协议
        • 1.http实现
        • 2.xml实现
        • 3.json实现
        • 4.zerorpc实现
      • go语言的rpc
        • 1. net/rec 实现 - hello word
        • 2. net/rpc 支持 json
        • 3. net/rpc 支持 http 协议
        • 4. !!! rpc 重新封装
    • grpc初识和实现
    • protobuf进阶
    • grpc进阶
  • grpc

  • protobuf

  • rpc+grpc
  • rpc
bigox
2022-06-16
目录

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()
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
  • 客户端
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))
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
  • 从上面的代码我们关注几点:
    1. 通信使用了json,所以json中的内容应该如何写就成为了一种协议
    2. 要想实现远程调用必须要要有网络连接
    3. 上面通过http连接,可以发现客户端和服务器端之间可以独立,实际上除了通过json以外还可以通过xml作为数据格式
    4. rpc不应该和http拿来比较
    5. rpc也不应该和restful拿来比较,我们完全可以把上述代码通过client和server端的封装将rpc编程基于restful来实现
    6. 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()
1
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))
1
2
3

# 3.json实现

pip install jsonrpclib-pelix -i https://pypi.douban.com/simple
1
  • 服务端
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()
1
2
3
4
5
6
7
8
  • 客户端
import jsonrpclib

server = jsonrpclib.ServerProxy('http://localhost:8081')
print(server.add(5,6))
1
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()
1
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"))
1
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
    29
  • Go客户端

    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
    23
  • python 客户端

    # 简版
    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
    15
    import 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
    14
  • rpcServer

    /*
     * @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
    35
  • serverStub

    /*
     * @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
    25
  • rpcClient

    /*
     * @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
    22
  • clientStub

    /*
     * @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
#rpc
上次更新: 2023/05/04, 15:30:48
RPC 初识
grpc初识和实现

← RPC 初识 grpc初识和实现→

最近更新
01
go与http代理
05-24
02
自制申威架构k8s-reloader
12-06
03
Docker Buildx 教程
12-01
更多文章>
Theme by Vdoing | Copyright © 2020-2024 小刘扎扎 | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式