作者:断雁难飞_920 | 来源:互联网 | 2023-02-04 21:48
RPC和ProtobufRPC是远程过程调用(RemoteProcedureCall)的缩写,通俗地来说就是调用远处的一个函数,远处到底有多远?可能是同一个机器的另一个进程,也可
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)
}
// 总结: 客户端和服务端都有接口,服务端的接口约束要实现的服务,客户端会根据接口定义去调用服务器的方法
// 客户端的接口为了约束调用服务器的函数