在golang中实现RPC非常简单,有封装好的官方库和一些第三方库提供支持。

golang官方的net/rpc库使用encoding/gob进行编解码,支持tcp或http数据传输方式,由于其他语言不支持gob编解码方式,所以使用net/rpc库实现的RPC方法没办法进行跨语言调用。

golang官方还提供了net/rpc/jsonrpc库实现RPC方法,JSON RPC采用JSON进行数据编解码,因而支持跨语言调用。但目前的jsonrpc库是基于tcp协议实现的,暂时不支持使用http进行数据传输。

除了golang官方提供的rpc库,还有许多第三方库为在golang中实现RPC提供支持,大部分第三方rpc库的实现都是使用protobuf进行数据编解码,根据protobuf声明文件自动生成rpc方法定义与服务注册代码,在golang中可以很方便的进行rpc服务调用。

例子一: 使用net/rpc

目录如下:

rpc_server.go 代码如下:

package main

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

type Greeter struct {

}

type Request struct{
	Name string
}

type Response struct{
	Msg string
}

func (s *Greeter) Hello(req Request, rsp *Response) error{
	rsp.Msg = "hello " + req.Name
	return nil
}

func main() {
	//注册rpc 服务
	rpc.Register(new(Greeter))
	//采用http 协议作为 rpc 载体
	rpc.HandleHTTP()

	lis ,err := net.Listen("tcp", "127.0.0.1:8095")
	if err != nil {
		log.Fatalln("fatal error:", err)
	}

	fmt.Fprintf(os.Stdout, "%s", "start connection")
	http.Serve(lis , nil)
}

rpc_client.go 代码如下:

package main

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

type Request struct{
	Name string         //注意首字母大写,不然gob 时会出现问题: error: gob: type main.Request has no exported fields
}

type Response struct{
	Msg string
}

func main() {
	conn, err := rpc.DialHTTP("tcp", "127.0.0.1:8095")
	if err != nil {
		log.Fatalln("dial error:", err)
	}

	req := Request{"liang"}
	var rsq = Response{}

	err = conn.Call("Greeter.Hello", req, &rsq)
	if err != nil {
		log.Fatalln("Greeter error:", err)
	}
	fmt.Printf("%s",rsq.Msg)

}

注意事项:

1. Request, Response struct 的属性可见性,一定要为可见(public) 如下:

type Request struct{
   Name string         //注意首字母大写,不然gob 时会出现问题: error: gob: type main.Request has no exported fields
}

type Response struct{
   Msg string
}

例子二: 使用 net/rpc;  net/rpc/jsonrpc;

目录如下:

jsonrpc_server.go 代码如下:

package main

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

type Greeter struct {

}

type Request struct{
	Name string
}

type Response struct{
	Msg string
}

func (s *Greeter) Hello(req Request, rsp *Response) error{
	rsp.Msg = "hello " + req.Name
	return nil
}

func main() {
	//注册rpc 服务
	rpc.Register(new(Greeter))
	//采用http 协议作为 rpc 载体
	rpc.HandleHTTP()

	lis ,err := net.Listen("tcp", "127.0.0.1:8096")
	if err != nil {
		log.Fatalln("fatal error:", err)
	}

	fmt.Fprintf(os.Stdout, "%s", "start connection")
	//http.Serve(lis , nil)

	for {
		//接收客户端连接请求
		conn, err := lis.Accept()
		if err != nil {
			continue
		}

		go func(conn net.Conn) {
			fmt.Fprintf(os.Stdout, "%s", "new client in coming\n")
			jsonrpc.ServeConn(conn)
		}(conn)
	}
}

jsonrpc_client.go 代码如下:

package main

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

type Request struct{
	Name string         //注意首字母大写,不然gob 时会出现问题: error: gob: type main.Request has no exported fields
}

type Response struct{
	Msg string
}

func main() {
	//conn, err := rpc.DialHTTP("tcp", "127.0.0.1:8095")
	conn, err := jsonrpc.Dial("tcp", "127.0.0.1:8096")
	if err != nil {
		log.Fatalln("dial error:", err)
	}

	req := Request{"liang"}
	var rsq = Response{}

	err = conn.Call("Greeter.Hello", req, &rsq)
	if err != nil {
		log.Fatalln("Greeter error:", err)
	}
	fmt.Printf("%s",rsq.Msg)

}

总结:

我们分别使用net/rpc、net/rpc/jsonrpc, 实现了golang中的RPC服务端,

并给出了对应的golang客户端RPC调用示例,因为JSON和protobuf是支持多语言的,所以使用jsonrpc和protorpc实现的RPC方法我们是可以在其他语言中进行调用的。下面给出一个php客户端程序,通过socket连接调用jsonrpc实现的服务端RPC方法。

php客户端程序 调查jsonrpc微服务:

<?php
/**
 * Created by PhpStorm.
 * Date: 2020-01-02
 * Time: 15:56
 */

class JsonRPC
{
    private $conn;

    function __construct($host, $port) {
        $this->conn = fsockopen($host, $port, $errno, $errstr, 3);
        if (!$this->conn) {
            return false;
        }
    }

    public function Call($method, $params) {
        if (!$this->conn) {
            return false;
        }
        $err = fwrite($this->conn, json_encode(array(
                'method' => $method,
                'params' => array($params),
                'id'     => "999xxx888",
            ))."\n");
        if ($err === false) {
            return false;
        }
        stream_set_timeout($this->conn, 0, 3000);
        $line = fgets($this->conn);
        if ($line === false) {
            return NULL;
        }
        return json_decode($line,true);
    }

}

$client = new JsonRPC("127.0.0.1", 8096);
$args = array("name" => "xiaoli");
$result = $client->Call("Greeter.Hello", $args);

var_dump($result);
//输出如下:

/Users/zhaozhiliang/Documents/work_www/lbm/platform/trade_service/JsonRPC.php:46:
array(3) {
  'id' =>
  string(9) "999xxx888"
  'result' =>
  array(1) {
    'Msg' =>
    string(12) "hello xiaoli"
  }
  'error' =>
  NULL
}