在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
}