RPC的使用

  RPC(Remote Procedure Call,远程过程调用)可以简单粗暴地理解为本地代码调用远程RPC服务器的函数或方法,并且调用是跨语言的,即本地代码可以使用PHP、Java等语言来编码,并不要求和RPC服务器的实现语言保持一致。
 
  从上面的描述可以看出,RPC和我们平常使用的HTTP API有点类似,只是RPC效率更高且大多用于微服务之间的内部通信。
 
  先来看一下使用net/rpc包实现RPC服务端和RPC客户端:
 
  RPC服务端
 
package main

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

type Calculator struct{}

type Nums struct {
   Num1 float64
   Num2 float64
}

type Result struct {
   Num float64
}

// Add 加法运算
func (calc *Calculator) Add(nums Nums, result *Result) (err error) {
   fmt.Println("有客户端调用Calculator.Add()")
   result.Num = nums.Num1 + nums.Num2
   return
}

// Subtract 减法运算
func (calc *Calculator) Subtract(nums Nums, result *Result) (err error) {
   fmt.Println("有客户端调用Calculator.Subtract()")
   result.Num = nums.Num1 - nums.Num2
   return
}

// Multiply 乘法运算
func (calc *Calculator) Multiply(nums Nums, result *Result) (err error) {
   fmt.Println("有客户端调用Calculator.Multiply()")
   result.Num = nums.Num1 * nums.Num2
   return
}

// Divide 除法运算
func (calc *Calculator) Divide(nums Nums, result *Result) (err error) {
   fmt.Println("有客户端调用Calculator.Divide()")

   // 重要提醒:这里不能用defer...recover的方式捕获panic,因为recover()恒为nil
   if nums.Num2 == 0 {
      return errors.New("除数不能为零")
   }

   result.Num = nums.Num1 / nums.Num2
   return
}

func main() {
   var err error
   var listener net.Listener

   err = rpc.Register(new(Calculator))
   if err != nil {
      panic("注册RPC服务失败:" + err.Error())
   }

   rpc.HandleHTTP()

   listener, err = net.Listen("tcp", ":10086") // RPC服务端口号
   if err != nil {
      panic("监听RPC服务失败:" + err.Error())
   }

   defer func(listener net.Listener) {
      _ = listener.Close()
   }(listener)

   err = http.Serve(listener, nil)
   if err != nil {
      panic("启动RPC服务失败:" + err.Error())
   }
}
 
  RPC客户端
 
package main

import (
   "fmt"
   "net/rpc"
)

type Nums struct {
   Num1 float64
   Num2 float64
}

type Result struct {
   Num float64
}

func main() {
   var err error
   var client *rpc.Client
   var nums Nums
   var result Result

   client, err = rpc.DialHTTP("tcp", ":10086")
   if err != nil {
      panic("连接RPC服务器失败:" + err.Error())
   }

   // ========== 加法运算 ========== //
   nums.Num1 = 5
   nums.Num2 = 2
   result = Result{}
   err = client.Call("Calculator.Add", nums, &result)
   if err != nil {
      fmt.Println("调用Calculator.Add()失败:" + err.Error())
   } else {
      fmt.Printf("调用Calculator.Add()成功:%v + %v = %v \n", nums.Num1, nums.Num2, result.Num)
      // 调用Calculator.Add()成功:5 + 2 = 7
   }

   // ========== 减法运算 ========== //
   nums.Num1 = 5
   nums.Num2 = 2
   result = Result{}
   err = client.Call("Calculator.Subtract", nums, &result)
   if err != nil {
      fmt.Println("调用Calculator.Subtract()失败:" + err.Error())
   } else {
      fmt.Printf("调用Calculator.Subtract()成功:%v - %v = %v \n", nums.Num1, nums.Num2, result.Num)
      // 调用Calculator.Subtract()成功:5 - 2 = 3
   }

   // ========== 乘法运算 ========== //
   nums.Num1 = 5
   nums.Num2 = 2
   result = Result{}
   err = client.Call("Calculator.Multiply", nums, &result)
   if err != nil {
      fmt.Println("调用Calculator.Multiply()失败:" + err.Error())
   } else {
      fmt.Printf("调用Calculator.Multiply()成功:%v * %v = %v \n", nums.Num1, nums.Num2, result.Num)
      // 调用Calculator.Multiply()成功:5 * 2 = 10
   }

   // ========== 除法运算(除数不为零) ========== //
   nums.Num1 = 5
   nums.Num2 = 2
   result = Result{}
   err = client.Call("Calculator.Divide", nums, &result)
   if err != nil {
      fmt.Println("调用Calculator.Divide()失败:" + err.Error())
   } else {
      fmt.Printf("调用Calculator.Divide()成功:%v / %v = %v \n", nums.Num1, nums.Num2, result.Num)
      // 调用Calculator.Divide()成功:5 / 2 = 2.5
   }

   // ========== 除法运算(除数为零) ========== //
   nums.Num1 = 5
   nums.Num2 = 0
   result = Result{}
   err = client.Call("Calculator.Divide", nums, &result)
   if err != nil {
      fmt.Println("调用Calculator.Divide()失败:" + err.Error())
      // 调用Calculator.Divide()失败:除数不能为零
   } else {
      fmt.Printf("调用Calculator.Divide()成功:%v / %v = %v \n", nums.Num1, nums.Num2, result.Num)
   }

   // ========== 取模运算(服务端不存在该方法) ========== //
   nums.Num1 = 5
   nums.Num2 = 2
   result = Result{}
   err = client.Call("Calculator.Mod", nums, &result)
   if err != nil {
      fmt.Println("调用Calculator.Mod()失败:" + err.Error())
      // 调用Calculator.Mod()失败:rpc: can't find method Calculator.Mod
   } else {
      fmt.Printf("调用Calculator.Mod()成功:%v %% %v = %v \n", nums.Num1, nums.Num2, result.Num)
   }
}

// ========== 输出结果·开始 ========== //
// 调用Calculator.Add()成功:5 + 2 = 7
// 调用Calculator.Subtract()成功:5 - 2 = 3
// 调用Calculator.Multiply()成功:5 * 2 = 10
// 调用Calculator.Divide()成功:5 / 2 = 2.5
// 调用Calculator.Divide()失败:除数不能为零
// 调用Calculator.Mod()失败:rpc: can't find method Calculator.Mod
// ========== 输出结果·结束 ========== //
 
  net/rpc包采用gob编解码,该编解码方式是Go语言独有的,其它编程语言不支持这种编解码方式,所以接下来我们改为使用net/rpc/jsonrpc包实现RPC服务端。
 
package main

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

type Calculator struct{}

type Nums struct {
   Num1 float64
   Num2 float64
}

type Result struct {
   Num float64
}

// Add 加法运算
func (calc *Calculator) Add(nums Nums, result *Result) (err error) {
   fmt.Println("有客户端调用Calculator.Add()")
   result.Num = nums.Num1 + nums.Num2
   return
}

// Subtract 减法运算
func (calc *Calculator) Subtract(nums Nums, result *Result) (err error) {
   fmt.Println("有客户端调用Calculator.Subtract()")
   result.Num = nums.Num1 - nums.Num2
   return
}

// Multiply 乘法运算
func (calc *Calculator) Multiply(nums Nums, result *Result) (err error) {
   fmt.Println("有客户端调用Calculator.Multiply()")
   result.Num = nums.Num1 * nums.Num2
   return
}

// Divide 除法运算
func (calc *Calculator) Divide(nums Nums, result *Result) (err error) {
   fmt.Println("有客户端调用Calculator.Divide()")

   // 重要提醒:这里不能用defer...recover的方式捕获panic,因为recover()恒为nil
   if nums.Num2 == 0 {
      return errors.New("除数不能为零")
   }

   result.Num = nums.Num1 / nums.Num2
   return
}

func main() {
   var err error
   var listener net.Listener

   err = rpc.Register(new(Calculator))
   if err != nil {
      panic("注册RPC服务失败:" + err.Error())
   }

   listener, err = net.Listen("tcp", ":10086") // RPC服务端口号
   if err != nil {
      panic("监听RPC服务失败:" + err.Error())
   }

   defer func(listener net.Listener) {
      _ = listener.Close()
   }(listener)

   for {
      var conn net.Conn
      conn, err = listener.Accept()
      if err != nil {
         panic("连接RPC服务失败:" + err.Error())
      }

      go func(conn net.Conn) {
         defer func(conn net.Conn) {
            _ = conn.Close()
         }(conn)

         jsonrpc.ServeConn(conn)
      }(conn)
   }
}
 
  由于使用JSON编解码,而绝大多数编程语言都支持JSON,所以这次就不再使用Go语言实现RPC客户端了,改为使用PHP实现RPC客户端。
 
<?php

/**
 * JSONRPC客户端
 */
class JSONRPCClient
{
    private $conn;

    /**
     * 构造方法
     *
     * @param string $url RPC服务器主机地址
     */
    public function __construct($url)
    {
        $parse = (array)parse_url($url);
        $hostname = "{$parse['scheme']}://{$parse['host']}";
        $port = $parse['port'];

        $this->conn = fsockopen($hostname, $port);
    }

    /**
     * 调用RPC服务器的方法
     *
     * @param string $method 方法名
     * @param array $params 方法参数
     * @return array 方法返回值
     */
    public function call($method, $params)
    {
        $reply = [];

        if ($this->conn) {
            $data = ['method' => $method, 'params' => [$params], 'id' => time()]; // 说明:id参数可不传递
            $data = json_encode($data);

            $fwrite = fwrite($this->conn, $data);
            if ($fwrite !== false) {
                $fgets = fgets($this->conn);
                $reply = json_decode($fgets, true);
            }
        }

        return $reply;
    }


    /**
     * 断开与RPC服务器的连接
     *
     * @return void
     */
    public function disconnect()
    {
        if ($this->conn) {
            fclose($this->conn);
        }
    }
}


$client = new JSONRPCClient('tcp://***.***.***.***:10086');


//========== 加法运算 ==========//
$nums = ['Num1' => 5, 'Num2' => 2];
$reply = $client->call('Calculator.Add', $nums);
if ($reply['error'] !== NULL) {
    echo "调用Calculator.Add()失败:{$reply['error']}" . PHP_EOL;
} else {
    echo "调用Calculator.Add()成功:{$nums['Num1']} + {$nums['Num2']} = {$reply['result']['Num']}" . PHP_EOL;
    // 调用Calculator.Add()成功:5 + 2 = 7
}


//========== 减法运算 ==========//
$nums = ['Num1' => 5, 'Num2' => 2];
$reply = $client->call('Calculator.Subtract', $nums);
if ($reply['error'] !== NULL) {
    echo "调用Calculator.Subtract()失败:{$reply['error']}" . PHP_EOL;
} else {
    echo "调用Calculator.Subtract()成功:{$nums['Num1']} - {$nums['Num2']} = {$reply['result']['Num']}" . PHP_EOL;
    // 调用Calculator.Subtract()成功:5 - 2 = 3
}


//========== 乘法运算 ==========//
$nums = ['Num1' => 5, 'Num2' => 2];
$reply = $client->call('Calculator.Multiply', $nums);
if ($reply['error'] !== NULL) {
    echo "调用Calculator.Multiply()失败:{$reply['error']}" . PHP_EOL;
} else {
    echo "调用Calculator.Multiply()成功:{$nums['Num1']} * {$nums['Num2']} = {$reply['result']['Num']}" . PHP_EOL;
    // 调用Calculator.Multiply()成功:5 * 2 = 10
}


//========== 除法运算(除数不为零) ==========//
$nums = ['Num1' => 5, 'Num2' => 2];
$reply = $client->call('Calculator.Divide', $nums);
if ($reply['error'] !== NULL) {
    echo "调用Calculator.Divide()失败:{$reply['error']}" . PHP_EOL;
} else {
    echo "调用Calculator.Divide()成功:{$nums['Num1']} / {$nums['Num2']} = {$reply['result']['Num']}" . PHP_EOL;
    // 调用Calculator.Divide()成功:5 / 2 = 2.5
}


//========== 除法运算(除数为零) ==========//
$nums = ['Num1' => 5, 'Num2' => 0];
$reply = $client->call('Calculator.Divide', $nums);
if ($reply['error'] !== NULL) {
    echo "调用Calculator.Divide()失败:{$reply['error']}" . PHP_EOL;
    // 调用Calculator.Divide()失败:除数不能为零
} else {
    echo "调用Calculator.Divide()成功:{$nums['Num1']} / {$nums['Num2']} = {$reply['result']['Num']}" . PHP_EOL;
}


//========== 取模运算(服务端不存在该方法) ==========//
$nums = ['Num1' => 5, 'Num2' => 2];
$reply = $client->call('Calculator.Mod', $nums);
if ($reply['error'] !== NULL) {
    echo "调用Calculator.Mod()失败:{$reply['error']}" . PHP_EOL;
    // 调用Calculator.Mod()失败:rpc: can't find method Calculator.Mod
} else {
    echo "调用Calculator.Mod()成功:{$nums['Num1']} % {$nums['Num2']} = {$reply['result']['Num']}" . PHP_EOL;
}


$client->disconnect();


//========== 输出结果·开始 ==========//
// 调用Calculator.Add()成功:5 + 2 = 7
// 调用Calculator.Subtract()成功:5 - 2 = 3
// 调用Calculator.Multiply()成功:5 * 2 = 10
// 调用Calculator.Divide()成功:5 / 2 = 2.5
// 调用Calculator.Divide()失败:除数不能为零
// 调用Calculator.Mod()失败:rpc: can't find method Calculator.Mod
//========== 输出结果·结束 ==========//

Copyright © 2024 码农人生. All Rights Reserved