zoukankan      html  css  js  c++  java
  • 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)
    }
    
    // 总结: 客户端和服务端都有接口,服务端的接口约束要实现的服务,客户端会根据接口定义去调用服务器的方法
    // 客户端的接口为了约束调用服务器的函数
    
  • 相关阅读:
    java中&和&&
    java保留字
    最优路径算法合集(附python源码)(原创)
    十大经典排序算法(python实现)(原创)
    电子欠款单设想(原创)
    羽毛球友谊赛规则(附带程序)(原创)
    基于python的opcode优化和模块按需加载机制研究(学习与个人思路)(原创)
    lazy_import源码解析(原创)
    多线程的音频打标记的python实现(原创)
    关于塔防游戏的浅析(原创)
  • 原文地址:https://www.cnblogs.com/double-W/p/12731756.html
Copyright © 2011-2022 走看看