RPC和Protobuf(一)

 

RPC和Protobuf

  RPC是远程过程调用(Remote Procedure Call) 的缩写, 通俗地来说就是调用远处的一个函数,远处到底有多远?可能是同一个机器的另一个进程,也可能是远在火星好奇号上的一个秘密东西。因为RPC涉及的函数可能非常远,远到它们之间说着不同的语言,所以我们需要解决沟通语言的障碍。而Protobuf由于支持多种不同的语言(甚至不支持的语言也可以拓展),其本身特性也非常方便描述服务的接口,因此非常适合作为RPC世界的接口交流语言。

 

RPC版“Hello World”

  Go语言的RPC包路径为net/rpc,也就是说Go默认是支持使用rpc的,在目前的行业中RPC是分布式系统中不同节点间流行的通行方式,在互联网时代,RPC已经和IPC(进程通信)一样成为一个不可或缺的基础构件。

server.go代码如下:

package main

import (
	"log"
	"net"
	"net/rpc"
)

// 构建一个承载函数的结构体
type HelloServer struct {

}

// 为承载对象添加实现方法,方法只能有两个可序列化参数:一个请求、一个相应,并且有一个error返回值,同时必须是个公开的方法
func (p *HelloServer) Hello (request string, reply *string) error {
	*reply = "Hello "+request
	return nil
}


func main() {
	// RegisterName 会将对象类型中所有满足rpc规则的对象方法注册到rpc服务中,所有注册的方法会放到HelloServer服务空间之下
	rpc.RegisterName("grpc_Pro.HelloServer",new(HelloServer))

	listener, err := net.Listen("tcp",":1234")
	if err != nil {
		log.Fatal("ListenTCP error:", err)
	}

	// 建立唯一的TCP连接,并通过rpc.ServerConn函数在该TCP连接上为对方提供RPC服务
	conn, err := listener.Accept()
	if err != nil {
		log.Fatal("Accept error:",err)
	}
	rpc.ServeConn(conn)
}  

client.go代码如下:

package main

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

// 自定义命名空间,与服务注册的名字一致
const HelloServiceName = "grpc_Pro.HelloServer"

// 使用一个接口来约束注册的结构体
type HelloServiceInterface = interface {
	Hello (request string, reply *string) error
}

// 将结构体注册到rpc的命名空间下,注意这里的命名空间可以是一个路径片段,这个函数是为了隐式实现HelloService是否实现接口需求
func RegisterHelloService(svc HelloServiceInterface) error {
	return rpc.RegisterName(HelloServiceName, svc)
}


func main() {
	// 首先通过rpc.Dial拨号rpc服务
	client, err := rpc.Dial("tcp","localhost:1234")
	if err != nil {
		log.Fatal("dialing: ",err)
	}

	var reply string
	// 通过client.Call()时,第一个参数是用点号连接的RPC服务名字和方法名字,第二个和第三个参数分别是定义rpc方法的两个参数
	err = client.Call(HelloServiceName+".Hello","hello",&reply)
	if err != nil {
		log.Fatal(err)
	}

	fmt.Println(reply)
}  

 

更安全的RPC接口

以上的代码中rpc并没有进行封装,下面我们我们将进行封装,使其更加安全。

safe_server.go代码如下:

package main

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

// 定义服务结构体
type HelloService struct {

}

// 定义服务空间名,之后要与客户端一致绑定
const HelloServiceName = "grpc.Server"

// 服务端接口要求方法实现
type HelloServiceInterface = interface {
	Hellos(request string, reply *string) error
}

// 将结构体注册到rpc的命名空间下,注意这里的命名空间可以是一个路径片段,隐式约束实现接口中的方法后才能注册
func RegisterHelloService(svc HelloServiceInterface) error {
	// 将注册一这个名字为空间的,可以通过映射到这个结构体下,在客户端通过.调用对应的方法
	return rpc.RegisterName(HelloServiceName, svc)
}

// 服务端实现方法,注意这个函数名要和client端的.名字保持一致
func (p *HelloService) Hellos(request string, reply *string) error {
	*reply = "hello :" + request
	return nil
}

func main() {
	// 将结构体注册到rpc中,前提式实现了方法
	RegisterHelloService(new(HelloService))
	listener, err := net.Listen("tcp",":1234")
	if err != nil {
		log.Fatal("ListenTCP error: ",err)
	}

	for {
		fmt.Println("listenning...")
		// 监听服务
		conn, err := listener.Accept()
		if err != nil {
			log.Fatal("Accept error:", err)
		}
		// 将监听到的服务挂在在rpc上
		go rpc.ServeConn(conn)
	}
}

safe_client.go代码如下:

package main

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

// rpc服务空间
const HelloServiceNames = "grpc.Server"

// 客户端接口,要求客户端实现的方法
type HelloServiceInterfaces = interface {
	Hello(request string, reply *string) error
}

// 客户端结构体,需要有roc的方法
type HelloServiceClient struct {
	*rpc.Client
}

// 约束实现接口,这种方式在执行时判断,如果不是_,其实式放回的接口对象
var _ HelloServiceInterfaces = (*HelloServiceClient)(nil)

/* 隐式约束结构体实现接口
//方式一:
func RegisterHelloServices(svc HelloServiceInterfaces) error {
	return rpc.RegisterName(HelloServiceNames, svc)
}

//方式二:
var _ HelloServiceInterfaces = new(HelloServiceClient)
/
 */

// 完成rpc拨号,并返回客户端对象
func DiaHelloServiceClient(network, address string) (*HelloServiceClient, error) {
	c, err := rpc.Dial(network, address)
	if err != nil {
		return nil, err
	}

	return &HelloServiceClient{Client: c}, nil
}

// 客户端方法实现
func (p *HelloServiceClient) Hello(request string, reply *string) error {
	// 客户端服务调用远端程序
	return p.Client.Call(HelloServiceNames+".Hellos", request, reply)
}


func main() {
	// 返回客户端对象
	client, err := DiaHelloServiceClient("tcp","localhost:1234")
	if err != nil {
		log.Fatal("dialing...",err)
	}

	var reply string
	// 客户端调用方法
	err = client.Hello("hello Mr.Wang", &reply)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println("result: ",reply)
}

// 总结: 客户端和服务端都有接口,服务端的接口约束要实现的服务,客户端会根据接口定义去调用服务器的方法
// 客户端的接口为了约束调用服务器的函数

 

posted @ 2020-04-19 15:31  独角兕大王  阅读(1039)  评论(0编辑  收藏  举报