Go+Python双语言混合开发 第三部分 grpc入门和进阶 Go开发学习 第2章 go语言的rpc开发体验 学习笔记

307 阅读5分钟

Go+Python双语言混合开发 第三部分 grpc入门和进阶 Go开发学习 第2章 go语言的rpc开发体验 学习笔记

第2章 go语言的rpc开发体验

2.1 go语言的rpc之hello world

Go语言的RPC包的路径为net/rpc,也就是放在了net包目录下面。因此我们可以猜测该RPC包是建立在net包基础之上的。在第一章“Hello, World”革命一节最后,我们基于http实现了一个打印例子。下面我们尝试基于rpc实现一个类似的例子。

2.1.1 服务端:

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)

}

其中Hello方法必须满足Go语言的RPC规则:方法只能有两个可序列化的参数,其中第二个参数是指针类型,并且返回一个error类型,同时必须是公开的方法

然后就可以将HelloService类型的对象注册为一个RPC服务:(TCP RPC服务)。

其中rpc.Register函数调用会将对象类型中所有满足RPC规则的对象方法注册为RPC函数,所有注册的方法会放在“HelloService”服务空间之下。然后我们建立一个唯一的TCP链接,并且通过rpc.ServeConn函数在该TCP链接上为对方提供RPC服务。

2.1.2 客户端

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

首先是通过rpc.Dial拨号RPC服务,然后通过client.Call调用具体的RPC方法。在调用client.Call时,第一个参数是用点号链接的RPC服务名字和方法名字,第二和第三个参数分别我们定义RPC方法的两个参数

2.2 rpc支持json

标准库的RPC默认采用Go语言特有的gob编码,因此从其它语言调用Go语言实现的RPC服务将比较困难。在互联网的微服务时代,每个RPC以及服务的使用者都可能采用不同的编程语言,因此跨语言是互联网时代RPC的一个首要条件。得益于RPC的框架设计,Go语言的RPC其实也是很容易实现跨语言支持的。

Go语言的RPC框架有两个比较有特色的设计:一个是RPC数据打包时可以通过插件实现自定义的编码和解码;另 一个是RPC建立在抽象的io.ReadWriteCloser接口之上的,我们可以将RPC架设在不同的通讯协议之上。这里我们将尝试通过官方自带的net/rpc/jsonrpc扩展实现一个跨语言的PPC。

首先是基于json编码重新实现RPC服务:

服务端

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

代码中最大的变化是用rpc.ServeCodec函数替代了rpc.ServeConn函数,传入的参数是针对服务端的json编解码器。

客户端

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

python客户端

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))
import json
import socket# 客户端 发送一个数据,再接收一个数据
request = {
    "id":0,
    "params":["imooc"],
    "method": "HelloService.Hello"
}

client = socket.create_connection(("localhost", 1234))
client.sendall(json.dumps(request).encode())

# This must loop if resp is bigger than 4K
response = client.recv(4096)
response = json.loads(response.decode())
print(response)

client.close() #关闭这个链接

2.3 基于http的rpc

服务端

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

2.4 进一步改进rpc调用过程

前面的rpc调用虽然简单,但是和普通的http的调用差异不大,这次我们解决下面的问题:

1. serviceName统一和名称冲突的问题

    1. server端和client端如何统一serviceName
    2. 多个server的包中serviceName同名的问题

新建handler/handler.go文件内容如下: 为什么要新建一个文件? - 解耦

package handler

const HelloServiceName = "handler/HelloService"
1. 服务端
package main

import (
    "net"
    "net/rpc"
    "start/rpc_ch01/handler"
)



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

func main(){
    _ = rpc.RegisterName(handler.HelloServiceName, &HelloService{})
    listener, err := net.Listen("tcp", ":1234")
    if err != nil {
        panic("监听端口失败")
    }
    conn, err := listener.Accept()
    if err != nil {
        panic("建立链接失败")
    }
    rpc.ServeConn(conn)

}
2. 客户端
package main

import (
    "fmt"
    "net/rpc"
    "start/rpc_ch01/handler"
)

func main() {
    client, err := rpc.Dial("tcp", "localhost:1234")
    if err != nil {
        panic("连接到服务器失败")
    }

    var reply string
    err = client.Call(handler.HelloServiceName+".Hello", "imooc", &reply)
    if err != nil {
        panic("服务调用失败")
    }

    fmt.Println(reply)
}

2. 继续屏蔽HelloServiceName和Hello函数名称

1. handler源码
package handler

type HelloService struct{}

func (s *HelloService) Hello(request string, reply *string) error {
    *reply = "hello " + request
    return nil
}
2. 服务端代理
package server_proxy

import "net/rpc"

const HelloServiceName = "handler/HelloService"

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

func RegisterHelloService(srv HelloServiceInterface) error {
    return rpc.RegisterName(HelloServiceName, srv)
}
3. 服务端
package main

import (
    "net"
    "net/rpc"
    "start/rpc_ch01/handler"
    "start/rpc_ch01/server_proxy"
)

func main(){
    hellohandler := &handler.HelloService{}
    _ = server_proxy.RegisterHelloService(hellohandler)
    listener, err := net.Listen("tcp", ":1234")
    if err != nil {
        panic("监听端口失败")
    }
    conn, err := listener.Accept()
    if err != nil {
        panic("建立链接失败")
    }
    rpc.ServeConn(conn)

}
4. 客户端代理

在这里插入图片描述 在这里插入图片描述 在这里插入图片描述 在这里插入图片描述 在这里插入图片描述 在这里插入图片描述 在这里插入图片描述