Go语言标准库学习之net/rpc——Server/Client之间通信的秘密

RPC(Remote Procedure Call)远程过程调用,它可以使一台主机上的进程调用另一台主机的进程,由以访为其他若干个主机提供服务,也就是我们常说的C/S服务,Server与Client之间通过rpc方式进行通信。下面向大叫刨析以下net/rpc标准库,希望对你有帮助。

一、Server和Client

1. server
(1)Server对象

在Server对象中定义了互斥锁用来保护请求数据,另外还包含请求信息和返回的信息以及注册的服务。

// Server represents an RPC Server.
type Server struct {
	serviceMap sync.Map   // map[string]*service
	reqLock    sync.Mutex // protects freeReq
	freeReq    *Request
	respLock   sync.Mutex // protects freeResp
	freeResp   *Response
}

我们可以通过NewServer初始化一个Server对象:

// NewServer returns a new Server.
func NewServer() *Server {
	return &Server{}
}

Server对象有8个方法,下面进行介绍。

(2)func (server *Server) Register(rcvr interface{}) error

Register用来向Server注册rpc服务,rpc服务必须满足下面五种要求:

  • 函数必须是导出的
  • 必须有两个导出类型参数
  • 第一个参数是接收参数
  • 第二个参数是返回给客户端参数,必须是指针类型
  • 函数还要有一个返回值error

注册之后的服务,Client可以进行远程调用。

还有一个和Registry类似的方法:

// RegisterName类似Register,但使用提供的name代替rcvr的具体类型名作为服务名。
func (server *Server) RegisterName(name string, rcvr interface{}) error {
	return server.register(rcvr, name, true)
}
(3)监听器
// Accept接收监听器l获取的连接,然后服务每一个连接。Accept会阻塞,调用者应另开线程:"go server.Accept(l)"
func (server *Server) Accept(lis net.Listener)
(4)服务端处理请求的相关方法
  • ServeConn方法

    // ServeConn在单个连接上执行server。ServeConn会阻塞,服务该连接直到客户端挂起。
    // 调用者一般应另开线程调用本函数:"go server.ServeConn(conn)"。ServeConn在该连接使用gob(参见encoding/gob包)有线格式。
    // 要使用其他的编解码器,可调用ServeCodec方法。
    func (server *Server) ServeConn(conn io.ReadWriteCloser)
    
  • ServeCodec方法

    // ServeCodec类似ServeConn,但使用指定的编解码器,以编码请求主体和解码回复主体。
    func (server *Server) ServeCodec(codec ServerCodec)
    
  • ServeRequest方法

    // ServeRequest类似ServeCodec,但异步的服务单个请求。它不会在调用结束后关闭codec。
    func (server *Server) ServeRequest(codec ServerCodec) error
    
  • ServeHTTP方法

    // ServeHTTP实现了回应RPC请求的http.Handler接口。
    func (server *Server) ServeHTTP(w http.ResponseWriter, req *http.Request)
    
  • HandleHTTP方法

    // HandleHTTP注册server的RPC信息HTTP处理器对应到rpcPath,注册server的debug信息HTTP处理器对应到debugPath。
    // HandleHTTP会注册到http.DefaultServeMux。之后,仍需要调用http.Serve(),一般会另开线程:"go http.Serve(l, nil)"
    func (server *Server) HandleHTTP(rpcPath, debugPath string)
    
2. client
(1)Client对象
// Client类型代表RPC客户端。同一个客户端可能有多个未返回的调用,也可能被多个go程同时使用。
type Client struct {
	codec ClientCodec

	reqMutex sync.Mutex // protects following
	request  Request

	mutex    sync.Mutex // protects following
	seq      uint64
	pending  map[uint64]*Call
	closing  bool // user has called Close
	shutdown bool // server has told us to stop
}
(2)新建一个Client
  • 初始化一个Client

    // NewClient返回一个新的Client,以管理对连接另一端的服务的请求。它添加缓冲到连接的写入侧,以便将回复的头域和有效负载作为一个单元发送。
    func NewClient(conn io.ReadWriteCloser) *Client
    
  • 初始化一个Client并指定编码器

    // 另外还有一个NewClientWithCodec方法,NewClientWithCodec类似NewClient,但使用指定的编解码器,以编码请求主体和解码回复主体。
    func NewClientWithCodec(codec ClientCodec) *Client
    
(3)连接服务端
  • 通过指定的网络和地址与RPC服务端连接。

    func Dial(network, address string) (*Client, error)
    
  • 通过指定的网络和地址与在默认HTTP RPC路径监听的HTTP RPC服务端连接。

    func DialHTTP(network, address string) (*Client, error)
    
  • 通过在指定的网络、地址和路径与HTTP RPC服务端连接。

    func DialHTTPPath(network, address, path string) (*Client, error)
    
(4)调用服务端的服务
  • Call方法

    // Call调用指定的方法,等待调用返回,将结果写入reply,然后返回执行的错误状态。
    func (client *Client) Call(serviceMethod string, args interface{}, reply interface{}) error
    
  • 异步调用(Go方法)

    // Go异步的调用函数。
    // 本方法Call结构体类型指针的返回值代表该次远程调用。通道类型的参数done会在本次调用完成时发出信号(通过返回本次Go方法的返回值)。
    // 如果done为nil,Go会申请一个新的通道(写入返回值的Done字段);如果done非nil,done必须有缓冲,否则Go方法会故意崩溃。
    func (client *Client) Go(serviceMethod string, args interface{}, reply interface{}, done chan *Call) *Call
    
(5)关闭Client
func (client *Client) Close() error

二、go语言标准库中的JSON-RPC

JSON-RPC,是一个无状态且轻量级的远程过程调用(RPC)传送协议,其传递内容通过 JSON 为主。相较于一般的 REST 通过网址(如 GET /user)调用远程服务器,JSON-RPC 直接在内容中定义了欲调用的函数名称(如 {“method”: “getUser”}),这也令开发者不会陷于该使用 PUT 或者 PATCH 的问题之中。
更多JSON-RPC约定参见:https://zh.wikipedia.org/wiki/JSON-RPC

1. 连接Server端
// Dial在指定的网络和地址连接一个JSON-RPC服务端。
func Dial(network, address string) (*rpc.Client, error)
2. 创建CLient
  • NewClient

    // NewClient返回一个新的rpc.Client,以管理对连接另一端的服务的请求。
    func NewClient(conn io.ReadWriteCloser) *rpc.Client
    
  • NewClientCodec

    // NewClientCodec返回一个在连接上使用JSON-RPC的rpc.ClientCodec。
    func NewClientCodec(conn io.ReadWriteCloser) rpc.ClientCodec
    
  • NewServerCodec

    // NewServerCodec返回一个在连接上使用JSON-RPC的rpc. ServerCodec。
    func NewServerCodec(conn io.ReadWriteCloser) rpc.ServerCodec
    
3. 处理客户端连接请求
// ServeConn在单个连接上执行DefaultServer。ServeConn会阻塞,服务该连接直到客户端挂起。
// 调用者一般应另开线程调用本函数:"go serveConn(conn)"。ServeConn在该连接使用JSON编解码格式。
func ServeConn(conn io.ReadWriteCloser)

三、rpc通信的三种方式

使用rpc方式通信需要通过下面几步才能完成:

Server端:

  • 初始化一个Server对象
  • 注册服务
  • 绑定处理器
  • 监听服务

Client 端:

  • 初始化一个Client对象
  • 连接RPC服务端
  • 发送请求
  • 接收返回值

下面介绍net/rpc的三种连接方式。

1. Http方式

Server端:

package main

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

type Student struct {
	Name   string
	School string
}
type RpcServer struct{}

func (r *RpcServer) Introduce(student Student, words *string) error {
	fmt.Println("student: ", student)
	*words = fmt.Sprintf("Hello everyone, my name is %s, and I am from %s", student.Name, student.School)
	return nil
}

func main() {
	rpcServer := new(RpcServer)
	// 注册rpc服务
	_ = rpc.Register(rpcServer)
	//把服务处理绑定到http协议上
	rpc.HandleHTTP()
	log.Println("http rpc service start success addr:8080")
	err := http.ListenAndServe(":8080", nil)
	if err != nil {
		log.Fatal(err)
	}
}

Client端:

package main

import (
	"fmt"
	"net/rpc"
)

func main() {
	type Student struct {
		Name   string
		School string
	}
    // 连接RPC服务端 Dial会调用NewClient初始化一个Client
	client, err := rpc.DialHTTP("tcp", "127.0.0.1:8080")
	if err != nil {
		panic(err)
	}
	defer client.Close()
	// 发送请求
	var reply string
	err = client.Call("RpcServer.Introduce", &Student{Name: "random_w", School: "Secret"}, &reply)
	if err != nil {
		panic(err)
	}
	fmt.Println(reply)
}

测试:

启动服务端:

$ go run httpRPC.go
2020/07/15 16:28:30 http rpc service start success addr:8080
student:  {random_w Secret}

启动客户端:

$ go run httpRPCClient.go
Hello everyone, my name is random_w, and I am from Secret

从客户端的日志可以看到我们成功执行了服务端的Introduce方法,服务端的日志中也显示了接收到的Student信息。

2. TCP 方式

Server端:

package main

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

type Student struct {
	Name   string
	School string
}
type RpcServer struct{}

func (r *RpcServer) Introduce(student Student, words *string) error {
	fmt.Println("student: ", student)
	*words = fmt.Sprintf("Hello everyone, my name is %s, and I am from %s", student.Name, student.School)
	return nil
}

func main() {
	rpcServer := new(RpcServer)
	// 注册rpc服务
	_ = rpc.Register(rpcServer)
	// 指定rpc模式为TCP模式,地址为127.0.0.1:8081
	tcpAddr, _ := net.ResolveTCPAddr("tcp", "127.0.0.1:8081")
	tcpListen, err := net.ListenTCP("tcp", tcpAddr)
	if err != nil {
		log.Fatal(err)
	}
	log.Println("tcp rpc service start success addr:8081")
	for {
		// 监听Client发送的请求
		conn, err3 := tcpListen.Accept()
		if err3 != nil {
			continue
		}
		// 创建一个goroutine处理请求
		go rpc.ServeConn(conn)
	}
}

Client端:

package main

import (
	"fmt"
	"net/rpc"
)

func main() {
	type Student struct {
		Name   string
		School string
	}
	// 连接RPC服务端 Dial会调用NewClient初始化一个Client
	client, err := rpc.Dial("tcp", "127.0.0.1:8081")
	if err != nil {
		panic(err)
	}
	defer client.Close()
	// 发送请求
	var reply string
	err = client.Call("RpcServer.Introduce", &Student{Name: "random_w", School: "Secret"}, &reply)
	if err != nil {
		panic(err)
	}
	fmt.Println(reply)
}

测试:

启动服务端:

$ go run tcpRPC.go
2020/07/15 16:13:21 tcp rpc service start success addr:8081
student:  {random_w Secret}

运行客户端:

$ go run tcpRPCClient.go
Hello everyone, my name is random_w, and I am from Secret

从客户端的日志可以看到我们成功执行了服务端的Introduce方法,服务端的日志中也显示了接收到的Student信息。

3. jsonrpc方式

jsonrpc方式支持跨语言调用。

Server端:

package main

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

type Student struct {
	Name   string
	School string
}

type RpcServer struct{}

func (r *RpcServer) Introduce(student Student, words *string) error {
	fmt.Println("student: ", student)
	*words = fmt.Sprintf("Hello everyone, my name is %s, and I am from %s", student.Name, student.School)
	return nil
}

func main() {
	rpcServer := new(RpcServer)
	// 注册rpc服务
	_ = rpc.Register(rpcServer)
    //jsonrpc是基于TCP协议的,现在他还不支持http协议
	tcpAddr, _ := net.ResolveTCPAddr("tcp", "127.0.0.1:8082")
	tcpListen, err := net.ListenTCP("tcp", tcpAddr)
	if err != nil {
		panic(err)
	}
	log.Println("tcp json-rpc service start success addr:8082")
	for {
		// 监听客户端请求
		conn, err3 := tcpListen.Accept()
		if err3 != nil {
			continue
		}
		go jsonrpc.ServeConn(conn)
	}
}

Client端:

package main

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

func main() {
	type Student struct {
		Name   string
		School string
	}
	client, err := jsonrpc.Dial("tcp", "127.0.0.1:8082")
	if err != nil {
		panic(err)
	}
	defer client.Close()
	var reply string
	// 发送json格式的数据
	err = client.Call("RpcServer.Introduce", &Student{
		Name:   "random_w",
		School: "Secret",
	}, &reply)
	if err != nil {
		panic(err)
	}
	fmt.Println(reply)
}

测试:

启动服务端:

$ go run jsonRPC.go
2020/07/15 17:15:42 tcp json-rpc service start success addr:8082
student:  {random_w Secret}

运行客户端:

$ go run jsonRPCClient.go
Hello everyone, my name is random_w, and I am from Secret

从客户端的日志可以看到我们成功执行了服务端的Introduce方法,服务端的日志中也显示了接收到的Student信息。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值