zoukankan      html  css  js  c++  java
  • 更安全的RPC接口

    在涉及RPC的应用中,作为开发人员一般至少有3种角色:首先是服务器端实现RPC方法的开发人员,其次是客户端调用RPC方法的人员,最后也是最重要的是制定服务器端和客户端RPC接口规范的设计人员。为了简化将以上几种角色的工作全部放到一起,虽然看似实现简单,但是不利于后期的维护和工作的切割。

    1. 如果要重构HelloService服务,第一步需要明确服务的名字和接口:

      我们将RPC服务的接口规范分为3部分:首先是服务的名字,然后是服务要实现的详细方法列表,最后是注册该类型服务的函数。为了避免名字冲突,我们在RPC服务的名字中增加了包路径前缀(这个是RPC服务抽象的包路径,并非完全等价于GO语言的包路径)。RegisterHelloService注册服务时,编译器会要求传入的对象满足HelloServiceInterface接口。
    2. 在定义了RPC服务接口实现规范之后,客户端就可以根据规范编写RPC调用的代码了:
      服务端代码:
    
    package main
    
    import (
    	"log"
    	"net"
    	"net/rpc"
    )
    const HelloServiceName = "path/to/pkg.HelloService"
    type HelloServiceInterface interface {
    	Hello(request string, reply *string) error
    }
    
    func RegisterHelloService(svc HelloServiceInterface) error{
    	return rpc.RegisterName(HelloServiceName, svc)
    }
    
    type HelloService struct {}
    func (p *HelloService) Hello(request string, reply *string) error{
    	*reply = "hello:" + request
    	return nil
    }
    
    func main(){
    	RegisterHelloService(new(HelloService))
    
    	listener, err := net.Listen("tcp", ":1234")
    	if err!=nil{
    		log.Fatal("ListenTCP error:", err)
    	}
    
    	conn, err := listener.Accept()
    	if err!=nil{
    		log.Fatal("Accept error:", err)
    	}
    
    	rpc.ServeConn(conn)
    }
    
    

    客户端代码:

    package main
    
    import (
    	"fmt"
    	"log"
    	"net/rpc"
    )
    
    const HelloServiceName = "path/to/pkg.HelloService"
    func main(){
    	client, err := rpc.Dial("tcp", "localhost:1234")
    	if err!=nil{
    		log.Fatal("dialing:", err)
    	}
    
    	var reply string
    	err = client.Call(HelloServiceName+".Hello", "hello", &reply)
    	if err!=nil{
    		log.Fatal(err)
    	}
    
    	fmt.Println(reply)
    }
    
    

    由于两个文件我放在了同一个包下面,但是各自有main方法,所以需要分别编译,这就需要每个文件里面都有一个const HelloServiceName.
    4. 为了简化客户端用户调用RPC函数,我们可以在接口规范部分增加对客户端的简单包装:

    我们在接口规范中针对客户端新增加了HelloServiceClient类型,该类型也必须满足HelloServiceInterface接口,这样客户端用户就可以直接通过接口调用RPC函数,同时提供了一个DialHelloService()函数,直接拨号HelloService服务。
    客户端最新代码:

    package main
    
    import (
    	"fmt"
    	"log"
    	"net/rpc"
    )
    
    const HelloServiceName = "path/to/pkg.HelloService"
    type HelloServiceClient struct {
    	*rpc.Client
    }
    
    var _HelloServiceInterface = (*HelloServiceClient)(nil)
    func DialHelloService(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(HelloServiceName+".Hello", request, reply)
    }
    
    func main(){
    	client, err := DialHelloService("tcp", "localhost:1234")
    	if err!=nil{
    		log.Fatal("dialing:", err)
    	}
    
    	var reply string
    	err = client.Call(HelloServiceName+".Hello", "hello", &reply)
    	if err!=nil{
    		log.Fatal(err)
    	}
    
    	fmt.Println(reply)
    }
    
    

    现在客户端用户不用担心RPC方法名字或参数类型不匹配等低级错误的发生。
    5. 最后是基于RPC接口规范编写的服务器端代码:

    package main
    
    import (
    	"log"
    	"net"
    	"net/rpc"
    )
    const HelloServiceName = "path/to/pkg.HelloService"
    type HelloServiceInterface interface {
    	Hello(request string, reply *string) error
    }
    
    func RegisterHelloService(svc HelloServiceInterface) error{
    	return rpc.RegisterName(HelloServiceName, svc)
    }
    
    type HelloService struct {}
    func (p *HelloService) Hello(request string, reply *string) error{
    	*reply = "hello:" + request
    	return nil
    }
    
    func main(){
    	RegisterHelloService(new(HelloService))
    
    	listener, err := net.Listen("tcp", ":1234")
    	if err!=nil{
    		log.Fatal("ListenTCP error:", err)
    	}
    	for{
    		conn, err := listener.Accept()
    		if err!=nil{
    			log.Fatal("Accept error:", err)
    		}
    		go rpc.ServeConn(conn)
    	}
    
    
    
    }
    
    1. 运行结果
    2. 在新的RPC服务器端实现中,我们用RegisterHelloService()函数来注册函数,这样不仅可以避免命名服务名称的工作,同时也保证了传入的服务对象满足RPC接口的定义,最后新的服务改为支持多个TCP链接,然后为每个TCP链接提供RPC服务。
  • 相关阅读:
    Linux驱动入门(三)Led驱动
    Linux驱动入门(二)操作硬件
    mysql表的完整性约束
    数据库操作
    初识数据库
    mysql的安装、启动和基础配置 —— windows版本
    Socket网络编程
    python进阶之多线程(简单介绍协程)
    python进阶多进程(进程池,进程间通信)
    python基础之异常处理
  • 原文地址:https://www.cnblogs.com/pangqianjin/p/14616076.html
Copyright © 2011-2022 走看看